}
}
+/**
+ * Finds all draft areas used in a textarea and copies the files into the primary textarea. If a user copies and pastes
+ * content from another draft area it's possible for a single textarea to reference multiple draft areas.
+ *
+ * @category files
+ * @param int $draftitemid the id of the primary draft area.
+ * @param int $usercontextid the user's context id.
+ * @param string $text some html content that needs to have files copied to the correct draft area.
+ * @param bool $forcehttps force https urls.
+ *
+ * @return string $text html content modified with new draft links
+ */
+function file_merge_draft_areas($draftitemid, $usercontextid, $text, $forcehttps=false) {
+ global $CFG;
+
+ if (is_null($text)) {
+ return null;
+ }
+
+ $urls = extract_draft_file_urls_from_text($text, $forcehttps, $usercontextid, 'user', 'draft');
+
+ // No draft areas to rewrite.
+ if (empty($urls)) {
+ return $text;
+ }
+
+ foreach ($urls as $url) {
+ // Do not process the "home" draft area.
+ if ($url['itemid'] == $draftitemid) {
+ continue;
+ }
+
+ // Decode the filename.
+ $filename = urldecode($url['filename']);
+
+ // Copy the file.
+ file_copy_file_to_file_area($url, $filename, $draftitemid);
+
+ // Rewrite draft area.
+ $text = file_replace_file_area_in_text($url, $draftitemid, $text, $forcehttps);
+ }
+ return $text;
+}
+
+/**
+ * Rewrites a file area in arbitrary text.
+ *
+ * @param array $file General information about the file.
+ * @param int $newid The new file area itemid.
+ * @param string $text The text to rewrite.
+ * @param bool $forcehttps force https urls.
+ * @return string The rewritten text.
+ */
+function file_replace_file_area_in_text($file, $newid, $text, $forcehttps = false) {
+ global $CFG;
+
+ $wwwroot = $CFG->wwwroot;
+ if ($forcehttps) {
+ $wwwroot = str_replace('http://', 'https://', $wwwroot);
+ }
+
+ $search = [
+ $wwwroot,
+ $file['urlbase'],
+ $file['contextid'],
+ $file['component'],
+ $file['filearea'],
+ $file['itemid'],
+ $file['filename']
+ ];
+ $replace = [
+ $wwwroot,
+ $file['urlbase'],
+ $file['contextid'],
+ $file['component'],
+ $file['filearea'],
+ $newid,
+ $file['filename']
+ ];
+
+ $text = str_ireplace( implode('/', $search), implode('/', $replace), $text);
+ return $text;
+}
+
+/**
+ * Copies a file from one file area to another.
+ *
+ * @param array $file Information about the file to be copied.
+ * @param string $filename The filename.
+ * @param int $itemid The new file area.
+ */
+function file_copy_file_to_file_area($file, $filename, $itemid) {
+ $fs = get_file_storage();
+
+ // Load the current file in the old draft area.
+ $fileinfo = array(
+ 'component' => $file['component'],
+ 'filearea' => $file['filearea'],
+ 'itemid' => $file['itemid'],
+ 'contextid' => $file['contextid'],
+ 'filepath' => '/',
+ 'filename' => $filename
+ );
+ $oldfile = $fs->get_file($fileinfo['contextid'], $fileinfo['component'], $fileinfo['filearea'],
+ $fileinfo['itemid'], $fileinfo['filepath'], $fileinfo['filename']);
+ $newfileinfo = array(
+ 'component' => $file['component'],
+ 'filearea' => $file['filearea'],
+ 'itemid' => $itemid,
+ 'contextid' => $file['contextid'],
+ 'filepath' => '/',
+ 'filename' => $filename
+ );
+
+ // Check if the file exists.
+ if ( ! $fs->file_exists($newfileinfo['contextid'], $newfileinfo['component'], $newfileinfo['filearea'],
+ $newfileinfo['itemid'], $newfileinfo['filepath'], $newfileinfo['filename']) ) {
+ $newfile = $fs->create_file_from_storedfile($newfileinfo, $oldfile);
+ }
+}
+
/**
* Saves files from a draft file area to a real one (merging the list of files).
* Can rewrite URLs in some content at the same time if desired.
$allowreferences = false;
}
+ // Check if the user has copy-pasted from other draft areas. Those files will be located in different draft
+ // areas and need to be copied into the current draft area.
+ $text = file_merge_draft_areas($draftitemid, $usercontext->id, $text, $forcehttps);
+
// Check if the draft area has exceeded the authorised limit. This should never happen as validation
// should have taken place before, unless the user is doing something nauthly. If so, let's just not save
// anything at all in the next area.
$this->assertEquals($fourthrecord['filename'], $allfiles[3]->filename);
$this->assertEquals($fifthrecord['filename'], $allfiles[4]->filename);
}
+
+ public function test_file_copy_file_to_file_area() {
+ // Create two files in different draft areas but owned by the same user.
+ global $USER;
+ $this->resetAfterTest(true);
+ $this->setAdminUser();
+
+ $filerecord = ['filename' => 'file1.png', 'itemid' => file_get_unused_draft_itemid()];
+ $file1 = self::create_draft_file($filerecord);
+ $filerecord = ['filename' => 'file2.png', 'itemid' => file_get_unused_draft_itemid()];
+ $file2 = self::create_draft_file($filerecord);
+
+ // Confirm one file in each draft area.
+ $fs = get_file_storage();
+ $usercontext = context_user::instance($USER->id);
+ $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $file1->get_itemid(), 'itemid', 0);
+ $this->assertCount(1, $draftfiles);
+ $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $file2->get_itemid(), 'itemid', 0);
+ $this->assertCount(1, $draftfiles);
+
+ // Create file record.
+ $filerecord = [
+ 'component' => $file2->get_component(),
+ 'filearea' => $file2->get_filearea(),
+ 'itemid' => $file2->get_itemid(),
+ 'contextid' => $file2->get_contextid(),
+ 'filepath' => '/',
+ 'filename' => $file2->get_filename()
+ ];
+
+ // Copy file2 into file1's draft area.
+ $newfile = file_copy_file_to_file_area($filerecord, $file2->get_filename(), $file1->get_itemid());
+ $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $file1->get_itemid(), 'itemid', 0);
+ $this->assertCount(2, $draftfiles);
+ $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $file2->get_itemid(), 'itemid', 0);
+ $this->assertCount(1, $draftfiles);
+ }
}
/**
$_GET = $currentget;
}
}
+
+ /**
+ * Tests for extract_draft_file_urls_from_text() function.
+ */
+ public function test_extract_draft_file_urls_from_text() {
+ global $CFG;
+
+ $url1 = "{$CFG->wwwroot}/draftfile.php/5/user/draft/99999999/test1.jpg";
+ $url2 = "{$CFG->wwwroot}/draftfile.php/5/user/draft/99999998/test2.jpg";
+
+ $html = "<p>This is a test.</p><p><img src=\"${url1}\" alt=\"\" role=\"presentation\"></p>
+ <br>Test content.<p></p><p><img src=\"{$url2}\" alt=\"\" width=\"2048\" height=\"1536\"
+ role=\"presentation\" class=\"img-responsive atto_image_button_text-bottom\"><br></p>";
+ $draftareas = array(
+ array(
+ 'urlbase' => 'draftfile.php',
+ 'contextid' => '5',
+ 'component' => 'user',
+ 'filearea' => 'draft',
+ 'itemid' => '99999999',
+ 'filename' => 'test1.jpg',
+ 0 => "{$CFG->wwwroot}/draftfile.php/5/user/draft/99999999/test1.jpg",
+ 1 => 'draftfile.php',
+ 2 => '5',
+ 3 => 'user',
+ 4 => 'draft',
+ 5 => '99999999',
+ 6 => 'test1.jpg'
+ ),
+ array(
+ 'urlbase' => 'draftfile.php',
+ 'contextid' => '5',
+ 'component' => 'user',
+ 'filearea' => 'draft',
+ 'itemid' => '99999998',
+ 'filename' => 'test2.jpg',
+ 0 => "{$CFG->wwwroot}/draftfile.php/5/user/draft/99999998/test2.jpg",
+ 1 => 'draftfile.php',
+ 2 => '5',
+ 3 => 'user',
+ 4 => 'draft',
+ 5 => '99999998',
+ 6 => 'test2.jpg'
+ )
+ );
+ $extracteddraftareas = extract_draft_file_urls_from_text($html, 5, 'user', 'draft');
+ $this->assertEquals($draftareas, $extracteddraftareas);
+ }
}
hard-coded. Instead, the setting $CFG->showuseridentity should be always respected, which has always been the default
behaviour (MDL-59847).
+* New functions to support the merging of user draft areas from the interface; see MDL-45170 for details:
+
+- file_copy_file_to_file_area()
+- file_merge_draft_areas()
+- file_replace_file_area_in_text()
+- extract_draft_file_urls_from_text()
+
=== 3.5 ===
* There is a new privacy API that every subsystem and plugin has to implement so that the site can become GDPR
return trim($content, "\r\n ");
}
+/**
+ * Factory method for extracting draft file links from arbitrary text using regular expressions. Only text
+ * is required; other file fields may be passed to filter.
+ *
+ * @param string $text Some html content.
+ * @param bool $forcehttps force https urls.
+ * @param int $contextid This parameter and the next three identify the file area to save to.
+ * @param string $component The component name.
+ * @param string $filearea The filearea.
+ * @param int $itemid The item id for the filearea.
+ * @param string $filename The specific filename of the file.
+ * @return array
+ */
+function extract_draft_file_urls_from_text($text, $forcehttps = false, $contextid = null, $component = null,
+ $filearea = null, $itemid = null, $filename = null) {
+ global $CFG;
+
+ $wwwroot = $CFG->wwwroot;
+ if ($forcehttps) {
+ $wwwroot = str_replace('http://', 'https://', $wwwroot);
+ }
+ $urlstring = '/' . preg_quote($wwwroot, '/');
+
+ $urlbase = preg_quote('draftfile.php');
+ $urlstring .= "\/(?<urlbase>{$urlbase})";
+
+ if (is_null($contextid)) {
+ $contextid = '[0-9]+';
+ }
+ $urlstring .= "\/(?<contextid>{$contextid})";
+
+ if (is_null($component)) {
+ $component = '[a-z_]+';
+ }
+ $urlstring .= "\/(?<component>{$component})";
+
+ if (is_null($filearea)) {
+ $filearea = '[a-z_]+';
+ }
+ $urlstring .= "\/(?<filearea>{$filearea})";
+
+ if (is_null($itemid)) {
+ $itemid = '[0-9]+';
+ }
+ $urlstring .= "\/(?<itemid>{$itemid})";
+
+ // Filename matching magic based on file_rewrite_urls_to_pluginfile().
+ if (is_null($filename)) {
+ $filename = '[^\'\",&<>|`\s:\\\\]+';
+ }
+ $urlstring .= "\/(?<filename>{$filename})/";
+
+ // Regular expression which matches URLs and returns their components.
+ preg_match_all($urlstring, $text, $urls, PREG_SET_ORDER);
+ return $urls;
+}
+
/**
* This function will highlight search words in a given string
*