diff --git a/lib/filestorage/file_storage.php b/lib/filestorage/file_storage.php index 7baf7059b86..967a3298e71 100644 --- a/lib/filestorage/file_storage.php +++ b/lib/filestorage/file_storage.php @@ -263,6 +263,71 @@ class file_storage { return $newfilename; } + /** + * Return an available directory name. + * + * This will return the next available directory name in the area, adding/incrementing a suffix + * of the last portion of path, ie: /path/ > /path (1)/ > /path (2)/ > etc... + * + * If the file path passed is available without modification, it is returned as is. + * + * @param int $contextid context ID. + * @param string $component component. + * @param string $filearea file area. + * @param int $itemid area item ID. + * @param string $suggestedpath the suggested file path. + * @return string available file path + * @since 2.5 + */ + public function get_unused_dirname($contextid, $component, $filearea, $itemid, $suggestedpath) { + global $DB; + + // Ensure suggestedpath has trailing '/' + $suggestedpath = rtrim($suggestedpath, '/'). '/'; + + // The directory does not exist, we return the same file path. + if (!$this->file_exists($contextid, $component, $filearea, $itemid, $suggestedpath, '.')) { + return $suggestedpath; + } + + // Trying to locate a file path using the used pattern. We remove the used pattern from the path first. + if (preg_match('~^(/.+) \(([0-9]+)\)/$~', $suggestedpath, $matches)) { + $suggestedpath = $matches[1]. '/'; + } + + $filepathlike = $DB->sql_like_escape(rtrim($suggestedpath, '/')) . ' (%)/'; + + $filepathlikesql = $DB->sql_like('f.filepath', ':filepathlike'); + $filepathlen = $DB->sql_length('f.filepath'); + $sql = "SELECT filepath + FROM {files} f + WHERE + f.contextid = :contextid AND + f.component = :component AND + f.filearea = :filearea AND + f.itemid = :itemid AND + f.filename = :filename AND + $filepathlikesql + ORDER BY + $filepathlen DESC, + f.filepath DESC"; + $params = array('contextid' => $contextid, 'component' => $component, 'filearea' => $filearea, 'itemid' => $itemid, + 'filename' => '.', 'filepathlike' => $filepathlike); + $results = $DB->get_fieldset_sql($sql, $params, IGNORE_MULTIPLE); + + // Loop over the results to make sure we are working on a valid file path. Because '/path (1)/' and '/path (copy)/' + // would both be returned, but only the one only containing digits should be used. + $number = 1; + foreach ($results as $result) { + if (preg_match('~ \(([0-9]+)\)/$~', $result, $matches)) { + $number = (int)($matches[1]) + 1; + break; + } + } + + return rtrim($suggestedpath, '/'). ' (' . $number . ')/'; + } + /** * Generates a preview image for the stored file * diff --git a/repository/draftfiles_ajax.php b/repository/draftfiles_ajax.php index b66e4bdc23b..9a8acfa0dab 100644 --- a/repository/draftfiles_ajax.php +++ b/repository/draftfiles_ajax.php @@ -218,15 +218,7 @@ switch ($action) { $file = $fs->get_file($user_context->id, 'user', 'draft', $draftid, $filepath, $filename); // Find unused name for directory to extract the archive. - $temppath = $filepath. pathinfo($filename, PATHINFO_FILENAME). '/'; - if ($fs->file_exists($user_context->id, 'user', 'draft', $draftid, $temppath, '.')) { - for ($i=0; $i<1000; $i++) { - if (!$fs->file_exists($user_context->id, 'user', 'draft', $draftid, rtrim($temppath, '/'). " ($i)/", '.')) { - $temppath = rtrim($temppath, '/'). " ($i)/"; - break; - } - } - } + $temppath = $fs->get_unused_dirname($user_context->id, 'user', 'draft', $draftid, $filepath. pathinfo($filename, PATHINFO_FILENAME). '/'); // Extract archive and move all files from $temppath to $filepath if ($file->extract_to_storage($zipper, $user_context->id, 'user', 'draft', $draftid, $temppath, $USER->id) !== false) { $extractedfiles = $fs->get_directory_files($user_context->id, 'user', 'draft', $draftid, $temppath, true);