Merge branch 'wip-MDL-33950-master' of git://github.com/marinaglancy/moodle
This commit is contained in:
+66
-46
@@ -59,6 +59,7 @@ $search_text = optional_param('s', '', PARAM_CLEANHTML);
|
||||
$maxfiles = optional_param('maxfiles', -1, PARAM_INT); // maxfiles
|
||||
$maxbytes = optional_param('maxbytes', 0, PARAM_INT); // maxbytes
|
||||
$subdirs = optional_param('subdirs', 0, PARAM_INT); // maxbytes
|
||||
$accepted_types = optional_param_array('accepted_types', '*', PARAM_RAW);
|
||||
|
||||
// the path to save files
|
||||
$savepath = optional_param('savepath', '/', PARAM_PATH);
|
||||
@@ -75,22 +76,16 @@ if (!$course = $DB->get_record('course', array('id'=>$courseid))) {
|
||||
}
|
||||
$PAGE->set_course($course);
|
||||
|
||||
// init repository plugin
|
||||
$sql = 'SELECT i.name, i.typeid, r.type FROM {repository} r, {repository_instances} i '.
|
||||
'WHERE i.id=? AND i.typeid=r.id';
|
||||
if ($repository = $DB->get_record_sql($sql, array($repo_id))) {
|
||||
$type = $repository->type;
|
||||
if (file_exists($CFG->dirroot.'/repository/'.$type.'/lib.php')) {
|
||||
require_once($CFG->dirroot.'/repository/'.$type.'/lib.php');
|
||||
$classname = 'repository_' . $type;
|
||||
try {
|
||||
$repo = new $classname($repo_id, $contextid, array('ajax'=>false, 'name'=>$repository->name, 'type'=>$type));
|
||||
} catch (repository_exception $e){
|
||||
print_error('pluginerror', 'repository');
|
||||
}
|
||||
} else {
|
||||
print_error('invalidplugin', 'repository');
|
||||
}
|
||||
if ($repo_id) {
|
||||
// Get repository instance information
|
||||
$repooptions = array(
|
||||
'ajax' => false,
|
||||
'mimetypes' => $accepted_types
|
||||
);
|
||||
$repo = repository::get_repository_by_id($repo_id, $contextid, $repooptions);
|
||||
|
||||
// Check permissions
|
||||
$repo->check_capability();
|
||||
}
|
||||
|
||||
$context = context::instance_by_id($contextid);
|
||||
@@ -285,40 +280,65 @@ case 'sign':
|
||||
break;
|
||||
|
||||
case 'download':
|
||||
$thefile = $repo->get_file($fileurl, $filename);
|
||||
$filesize = filesize($thefile['path']);
|
||||
if (($maxbytes!=-1) && ($filesize>$maxbytes)) {
|
||||
print_error('maxbytes');
|
||||
// Check that user has permission to access this file
|
||||
if (!$repo->file_is_accessible($fileurl)) {
|
||||
print_error('storedfilecannotread');
|
||||
}
|
||||
if (!empty($thefile)) {
|
||||
$record = new stdClass();
|
||||
$record->filepath = $savepath;
|
||||
$record->filename = $filename;
|
||||
$record->component = 'user';
|
||||
$record->filearea = 'draft';
|
||||
$record->itemid = $itemid;
|
||||
$record->license = '';
|
||||
$record->author = '';
|
||||
|
||||
$now = time();
|
||||
$record->timecreated = $now;
|
||||
$record->timemodified = $now;
|
||||
$record->userid = $USER->id;
|
||||
$record->contextid = $user_context->id;
|
||||
|
||||
$sourcefield = $repo->get_file_source_info($thefile['url']);
|
||||
$record->source = repository::build_source_field($sourcefield);
|
||||
try {
|
||||
$info = repository::move_to_filepool($thefile['path'], $record);
|
||||
redirect($home_url, get_string('downloadsucc', 'repository'));
|
||||
} catch (moodle_exception $e) {
|
||||
// inject target URL
|
||||
$e->link = $PAGE->url->out();
|
||||
echo $OUTPUT->header(); // hack: we need the embedded header here, standard error printing would not use it
|
||||
throw $e;
|
||||
// If file is already a reference, set $fileurl = file source, $repo = file repository
|
||||
// note that in this case user may not have permission to access the source file directly
|
||||
// so no file_browser/file_info can be used below
|
||||
if ($repo->has_moodle_files()) {
|
||||
$file = repository::get_moodle_file($fileurl);
|
||||
if ($file && $file->is_external_file()) {
|
||||
$fileurl = $file->get_reference();
|
||||
$repo_id = $file->get_repository_id();
|
||||
$repo = repository::get_repository_by_id($repo_id, $contextid, $repooptions);
|
||||
}
|
||||
}
|
||||
|
||||
$record = new stdClass();
|
||||
$record->filepath = $savepath;
|
||||
$record->filename = $filename;
|
||||
$record->component = 'user';
|
||||
$record->filearea = 'draft';
|
||||
$record->itemid = $itemid;
|
||||
$record->license = '';
|
||||
$record->author = '';
|
||||
|
||||
$now = time();
|
||||
$record->timecreated = $now;
|
||||
$record->timemodified = $now;
|
||||
$record->userid = $USER->id;
|
||||
$record->contextid = $user_context->id;
|
||||
$record->sortorder = 0;
|
||||
|
||||
$sourcefield = $repo->get_file_source_info($fileurl);
|
||||
$record->source = repository::build_source_field($sourcefield);
|
||||
|
||||
if ($repo->has_moodle_files()) {
|
||||
$fileinfo = $repo->copy_to_area($fileurl, $record, $maxbytes);
|
||||
redirect($home_url, get_string('downloadsucc', 'repository'));
|
||||
} else {
|
||||
print_error('cannotdownload', 'repository');
|
||||
$thefile = $repo->get_file($fileurl, $filename);
|
||||
if (!empty($thefile['path'])) {
|
||||
$filesize = filesize($thefile['path']);
|
||||
if ($maxbytes != -1 && $filesize>$maxbytes) {
|
||||
unlink($thefile['path']);
|
||||
print_error('maxbytes');
|
||||
}
|
||||
try {
|
||||
$info = repository::move_to_filepool($thefile['path'], $record);
|
||||
redirect($home_url, get_string('downloadsucc', 'repository'));
|
||||
} catch (moodle_exception $e) {
|
||||
// inject target URL
|
||||
$e->link = $PAGE->url->out();
|
||||
echo $OUTPUT->header(); // hack: we need the embedded header here, standard error printing would not use it
|
||||
throw $e;
|
||||
}
|
||||
} else {
|
||||
print_error('cannotdownload', 'repository');
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
+62
-49
@@ -521,9 +521,10 @@ abstract class repository {
|
||||
*
|
||||
* @param int $repositoryid repository ID
|
||||
* @param stdClass|int $context context instance or context ID
|
||||
* @param array $options additional repository options
|
||||
* @return repository
|
||||
*/
|
||||
public static function get_repository_by_id($repositoryid, $context) {
|
||||
public static function get_repository_by_id($repositoryid, $context, $options = array()) {
|
||||
global $CFG, $DB;
|
||||
|
||||
$sql = 'SELECT i.name, i.typeid, r.type FROM {repository} r, {repository_instances} i WHERE i.id=? AND i.typeid=r.id';
|
||||
@@ -539,10 +540,15 @@ abstract class repository {
|
||||
if (is_object($context)) {
|
||||
$contextid = $context->id;
|
||||
}
|
||||
$repository = new $classname($repositoryid, $contextid, array('type'=>$type));
|
||||
$options['type'] = $type;
|
||||
$options['typeid'] = $record->typeid;
|
||||
if (empty($options['name'])) {
|
||||
$options['name'] = $record->name;
|
||||
}
|
||||
$repository = new $classname($repositoryid, $contextid, $options);
|
||||
return $repository;
|
||||
} else {
|
||||
throw new moodle_exception('error');
|
||||
throw new repository_exception('invalidplugin', 'repository');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -609,16 +615,16 @@ abstract class repository {
|
||||
}
|
||||
|
||||
/**
|
||||
* To check if the context id is valid
|
||||
* Checks if user has a capability to view the current repository in current context
|
||||
*
|
||||
* @static
|
||||
* @param int $contextid
|
||||
* @param stdClass $instance
|
||||
* @return bool
|
||||
*/
|
||||
public static function check_capability($contextid, $instance) {
|
||||
$context = get_context_instance_by_id($contextid);
|
||||
$capability = has_capability('repository/'.$instance->type.':view', $context);
|
||||
public final function check_capability() {
|
||||
$capability = false;
|
||||
if (preg_match("/^repository_(.*)$/", get_class($this), $matches)) {
|
||||
$type = $matches[1];
|
||||
$capability = has_capability('repository/'.$type.':view', $this->context);
|
||||
}
|
||||
if (!$capability) {
|
||||
throw new repository_exception('nopermissiontoaccess', 'repository');
|
||||
}
|
||||
@@ -651,45 +657,60 @@ abstract class repository {
|
||||
* @return stored_file|null
|
||||
*/
|
||||
public static function get_moodle_file($source) {
|
||||
$params = unserialize(base64_decode($source));
|
||||
if (empty($params) || !is_array($params)) {
|
||||
return null;
|
||||
}
|
||||
foreach (array('contextid', 'itemid', 'filename', 'filepath', 'component') as $key) {
|
||||
if (!array_key_exists($key, $params)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
$contextid = clean_param($params['contextid'], PARAM_INT);
|
||||
$component = clean_param($params['component'], PARAM_COMPONENT);
|
||||
$filearea = clean_param($params['filearea'], PARAM_AREA);
|
||||
$itemid = clean_param($params['itemid'], PARAM_INT);
|
||||
$filepath = clean_param($params['filepath'], PARAM_PATH);
|
||||
$filename = clean_param($params['filename'], PARAM_FILE);
|
||||
$params = file_storage::unpack_reference($source, true);
|
||||
$fs = get_file_storage();
|
||||
return $fs->get_file($contextid, $component, $filearea, $itemid, $filepath, $filename);
|
||||
return $fs->get_file($params['contextid'], $params['component'], $params['filearea'],
|
||||
$params['itemid'], $params['filepath'], $params['filename']);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is used to copy a moodle file to draft area
|
||||
* Repository method to make sure that user can access particular file.
|
||||
*
|
||||
* @param string $encoded The metainfo of file, it is base64 encoded php serialized data
|
||||
* This is checked when user tries to pick the file from repository to deal with
|
||||
* potential parameter substitutions is request
|
||||
*
|
||||
* @param string $source
|
||||
* @return bool whether the file is accessible by current user
|
||||
*/
|
||||
public function file_is_accessible($source) {
|
||||
if ($this->has_moodle_files()) {
|
||||
try {
|
||||
$params = file_storage::unpack_reference($source, true);
|
||||
} catch (file_reference_exception $e) {
|
||||
return false;
|
||||
}
|
||||
$browser = get_file_browser();
|
||||
$context = context::instance_by_id($params['contextid']);
|
||||
$file_info = $browser->get_file_info($context, $params['component'], $params['filearea'],
|
||||
$params['itemid'], $params['filepath'], $params['filename']);
|
||||
return !empty($file_info);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is used to copy a moodle file to draft area.
|
||||
*
|
||||
* It DOES NOT check if the user is allowed to access this file because the actual file
|
||||
* can be located in the area where user does not have access to but there is an alias
|
||||
* to this file in the area where user CAN access it.
|
||||
* {@link file_is_accessible} should be called for alias location before calling this function.
|
||||
*
|
||||
* @param string $source The metainfo of file, it is base64 encoded php serialized data
|
||||
* @param stdClass|array $filerecord contains itemid, filepath, filename and optionally other
|
||||
* attributes of the new file
|
||||
* @param int $maxbytes maximum allowed size of file, -1 if unlimited. If size of file exceeds
|
||||
* the limit, the file_exception is thrown.
|
||||
* @return array The information of file
|
||||
* @return array The information about the created file
|
||||
*/
|
||||
public function copy_to_area($encoded, $filerecord, $maxbytes = -1) {
|
||||
public function copy_to_area($source, $filerecord, $maxbytes = -1) {
|
||||
global $USER;
|
||||
$fs = get_file_storage();
|
||||
$browser = get_file_browser();
|
||||
|
||||
if ($this->has_moodle_files() == false) {
|
||||
throw new coding_exception('Only repository used to browse moodle files can use repository::copy_to_area()');
|
||||
}
|
||||
|
||||
$params = unserialize(base64_decode($encoded));
|
||||
$user_context = context_user::instance($USER->id);
|
||||
|
||||
$filerecord = (array)$filerecord;
|
||||
@@ -701,17 +722,9 @@ abstract class repository {
|
||||
$new_filepath = $filerecord['filepath'];
|
||||
$new_filename = $filerecord['filename'];
|
||||
|
||||
$contextid = clean_param($params['contextid'], PARAM_INT);
|
||||
$fileitemid = clean_param($params['itemid'], PARAM_INT);
|
||||
$filename = clean_param($params['filename'], PARAM_FILE);
|
||||
$filepath = clean_param($params['filepath'], PARAM_PATH);;
|
||||
$filearea = clean_param($params['filearea'], PARAM_AREA);
|
||||
$component = clean_param($params['component'], PARAM_COMPONENT);
|
||||
|
||||
$context = get_context_instance_by_id($contextid);
|
||||
// the file needs to copied to draft area
|
||||
$file_info = $browser->get_file_info($context, $component, $filearea, $fileitemid, $filepath, $filename);
|
||||
if ($maxbytes !== -1 && $file_info->get_filesize() > $maxbytes) {
|
||||
$stored_file = self::get_moodle_file($source);
|
||||
if ($maxbytes != -1 && $stored_file->get_filesize() > $maxbytes) {
|
||||
throw new file_exception('maxbytes');
|
||||
}
|
||||
|
||||
@@ -719,7 +732,7 @@ abstract class repository {
|
||||
// create new file
|
||||
$unused_filename = repository::get_unused_filename($draftitemid, $new_filepath, $new_filename);
|
||||
$filerecord['filename'] = $unused_filename;
|
||||
$file_info->copy_to_storage($filerecord);
|
||||
$fs->create_file_from_storedfile($filerecord, $stored_file);
|
||||
$event = array();
|
||||
$event['event'] = 'fileexists';
|
||||
$event['newfile'] = new stdClass;
|
||||
@@ -729,17 +742,17 @@ abstract class repository {
|
||||
$event['existingfile'] = new stdClass;
|
||||
$event['existingfile']->filepath = $new_filepath;
|
||||
$event['existingfile']->filename = $new_filename;
|
||||
$event['existingfile']->url = moodle_url::make_draftfile_url($draftitemid, $new_filepath, $new_filename)->out();;
|
||||
$event['existingfile']->url = moodle_url::make_draftfile_url($draftitemid, $new_filepath, $new_filename)->out();
|
||||
return $event;
|
||||
} else {
|
||||
$file_info->copy_to_storage($filerecord);
|
||||
$fs->create_file_from_storedfile($filerecord, $stored_file);
|
||||
$info = array();
|
||||
$info['itemid'] = $draftitemid;
|
||||
$info['file'] = $new_filename;
|
||||
$info['title'] = $new_filename;
|
||||
$info['file'] = $new_filename;
|
||||
$info['title'] = $new_filename;
|
||||
$info['contextid'] = $user_context->id;
|
||||
$info['url'] = moodle_url::make_draftfile_url($draftitemid, $new_filepath, $new_filename)->out();;
|
||||
$info['filesize'] = $file_info->get_filesize();
|
||||
$info['url'] = moodle_url::make_draftfile_url($draftitemid, $new_filepath, $new_filename)->out();
|
||||
$info['filesize'] = $stored_file->get_filesize();
|
||||
return $info;
|
||||
}
|
||||
}
|
||||
|
||||
+12
-81
@@ -177,91 +177,22 @@ class repository_recent extends repository {
|
||||
public function supported_returntypes() {
|
||||
return FILE_INTERNAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function overwrite the default implement to copying file using file_storage
|
||||
* Repository method to make sure that user can access particular file.
|
||||
*
|
||||
* @param string $encoded The information of file, it is base64 encoded php serialized data
|
||||
* @param stdClass|array $filerecord contains itemid, filepath, filename and optionally other
|
||||
* attributes of the new file
|
||||
* @param int $maxbytes maximum allowed size of file, -1 if unlimited. If size of file exceeds
|
||||
* the limit, the file_exception is thrown.
|
||||
* @return array The information of file
|
||||
* This is checked when user tries to pick the file from repository to deal with
|
||||
* potential parameter substitutions is request
|
||||
*
|
||||
* @todo MDL-33805 remove this function when recent files are managed correctly
|
||||
*
|
||||
* @param string $source
|
||||
* @return bool whether the file is accessible by current user
|
||||
*/
|
||||
public function copy_to_area($encoded, $filerecord, $maxbytes = -1) {
|
||||
public function file_is_accessible($source) {
|
||||
global $USER;
|
||||
|
||||
$user_context = get_context_instance(CONTEXT_USER, $USER->id);
|
||||
|
||||
$filerecord = (array)$filerecord;
|
||||
// make sure the new file will be created in user draft area
|
||||
$filerecord['component'] = 'user'; // make sure
|
||||
$filerecord['filearea'] = 'draft'; // make sure
|
||||
$filerecord['contextid'] = $user_context->id;
|
||||
$filerecord['sortorder'] = 0;
|
||||
$draftitemid = $filerecord['itemid'];
|
||||
$new_filepath = $filerecord['filepath'];
|
||||
$new_filename = $filerecord['filename'];
|
||||
|
||||
$fs = get_file_storage();
|
||||
|
||||
$params = unserialize(base64_decode($encoded));
|
||||
|
||||
$contextid = clean_param($params['contextid'], PARAM_INT);
|
||||
$fileitemid = clean_param($params['itemid'], PARAM_INT);
|
||||
$filename = clean_param($params['filename'], PARAM_FILE);
|
||||
$filepath = clean_param($params['filepath'], PARAM_PATH);;
|
||||
$filearea = clean_param($params['filearea'], PARAM_AREA);
|
||||
$component = clean_param($params['component'], PARAM_COMPONENT);
|
||||
|
||||
// XXX:
|
||||
// When user try to pick a file from other filearea, normally file api will use file browse to
|
||||
// operate the files with capability check, but in some areas, users don't have permission to
|
||||
// browse the files (for example, forum_attachment area).
|
||||
//
|
||||
// To get 'recent' plugin working, we need to use lower level file_stoarge class to bypass the
|
||||
// capability check, we will use a better workaround to improve it.
|
||||
// TODO MDL-33297 apply here
|
||||
if ($stored_file = $fs->get_file($contextid, $component, $filearea, $fileitemid, $filepath, $filename)) {
|
||||
// verify user id
|
||||
if ($USER->id != $stored_file->get_userid()) {
|
||||
throw new moodle_exception('errornotyourfile', 'repository');
|
||||
}
|
||||
if ($maxbytes !== -1 && $stored_file->get_filesize() > $maxbytes) {
|
||||
throw new file_exception('maxbytes');
|
||||
}
|
||||
|
||||
// test if file already exists
|
||||
if (repository::draftfile_exists($draftitemid, $new_filepath, $new_filename)) {
|
||||
// create new file
|
||||
$unused_filename = repository::get_unused_filename($draftitemid, $new_filepath, $new_filename);
|
||||
$filerecord['filename'] = $unused_filename;
|
||||
// create a tmp file
|
||||
$fs->create_file_from_storedfile($filerecord, $stored_file);
|
||||
$event = array();
|
||||
$event['event'] = 'fileexists';
|
||||
$event['newfile'] = new stdClass;
|
||||
$event['newfile']->filepath = $new_filepath;
|
||||
$event['newfile']->filename = $unused_filename;
|
||||
$event['newfile']->url = moodle_url::make_draftfile_url($draftitemid, $new_filepath, $unused_filename)->out();
|
||||
$event['existingfile'] = new stdClass;
|
||||
$event['existingfile']->filepath = $new_filepath;
|
||||
$event['existingfile']->filename = $new_filename;
|
||||
$event['existingfile']->url = moodle_url::make_draftfile_url($draftitemid, $new_filepath, $new_filename)->out();;
|
||||
return $event;
|
||||
} else {
|
||||
$fs->create_file_from_storedfile($filerecord, $stored_file);
|
||||
$info = array();
|
||||
$info['title'] = $new_filename;
|
||||
$info['file'] = $new_filename;
|
||||
$info['itemid'] = $draftitemid;
|
||||
$info['filesize'] = $stored_file->get_filesize();
|
||||
$info['url'] = moodle_url::make_draftfile_url($draftitemid, $new_filepath, $new_filename)->out();;
|
||||
$info['contextid'] = $user_context->id;
|
||||
return $info;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
$file = self::get_moodle_file($source);
|
||||
return (!empty($file) && $file->get_userid() == $USER->id);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -70,17 +70,14 @@ if (!confirm_sesskey()) {
|
||||
}
|
||||
|
||||
// Get repository instance information
|
||||
$sql = 'SELECT i.name, i.typeid, r.type FROM {repository} r, {repository_instances} i WHERE i.id=? AND i.typeid=r.id';
|
||||
|
||||
if (!$repository = $DB->get_record_sql($sql, array($repo_id))) {
|
||||
$err->error = get_string('invalidrepositoryid', 'repository');
|
||||
die(json_encode($err));
|
||||
} else {
|
||||
$type = $repository->type;
|
||||
}
|
||||
$repooptions = array(
|
||||
'ajax' => true,
|
||||
'mimetypes' => $accepted_types
|
||||
);
|
||||
$repo = repository::get_repository_by_id($repo_id, $contextid, $repooptions);
|
||||
|
||||
// Check permissions
|
||||
repository::check_capability($contextid, $repository);
|
||||
$repo->check_capability();
|
||||
|
||||
$moodle_maxbytes = get_user_max_upload_file_size($context);
|
||||
// to prevent maxbytes greater than moodle maxbytes setting
|
||||
@@ -121,21 +118,6 @@ switch ($action) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (file_exists($CFG->dirroot.'/repository/'.$type.'/lib.php')) {
|
||||
require_once($CFG->dirroot.'/repository/'.$type.'/lib.php');
|
||||
$classname = 'repository_' . $type;
|
||||
$repooptions = array(
|
||||
'ajax' => true,
|
||||
'name' => $repository->name,
|
||||
'type' => $type,
|
||||
'mimetypes' => $accepted_types
|
||||
);
|
||||
$repo = new $classname($repo_id, $contextid, $repooptions);
|
||||
} else {
|
||||
$err->error = get_string('invalidplugin', 'repository', $type);
|
||||
die(json_encode($err));
|
||||
}
|
||||
|
||||
// These actions all occur on the currently active repository instance
|
||||
switch ($action) {
|
||||
case 'sign':
|
||||
@@ -232,7 +214,14 @@ switch ($action) {
|
||||
$record->userid = $USER->id;
|
||||
$record->sortorder = 0;
|
||||
|
||||
// Check that user has permission to access this file
|
||||
if (!$repo->file_is_accessible($source)) {
|
||||
throw new file_exception('storedfilecannotread');
|
||||
}
|
||||
|
||||
// If file is already a reference, set $source = file source, $repo = file repository
|
||||
// note that in this case user may not have permission to access the source file directly
|
||||
// so no file_browser/file_info can be used below
|
||||
if ($repo->has_moodle_files()) {
|
||||
$file = repository::get_moodle_file($source);
|
||||
if ($file && $file->is_external_file()) {
|
||||
@@ -298,13 +287,13 @@ switch ($action) {
|
||||
} else {
|
||||
// Download file to moodle.
|
||||
$downloadedfile = $repo->get_file($source, $saveas_filename);
|
||||
if ($downloadedfile['path'] === false) {
|
||||
if (empty($downloadedfile['path'])) {
|
||||
$err->error = get_string('cannotdownload', 'repository');
|
||||
die(json_encode($err));
|
||||
}
|
||||
|
||||
// Check if exceed maxbytes.
|
||||
if (($maxbytes!==-1) && (filesize($downloadedfile['path']) > $maxbytes)) {
|
||||
if ($maxbytes != -1 && filesize($downloadedfile['path']) > $maxbytes) {
|
||||
throw new file_exception('maxbytes');
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user