From 8b6a5b424903ded84b3aeb2e9baa222006d1e70c Mon Sep 17 00:00:00 2001 From: DamsFX Date: Fri, 8 Mar 2024 19:51:42 +0100 Subject: [PATCH 1/8] Add cloning multiple files or folders with auto generated names --- modules/backend/lang/en/lang.php | 3 + modules/backend/lang/fr/lang.php | 3 + modules/backend/widgets/MediaManager.php | 134 ++++++++++++++++++ .../assets/js/mediamanager-browser-min.js | 18 ++- .../mediamanager/assets/js/mediamanager.js | 63 +++++++- .../widgets/mediamanager/partials/_body.php | 2 + .../mediamanager/partials/_toolbar.php | 1 + modules/system/classes/MediaLibrary.php | 64 +++++++++ 8 files changed, 280 insertions(+), 8 deletions(-) diff --git a/modules/backend/lang/en/lang.php b/modules/backend/lang/en/lang.php index 6479035d50..e71c9a184d 100644 --- a/modules/backend/lang/en/lang.php +++ b/modules/backend/lang/en/lang.php @@ -599,6 +599,7 @@ 'menu_label' => 'Media', 'upload' => 'Upload', 'move' => 'Move', + 'clone' => 'Clone', 'delete' => 'Delete', 'add_folder' => 'Add folder', 'search' => 'Search', @@ -629,6 +630,8 @@ 'direction_desc' => 'Descending', 'folder' => 'Folder', 'no_files_found' => 'No files found by your request.', + 'clone_empty' => 'Please select items to clone.', + 'clone_multiple_confirm' => 'Multiple items selected, they will be cloned with generated names. Are you sure?', 'delete_empty' => 'Please select items to delete.', 'delete_confirm' => 'Delete the selected item(s)?', 'error_renaming_file' => 'Error renaming the item.', diff --git a/modules/backend/lang/fr/lang.php b/modules/backend/lang/fr/lang.php index 4f82b53265..91e0db3cb8 100644 --- a/modules/backend/lang/fr/lang.php +++ b/modules/backend/lang/fr/lang.php @@ -596,6 +596,7 @@ 'menu_label' => 'Média', 'upload' => 'Déposer un fichier', 'move' => 'Déplacer', + 'clone' => 'Cloner', 'delete' => 'Supprimer', 'add_folder' => 'Ajouter un répertoire', 'search' => 'Rechercher', @@ -626,6 +627,8 @@ 'direction_desc' => 'Descendant', 'folder' => 'Répertoire', 'no_files_found' => 'Aucun fichier trouvé.', + 'clone_empty' => 'Veuillez sélectionner les éléments à cloner.', + 'clone_multiple_confirm' => 'Plusieurs éléments sélectionnés, ils seront clonés avec des noms générés. Êtes-vous sûr ?', 'delete_empty' => 'Veuillez sélectionner les éléments à supprimer.', 'delete_confirm' => 'Confirmer la suppression de ces éléments ?', 'error_renaming_file' => 'Erreur lors du renommage de l\'élément.', diff --git a/modules/backend/widgets/MediaManager.php b/modules/backend/widgets/MediaManager.php index 067d53a50d..1f05f59559 100644 --- a/modules/backend/widgets/MediaManager.php +++ b/modules/backend/widgets/MediaManager.php @@ -520,6 +520,140 @@ public function onCreateFolder(): array ]; } + /** + * Render the clone popup + * + * @throws ApplicationException If the exclude input data is not an array + */ + public function onLoadClonePopup(): string + { + $this->abortIfReadOnly(); + + // $exclude = Input::get('exclude', []); + // if (!is_array($exclude)) { + // throw new ApplicationException('Invalid input data'); + // } + + // $folders = MediaLibrary::instance()->listAllDirectories($exclude); + + // $folderList = []; + // foreach ($folders as $folder) { + // $path = $folder; + + // if ($folder == '/') { + // $name = Lang::get('backend::lang.media.library'); + // } else { + // $segments = explode('/', $folder); + // $name = str_repeat(' ', (count($segments) - 1) * 4) . basename($folder); + // } + + // $folderList[$path] = $name; + // } + + // $this->vars['folders'] = $folderList; + $this->vars['originalPath'] = Input::get('path'); + + return $this->makePartial('move-form'); + } + + /** + * Clone the selected items ("files", "folders") without prompting the user + * + * @throws ApplicationException if the input data is invalid + */ + public function onCloneItems(): array + { + $this->abortIfReadOnly(); + + $paths = Input::get('paths'); + + if (!is_array($paths)) { + throw new ApplicationException('Invalid input data'); + } + + $library = MediaLibrary::instance(); + + $itemsToClone = []; + foreach ($paths as $pathInfo) { + $path = array_get($pathInfo, 'path'); + $type = array_get($pathInfo, 'type'); + + if (!$path || !$type) { + throw new ApplicationException('Invalid input data'); + } + + if ($type === MediaLibraryItem::TYPE_FILE) { + /* + * Add to bulk collection + */ + $itemsToClone[] = $path; + } elseif ($type === MediaLibraryItem::TYPE_FOLDER) { + /* + * Clone single folder + */ + $library->cloneFolder($path); + + /** + * @event media.folder.clone + * Called after a folder is cloned + * + * Example usage: + * + * Event::listen('media.folder.clone', function ((\Backend\Widgets\MediaManager) $mediaWidget, (string) $path) { + * \Log::info($path . " was cloned"); + * }); + * + * Or + * + * $mediaWidget->bindEvent('folder.clone', function ((string) $path) { + * \Log::info($path . " was cloned"); + * }); + * + */ + $this->fireSystemEvent('media.folder.delete', [$path]); + } + } + + if (count($itemsToClone) > 0) { + /* + * Clone collection of files + */ + $library->cloneFiles($itemsToClone); + + /* + * Extensibility + */ + foreach ($itemsToClone as $path) { + /** + * @event media.file.clone + * Called after a file is cloned + * + * Example usage: + * + * Event::listen('media.file.clone', function ((\Backend\Widgets\MediaManager) $mediaWidget, (string) $path) { + * \Log::info($path . " was cloned"); + * }); + * + * Or + * + * $mediaWidget->bindEvent('file.clone', function ((string) $path) { + * \Log::info($path . " was cloned"); + * }); + * + */ + $this->fireSystemEvent('media.file.clone', [$path]); + } + } + + $library->resetCache(); + $this->prepareVars(); + + return [ + '#' . $this->getId('item-list') => $this->makePartial('item-list') + ]; + } + + /** * Render the move popup with a list of folders to move the selected items to excluding the provided paths in the request ("exclude") * diff --git a/modules/backend/widgets/mediamanager/assets/js/mediamanager-browser-min.js b/modules/backend/widgets/mediamanager/assets/js/mediamanager-browser-min.js index 78f74226c7..3fbb3721f1 100644 --- a/modules/backend/widgets/mediamanager/assets/js/mediamanager-browser-min.js +++ b/modules/backend/widgets/mediamanager/assets/js/mediamanager-browser-min.js @@ -100,8 +100,9 @@ MediaManager.prototype.selectItem=function(node,expandSelection){if(!expandSelec for(var i=0,len=items.length;i1){$.wn.confirm(this.options.cloneMultipleConfirm,this.proxy(this.cloneMultipleConfirmation))}else{$(ev.target).popup({handler:this.options.alias+'::onLoadClonePopup',zIndex:1200})}} +MediaManager.prototype.cloneMultipleConfirmation=function(confirmed){if(!confirmed)return +var items=this.$el.get(0).querySelectorAll('[data-type="media-item"].selected'),paths=[] +for(var i=0,len=items.length;i 1) { + $.wn.confirm(this.options.cloneMultipleConfirm, this.proxy(this.cloneMultipleConfirmation)) + } else { + $(ev.target).popup({ + handler: this.options.alias+'::onLoadClonePopup', + // extraData: data, + zIndex: 1200 // Media Manager can be opened in a popup, so this new popup should have a higher z-index + }) + } + } + + MediaManager.prototype.cloneMultipleConfirmation = function (confirmed) { + if (!confirmed) + return + + var items = this.$el.get(0).querySelectorAll('[data-type="media-item"].selected'), + paths = [] + + for (var i = 0, len = items.length; i < len; i++) { + // Skip the 'return to parent' item + if (items[i].hasAttribute('data-root')) { + continue; + } + paths.push({ + 'path': items[i].getAttribute('data-path'), + 'type': items[i].getAttribute('data-item-type') + }) + } + + var data = { + paths: paths + } + + $.wn.stripeLoadIndicator.show() + this.$form.request(this.options.alias + '::onCloneItems', { + data: data + }).always(function () { + $.wn.stripeLoadIndicator.hide() + }).done(this.proxy(this.afterNavigate)) + } + MediaManager.prototype.moveItems = function(ev) { var items = this.$el.get(0).querySelectorAll('[data-type="media-item"].selected') @@ -1108,6 +1158,9 @@ case 'create-folder': this.createFolder(ev) break; + case 'clone': + this.cloneItems(ev) + break; case 'move': this.moveItems(ev) break; @@ -1303,6 +1356,8 @@ url: window.location, uploadHandler: null, alias: '', + cloneEmpty: 'Please select an item to clone.', + cloneMultipleConfirm: 'Multiple items selected, they will be cloned with generated names. Are you sure?', deleteEmpty: 'Please select files to delete.', deleteConfirm: 'Delete the selected file(s)?', moveEmpty: 'Please select files to move.', diff --git a/modules/backend/widgets/mediamanager/partials/_body.php b/modules/backend/widgets/mediamanager/partials/_body.php index 3dd1b17e0a..c91f0304dc 100644 --- a/modules/backend/widgets/mediamanager/partials/_body.php +++ b/modules/backend/widgets/mediamanager/partials/_body.php @@ -3,6 +3,8 @@ class="layout" data-alias="alias ?>" data-upload-handler="getEventHandler('onUpload') ?>" + data-clone-empty="" + data-clone-multiple-confirm="" data-delete-empty="" data-delete-confirm="" data-move-empty="" diff --git a/modules/backend/widgets/mediamanager/partials/_toolbar.php b/modules/backend/widgets/mediamanager/partials/_toolbar.php index 537580977c..92524c5191 100644 --- a/modules/backend/widgets/mediamanager/partials/_toolbar.php +++ b/modules/backend/widgets/mediamanager/partials/_toolbar.php @@ -14,6 +14,7 @@ readOnly): ?>
+
diff --git a/modules/system/classes/MediaLibrary.php b/modules/system/classes/MediaLibrary.php index 0a28ea0230..71568f59a9 100644 --- a/modules/system/classes/MediaLibrary.php +++ b/modules/system/classes/MediaLibrary.php @@ -206,6 +206,70 @@ public function findFiles($searchTerm, $sortBy = 'title', $filter = null) return $result; } + /** + * Clones a file from the Library. + * @param array $paths A list of file paths relative to the Library root to clone. + */ + public function cloneFiles($paths) + { + $disk = $this->getStorageDisk(); + + $getDuplicateFileName = function ($path) use ($disk) { + $pathInfos = pathinfo($path); + $sameFiles = array_filter( + $disk->files($pathInfos['dirname']), + function ($file) use ($pathInfos) { + return preg_match('/'. $pathInfos['filename'] .'-(\d*)\.'. $pathInfos['extension'] .'$/U', $file); + } + ); + $newIndex = count($sameFiles) + 1; + + return $pathInfos['dirname'] .'/'. $pathInfos['filename'] .'-' . $newIndex . '.' . $pathInfos['extension']; + }; + + $duplicateFiles = function ($paths) use ($disk, $getDuplicateFileName) { + foreach ($paths as $path) { + $path = self::validatePath($path); + $fullSrcPath = $this->getMediaPath($path); + $fullDestPath = $getDuplicateFileName($fullSrcPath); + + if (!$disk->copy($fullSrcPath, $fullDestPath)) { + return false; + } + } + + return true; + }; + + return $duplicateFiles($paths); + } + + /** + * Clones a folder from the Library. + * @param array $path Specifies the folder path relative to the Library root. + */ + public function cloneFolder($path) + { + $originalPath = self::validatePath($path); + $disk = $this->getStorageDisk(); + + $getDuplicateFolderName = function ($path) use ($disk) { + $sameFolders = array_filter( + $disk->directories(dirname($this->getMediaPath($path))), + function ($folder) use ($path) { + return preg_match('/'. basename($path) .'-(\d*)$/U', $folder); + } + ); + $newIndex = count($sameFolders) + 1; + + return dirname($path) .'/'. basename($path) .'-' . $newIndex; + }; + + $newPath = $getDuplicateFolderName($originalPath); + + return $this->copyFolder($originalPath, $newPath); + } + /** * Deletes a file from the Library. * @param array $paths A list of file paths relative to the Library root to delete. From 5af2c6e8fd0a3c03d72826992dd64b40863f6a55 Mon Sep 17 00:00:00 2001 From: DamsFX Date: Sat, 9 Mar 2024 06:30:40 +0100 Subject: [PATCH 2/8] Add cloning file or folder with user input --- modules/backend/lang/en/lang.php | 3 + modules/backend/lang/fr/lang.php | 3 + modules/backend/widgets/MediaManager.php | 142 +++++++++++---- .../assets/js/mediamanager-browser-min.js | 18 +- .../mediamanager/assets/js/mediamanager.js | 45 ++++- .../mediamanager/partials/_clone-form.php | 34 ++++ modules/system/classes/MediaLibrary.php | 167 +++++++++++------- 7 files changed, 315 insertions(+), 97 deletions(-) create mode 100644 modules/backend/widgets/mediamanager/partials/_clone-form.php diff --git a/modules/backend/lang/en/lang.php b/modules/backend/lang/en/lang.php index e71c9a184d..7bd94f0069 100644 --- a/modules/backend/lang/en/lang.php +++ b/modules/backend/lang/en/lang.php @@ -632,6 +632,9 @@ 'no_files_found' => 'No files found by your request.', 'clone_empty' => 'Please select items to clone.', 'clone_multiple_confirm' => 'Multiple items selected, they will be cloned with generated names. Are you sure?', + 'clone_popup_title' => 'Clone file or folder', + 'clone_new_name' => 'New name (suggested)', + 'clone_button' => 'Clone', 'delete_empty' => 'Please select items to delete.', 'delete_confirm' => 'Delete the selected item(s)?', 'error_renaming_file' => 'Error renaming the item.', diff --git a/modules/backend/lang/fr/lang.php b/modules/backend/lang/fr/lang.php index 91e0db3cb8..6ba7b2a17f 100644 --- a/modules/backend/lang/fr/lang.php +++ b/modules/backend/lang/fr/lang.php @@ -629,6 +629,9 @@ 'no_files_found' => 'Aucun fichier trouvé.', 'clone_empty' => 'Veuillez sélectionner les éléments à cloner.', 'clone_multiple_confirm' => 'Plusieurs éléments sélectionnés, ils seront clonés avec des noms générés. Êtes-vous sûr ?', + 'clone_popup_title' => 'Cloner un fichier ou dossier', + 'clone_new_name' => 'Nouveau nom (suggestion)', + 'clone_button' => 'Cloner', 'delete_empty' => 'Veuillez sélectionner les éléments à supprimer.', 'delete_confirm' => 'Confirmer la suppression de ces éléments ?', 'error_renaming_file' => 'Erreur lors du renommage de l\'élément.', diff --git a/modules/backend/widgets/MediaManager.php b/modules/backend/widgets/MediaManager.php index 1f05f59559..a9f332f7c3 100644 --- a/modules/backend/widgets/MediaManager.php +++ b/modules/backend/widgets/MediaManager.php @@ -521,43 +521,126 @@ public function onCreateFolder(): array } /** - * Render the clone popup - * - * @throws ApplicationException If the exclude input data is not an array + * Render the clone popup for the provided "path" from the request */ public function onLoadClonePopup(): string { $this->abortIfReadOnly(); - // $exclude = Input::get('exclude', []); - // if (!is_array($exclude)) { - // throw new ApplicationException('Invalid input data'); - // } + $path = Input::get('path'); + $type = Input::get('type'); + $path = MediaLibrary::validatePath($path); + $suggestedName = ''; - // $folders = MediaLibrary::instance()->listAllDirectories($exclude); + $library = MediaLibrary::instance(); - // $folderList = []; - // foreach ($folders as $folder) { - // $path = $folder; + if ($type == MediaLibraryItem::TYPE_FILE) { + $suggestedName = $library->generateIncrementedFileName($path); + } else { + $suggestedName = $library->generateIncrementedFolderName($path); + } - // if ($folder == '/') { - // $name = Lang::get('backend::lang.media.library'); - // } else { - // $segments = explode('/', $folder); - // $name = str_repeat(' ', (count($segments) - 1) * 4) . basename($folder); - // } + $this->vars['originalPath'] = $path; + $this->vars['newName'] = $suggestedName; + $this->vars['type'] = $type; - // $folderList[$path] = $name; - // } + return $this->makePartial('clone-form'); + } - // $this->vars['folders'] = $folderList; - $this->vars['originalPath'] = Input::get('path'); + /** + * Clone the provided path from the request ("originalPath") to the new name ("name") + * + * @throws ApplicationException if the new name is invalid + */ + public function onCloneItem(): array + { + $this->abortIfReadOnly(); - return $this->makePartial('move-form'); + $newName = Input::get('newName'); + if (!strlen($newName)) { + throw new ApplicationException(Lang::get('cms::lang.asset.name_cant_be_empty')); + } + + if (!$this->validateFileName($newName)) { + throw new ApplicationException(Lang::get('cms::lang.asset.invalid_name')); + } + + + $originalPath = Input::get('originalPath'); + $originalPath = MediaLibrary::validatePath($originalPath); + $newPath = dirname($originalPath) . '/' . $newName; + $type = Input::get('type'); + + $library = MediaLibrary::instance(); + + if ($type == MediaLibraryItem::TYPE_FILE) { + /* + * Validate extension + */ + if (!$this->validateFileType($newName)) { + throw new ApplicationException(Lang::get('backend::lang.media.type_blocked')); + } + + /* + * Clone single file + */ + $library->copyFile($originalPath, $newPath); + + /** + * @event media.file.clone + * Called after a file is cloned + * + * Example usage: + * + * Event::listen('media.file.clone', function ((\Backend\Widgets\MediaManager) $mediaWidget, (string) $originalPath, (string) $newPath) { + * \Log::info($originalPath . " was cloned to " . $path); + * }); + * + * Or + * + * $mediaWidget->bindEvent('file.clone', function ((string) $originalPath, (string) $newPath) { + * \Log::info($originalPath . " was cloned to " . $path); + * }); + * + */ + $this->fireSystemEvent('media.file.clone', [$originalPath, $newPath]); + } else { + /* + * Clone single folder + */ + $library->copyFolder($originalPath, $newPath); + + /** + * @event media.folder.clone + * Called after a folder is cloned + * + * Example usage: + * + * Event::listen('media.folder.clone', function ((\Backend\Widgets\MediaManager) $mediaWidget, (string) $originalPath, (string) $newPath) { + * \Log::info($originalPath . " was cloned to " . $path); + * }); + * + * Or + * + * $mediaWidget->bindEvent('folder.clone', function ((string) $originalPath, (string) $newPath) { + * \Log::info($originalPath . " was cloned to " . $path); + * }); + * + */ + $this->fireSystemEvent('media.folder.clone', [$originalPath, $newPath]); + } + + $library->resetCache(); + $this->prepareVars(); + + return [ + '#' . $this->getId('item-list') => $this->makePartial('item-list') + ]; } /** - * Clone the selected items ("files", "folders") without prompting the user + * Clone the selected files or folders without prompting the user + * The new name will be generated in an incremented sequence * * @throws ApplicationException if the input data is invalid */ @@ -573,7 +656,7 @@ public function onCloneItems(): array $library = MediaLibrary::instance(); - $itemsToClone = []; + $filesToClone = []; foreach ($paths as $pathInfo) { $path = array_get($pathInfo, 'path'); $type = array_get($pathInfo, 'type'); @@ -586,7 +669,7 @@ public function onCloneItems(): array /* * Add to bulk collection */ - $itemsToClone[] = $path; + $filesToClone[] = $path; } elseif ($type === MediaLibraryItem::TYPE_FOLDER) { /* * Clone single folder @@ -610,20 +693,20 @@ public function onCloneItems(): array * }); * */ - $this->fireSystemEvent('media.folder.delete', [$path]); + $this->fireSystemEvent('media.folder.clone', [$path]); } } - if (count($itemsToClone) > 0) { + if (count($filesToClone) > 0) { /* * Clone collection of files */ - $library->cloneFiles($itemsToClone); + $library->cloneFiles($filesToClone); /* * Extensibility */ - foreach ($itemsToClone as $path) { + foreach ($filesToClone as $path) { /** * @event media.file.clone * Called after a file is cloned @@ -653,7 +736,6 @@ public function onCloneItems(): array ]; } - /** * Render the move popup with a list of folders to move the selected items to excluding the provided paths in the request ("exclude") * diff --git a/modules/backend/widgets/mediamanager/assets/js/mediamanager-browser-min.js b/modules/backend/widgets/mediamanager/assets/js/mediamanager-browser-min.js index 3fbb3721f1..bb79a3b8dc 100644 --- a/modules/backend/widgets/mediamanager/assets/js/mediamanager-browser-min.js +++ b/modules/backend/widgets/mediamanager/assets/js/mediamanager-browser-min.js @@ -64,6 +64,8 @@ this.$el.on('input','[data-control="search"]',this.proxy(this.onSearchChanged)) this.$el.on('mediarefresh',this.proxy(this.refresh)) this.$el.on('shown.oc.popup','[data-command="create-folder"]',this.proxy(this.onFolderPopupShown)) this.$el.on('hidden.oc.popup','[data-command="create-folder"]',this.proxy(this.onFolderPopupHidden)) +this.$el.on('shown.oc.popup','[data-command="clone"]',this.proxy(this.onClonePopupShown)) +this.$el.on('hidden.oc.popup','[data-command="clone"]',this.proxy(this.onClonePopupHidden)) this.$el.on('shown.oc.popup','[data-command="move"]',this.proxy(this.onMovePopupShown)) this.$el.on('hidden.oc.popup','[data-command="move"]',this.proxy(this.onMovePopupHidden)) this.$el.on('keydown',this.proxy(this.onKeyDown)) @@ -77,6 +79,8 @@ this.$el.off('change','[data-control="sorting"]',this.proxy(this.onSortingChange this.$el.off('keyup','[data-control="search"]',this.proxy(this.onSearchChanged)) this.$el.off('shown.oc.popup','[data-command="create-folder"]',this.proxy(this.onFolderPopupShown)) this.$el.off('hidden.oc.popup','[data-command="create-folder"]',this.proxy(this.onFolderPopupHidden)) +this.$el.off('shown.oc.popup','[data-command="clone"]',this.proxy(this.onClonePopupShown)) +this.$el.off('hidden.oc.popup','[data-command="clone"]',this.proxy(this.onClonePopupHidden)) this.$el.off('shown.oc.popup','[data-command="move"]',this.proxy(this.onMovePopupShown)) this.$el.off('hidden.oc.popup','[data-command="move"]',this.proxy(this.onMovePopupHidden)) this.$el.off('keydown',this.proxy(this.onKeyDown)) @@ -286,12 +290,22 @@ MediaManager.prototype.folderCreated=function(){this.$el.find('button[data-comma this.afterNavigate()} MediaManager.prototype.cloneItems=function(ev){var items=this.$el.get(0).querySelectorAll('[data-type="media-item"].selected') if(!items.length){$.wn.alert(this.options.cloneEmpty) -return}if(items.length>1){$.wn.confirm(this.options.cloneMultipleConfirm,this.proxy(this.cloneMultipleConfirmation))}else{$(ev.target).popup({handler:this.options.alias+'::onLoadClonePopup',zIndex:1200})}} +return}if(items.length>1){$.wn.confirm(this.options.cloneMultipleConfirm,this.proxy(this.cloneMultipleConfirmation))}else{var data={path:items[0].getAttribute('data-path'),type:items[0].getAttribute('data-item-type')} +$(ev.target).popup({handler:this.options.alias+'::onLoadClonePopup',extraData:data,zIndex:1200})}} MediaManager.prototype.cloneMultipleConfirmation=function(confirmed){if(!confirmed)return var items=this.$el.get(0).querySelectorAll('[data-type="media-item"].selected'),paths=[] for(var i=0,len=items.length;i 1) { $.wn.confirm(this.options.cloneMultipleConfirm, this.proxy(this.cloneMultipleConfirmation)) } else { + var data = { + // path: this.$el.find('[data-type="current-folder"]').val() + path: items[0].getAttribute('data-path'), + type: items[0].getAttribute('data-item-type') + } + $(ev.target).popup({ handler: this.options.alias+'::onLoadClonePopup', - // extraData: data, + extraData: data, zIndex: 1200 // Media Manager can be opened in a popup, so this new popup should have a higher z-index }) } @@ -1048,6 +1058,39 @@ }).done(this.proxy(this.afterNavigate)) } + MediaManager.prototype.onClonePopupShown = function (ev, button, popup) { + $(popup).on('submit.media', 'form', this.proxy(this.onCloneItemSubmit)) + } + + MediaManager.prototype.onCloneItemSubmit = function (ev) { + var item = this.$el.get(0).querySelector('[data-type="media-item"].selected'), + data = { + newName: $(ev.target).find('input[name=newName]').val(), + originalPath: $(ev.target).find('input[name=originalPath]').val(), + type: $(ev.target).find('input[name=type]').val() + } + + $.wn.stripeLoadIndicator.show() + this.$form.request(this.options.alias + '::onCloneItem', { + data: data + }).always(function () { + $.wn.stripeLoadIndicator.hide() + }).done(this.proxy(this.itemCloned)) + + ev.preventDefault() + return false + } + + MediaManager.prototype.onClonePopupHidden = function (ev, button, popup) { + $(popup).off('.media', 'form') + } + + MediaManager.prototype.itemCloned = function () { + this.$el.find('button[data-command="clone"]').popup('hide') + + this.afterNavigate() + } + MediaManager.prototype.moveItems = function(ev) { var items = this.$el.get(0).querySelectorAll('[data-type="media-item"].selected') diff --git a/modules/backend/widgets/mediamanager/partials/_clone-form.php b/modules/backend/widgets/mediamanager/partials/_clone-form.php new file mode 100644 index 0000000000..f9da195c62 --- /dev/null +++ b/modules/backend/widgets/mediamanager/partials/_clone-form.php @@ -0,0 +1,34 @@ + + + + + diff --git a/modules/system/classes/MediaLibrary.php b/modules/system/classes/MediaLibrary.php index 71568f59a9..b7402ec8b5 100644 --- a/modules/system/classes/MediaLibrary.php +++ b/modules/system/classes/MediaLibrary.php @@ -206,70 +206,6 @@ public function findFiles($searchTerm, $sortBy = 'title', $filter = null) return $result; } - /** - * Clones a file from the Library. - * @param array $paths A list of file paths relative to the Library root to clone. - */ - public function cloneFiles($paths) - { - $disk = $this->getStorageDisk(); - - $getDuplicateFileName = function ($path) use ($disk) { - $pathInfos = pathinfo($path); - $sameFiles = array_filter( - $disk->files($pathInfos['dirname']), - function ($file) use ($pathInfos) { - return preg_match('/'. $pathInfos['filename'] .'-(\d*)\.'. $pathInfos['extension'] .'$/U', $file); - } - ); - $newIndex = count($sameFiles) + 1; - - return $pathInfos['dirname'] .'/'. $pathInfos['filename'] .'-' . $newIndex . '.' . $pathInfos['extension']; - }; - - $duplicateFiles = function ($paths) use ($disk, $getDuplicateFileName) { - foreach ($paths as $path) { - $path = self::validatePath($path); - $fullSrcPath = $this->getMediaPath($path); - $fullDestPath = $getDuplicateFileName($fullSrcPath); - - if (!$disk->copy($fullSrcPath, $fullDestPath)) { - return false; - } - } - - return true; - }; - - return $duplicateFiles($paths); - } - - /** - * Clones a folder from the Library. - * @param array $path Specifies the folder path relative to the Library root. - */ - public function cloneFolder($path) - { - $originalPath = self::validatePath($path); - $disk = $this->getStorageDisk(); - - $getDuplicateFolderName = function ($path) use ($disk) { - $sameFolders = array_filter( - $disk->directories(dirname($this->getMediaPath($path))), - function ($folder) use ($path) { - return preg_match('/'. basename($path) .'-(\d*)$/U', $folder); - } - ); - $newIndex = count($sameFolders) + 1; - - return dirname($path) .'/'. basename($path) .'-' . $newIndex; - }; - - $newPath = $getDuplicateFolderName($originalPath); - - return $this->copyFolder($originalPath, $newPath); - } - /** * Deletes a file from the Library. * @param array $paths A list of file paths relative to the Library root to delete. @@ -398,6 +334,68 @@ public function put($path, $contents) return $this->getStorageDisk()->put($fullPath, $contents); } + /** + * Clones files from the Library. + * + * @param array $paths A list of file paths relative to the Library root to clone. + */ + public function cloneFiles($paths) + { + $duplicateFiles = function ($paths) { + foreach ($paths as $path) { + $path = self::validatePath($path); + // $fullSrcPath = $this->getMediaPath($path); + $destPath = dirname($path) .'/'. $this->generateIncrementedFileName($path); + + if (!$this->copyFile($path, $destPath)) { + return false; + } + } + + return true; + }; + + return $duplicateFiles($paths); + } + + /** + * Clones a folder from the Library. + * @param array $path Specifies the folder path relative to the Library root. + */ + public function cloneFolder($path) + { + $originalPath = self::validatePath($path); + $newPath = dirname($path) .'/'. $this->generateIncrementedFolderName($originalPath); + + return $this->copyFolder($originalPath, $newPath); + } + + /** + * Copy a file to another location. + * @param string $oldPath Specifies the original path of the file. + * @param string $newPath Specifies the new path of the file. + * @return boolean + */ + public function copyFile($oldPath, $newPath, $isRename = false) + { + $oldPath = self::validatePath($oldPath); + $fullOldPath = $this->getMediaPath($oldPath); + + $newPath = self::validatePath($newPath); + $fullNewPath = $this->getMediaPath($newPath); + + // If the file extension is changed to SVG, ensure that it has been sanitized + $oldExt = pathinfo($oldPath, PATHINFO_EXTENSION); + $newExt = pathinfo($newPath, PATHINFO_EXTENSION); + if ($oldExt !== $newExt && strtolower($newExt) === 'svg') { + $contents = $this->getStorageDisk()->get($fullOldPath); + $contents = Svg::sanitize($contents); + return $this->getStorageDisk()->put($fullNewPath, $contents); + } + + return $this->getStorageDisk()->copy($fullOldPath, $fullNewPath); + } + /** * Moves a file to another location. * @param string $oldPath Specifies the original path of the file. @@ -898,4 +896,45 @@ protected function generateRandomTmpFolderName($location) return $tmpPath; } + + /** + * Generates a incremental file name based + * on the existing files in the same folder. + * + * @param string $path Specifies a file path to check. + * @return string The generated file name. + */ + public function generateIncrementedFileName($path): string + { + $pathInfos = pathinfo($path); + $sameFiles = array_filter( + $this->getStorageDisk()->files($pathInfos['dirname']), + function ($file) use ($pathInfos) { + return preg_match('/'. $pathInfos['filename'] .'-(\d*)\.'. $pathInfos['extension'] .'$/U', $file); + } + ); + $newIndex = count($sameFiles) + 1; + + return $pathInfos['filename'] .'-' . $newIndex . '.' . $pathInfos['extension']; + } + + /** + * Generates a incremental folder name based + * on the existing folders in the same folder. + * + * @param string $path Specifies a folder path to check. + * @return string The generated folder name. + */ + public function generateIncrementedFolderName($path) + { + $sameFolders = array_filter( + $this->getStorageDisk()->directories(dirname($this->getMediaPath($path))), + function ($folder) use ($path) { + return preg_match('/'. basename($path) .'-(\d*)$/U', $folder); + } + ); + $newIndex = count($sameFolders) + 1; + + return basename($path) .'-' . $newIndex; + } } From d99866d6fcb733edb528b6df8b783dd3dfc11770 Mon Sep 17 00:00:00 2001 From: DamsFX Date: Sat, 9 Mar 2024 18:52:48 +0100 Subject: [PATCH 3/8] Removing unnecessary comments --- modules/backend/lang/en/lang.php | 2 +- modules/backend/lang/fr/lang.php | 2 +- modules/backend/widgets/mediamanager/assets/js/mediamanager.js | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/backend/lang/en/lang.php b/modules/backend/lang/en/lang.php index 7bd94f0069..351f9a60e4 100644 --- a/modules/backend/lang/en/lang.php +++ b/modules/backend/lang/en/lang.php @@ -633,7 +633,7 @@ 'clone_empty' => 'Please select items to clone.', 'clone_multiple_confirm' => 'Multiple items selected, they will be cloned with generated names. Are you sure?', 'clone_popup_title' => 'Clone file or folder', - 'clone_new_name' => 'New name (suggested)', + 'clone_new_name' => 'New name', 'clone_button' => 'Clone', 'delete_empty' => 'Please select items to delete.', 'delete_confirm' => 'Delete the selected item(s)?', diff --git a/modules/backend/lang/fr/lang.php b/modules/backend/lang/fr/lang.php index 6ba7b2a17f..d4a458faaa 100644 --- a/modules/backend/lang/fr/lang.php +++ b/modules/backend/lang/fr/lang.php @@ -630,7 +630,7 @@ 'clone_empty' => 'Veuillez sélectionner les éléments à cloner.', 'clone_multiple_confirm' => 'Plusieurs éléments sélectionnés, ils seront clonés avec des noms générés. Êtes-vous sûr ?', 'clone_popup_title' => 'Cloner un fichier ou dossier', - 'clone_new_name' => 'Nouveau nom (suggestion)', + 'clone_new_name' => 'Nouveau nom', 'clone_button' => 'Cloner', 'delete_empty' => 'Veuillez sélectionner les éléments à supprimer.', 'delete_confirm' => 'Confirmer la suppression de ces éléments ?', diff --git a/modules/backend/widgets/mediamanager/assets/js/mediamanager.js b/modules/backend/widgets/mediamanager/assets/js/mediamanager.js index 86418652bd..d8856084fd 100644 --- a/modules/backend/widgets/mediamanager/assets/js/mediamanager.js +++ b/modules/backend/widgets/mediamanager/assets/js/mediamanager.js @@ -1015,7 +1015,6 @@ $.wn.confirm(this.options.cloneMultipleConfirm, this.proxy(this.cloneMultipleConfirmation)) } else { var data = { - // path: this.$el.find('[data-type="current-folder"]').val() path: items[0].getAttribute('data-path'), type: items[0].getAttribute('data-item-type') } From 34ad045b5f8f9fc6c069c760c6679d914f9ea8e0 Mon Sep 17 00:00:00 2001 From: DamsFX Date: Mon, 11 Mar 2024 12:13:54 +0100 Subject: [PATCH 4/8] Rename to "duplicate" --- modules/backend/lang/en/lang.php | 12 +-- modules/backend/lang/fr/lang.php | 12 +-- modules/backend/widgets/MediaManager.php | 90 +++++++++---------- .../assets/js/mediamanager-browser-min.js | 40 ++++----- .../mediamanager/assets/js/mediamanager.js | 54 +++++------ .../widgets/mediamanager/partials/_body.php | 4 +- .../{_clone-form.php => _duplicate-form.php} | 6 +- .../mediamanager/partials/_toolbar.php | 2 +- modules/system/classes/MediaLibrary.php | 12 +-- 9 files changed, 116 insertions(+), 116 deletions(-) rename modules/backend/widgets/mediamanager/partials/{_clone-form.php => _duplicate-form.php} (84%) diff --git a/modules/backend/lang/en/lang.php b/modules/backend/lang/en/lang.php index 351f9a60e4..b17e5cb0c7 100644 --- a/modules/backend/lang/en/lang.php +++ b/modules/backend/lang/en/lang.php @@ -599,7 +599,7 @@ 'menu_label' => 'Media', 'upload' => 'Upload', 'move' => 'Move', - 'clone' => 'Clone', + 'duplicate' => 'Duplicate', 'delete' => 'Delete', 'add_folder' => 'Add folder', 'search' => 'Search', @@ -630,13 +630,13 @@ 'direction_desc' => 'Descending', 'folder' => 'Folder', 'no_files_found' => 'No files found by your request.', - 'clone_empty' => 'Please select items to clone.', - 'clone_multiple_confirm' => 'Multiple items selected, they will be cloned with generated names. Are you sure?', - 'clone_popup_title' => 'Clone file or folder', - 'clone_new_name' => 'New name', - 'clone_button' => 'Clone', 'delete_empty' => 'Please select items to delete.', 'delete_confirm' => 'Delete the selected item(s)?', + 'duplicate_empty' => 'Please select items to duplicate.', + 'duplicate_multiple_confirm' => 'Multiple items selected. They will be duplicated with generated names. Are you sure?', + 'duplicate_popup_title' => 'Duplicate file or folder', + 'duplicate_new_name' => 'New name', + 'duplicate_button' => 'Duplicate', 'error_renaming_file' => 'Error renaming the item.', 'new_folder_title' => 'New folder', 'folder_name' => 'Folder name', diff --git a/modules/backend/lang/fr/lang.php b/modules/backend/lang/fr/lang.php index d4a458faaa..a1fc481dfc 100644 --- a/modules/backend/lang/fr/lang.php +++ b/modules/backend/lang/fr/lang.php @@ -596,7 +596,7 @@ 'menu_label' => 'Média', 'upload' => 'Déposer un fichier', 'move' => 'Déplacer', - 'clone' => 'Cloner', + 'duplicate' => 'Dupliquer', 'delete' => 'Supprimer', 'add_folder' => 'Ajouter un répertoire', 'search' => 'Rechercher', @@ -627,13 +627,13 @@ 'direction_desc' => 'Descendant', 'folder' => 'Répertoire', 'no_files_found' => 'Aucun fichier trouvé.', - 'clone_empty' => 'Veuillez sélectionner les éléments à cloner.', - 'clone_multiple_confirm' => 'Plusieurs éléments sélectionnés, ils seront clonés avec des noms générés. Êtes-vous sûr ?', - 'clone_popup_title' => 'Cloner un fichier ou dossier', - 'clone_new_name' => 'Nouveau nom', - 'clone_button' => 'Cloner', 'delete_empty' => 'Veuillez sélectionner les éléments à supprimer.', 'delete_confirm' => 'Confirmer la suppression de ces éléments ?', + 'duplicate_empty' => 'Veuillez sélectionner les éléments à dupliquer.', + 'duplicate_multiple_confirm' => 'Plusieurs éléments sélectionnés. Ils seront clonés avec des noms générés. Êtes-vous sûr ?', + 'duplicate_popup_title' => 'Dupliquer un fichier ou dossier', + 'duplicate_new_name' => 'Nouveau nom', + 'duplicate_button' => 'Dupliquer', 'error_renaming_file' => 'Erreur lors du renommage de l\'élément.', 'new_folder_title' => 'Nouveau répertoire', 'folder_name' => 'Nom du répertoire', diff --git a/modules/backend/widgets/MediaManager.php b/modules/backend/widgets/MediaManager.php index a9f332f7c3..11fce16122 100644 --- a/modules/backend/widgets/MediaManager.php +++ b/modules/backend/widgets/MediaManager.php @@ -521,9 +521,9 @@ public function onCreateFolder(): array } /** - * Render the clone popup for the provided "path" from the request + * Render the duplicate popup for the provided "path" from the request */ - public function onLoadClonePopup(): string + public function onLoadDuplicatePopup(): string { $this->abortIfReadOnly(); @@ -544,15 +544,15 @@ public function onLoadClonePopup(): string $this->vars['newName'] = $suggestedName; $this->vars['type'] = $type; - return $this->makePartial('clone-form'); + return $this->makePartial('duplicate-form'); } /** - * Clone the provided path from the request ("originalPath") to the new name ("name") + * Duplicate the provided path from the request ("originalPath") to the new name ("name") * * @throws ApplicationException if the new name is invalid */ - public function onCloneItem(): array + public function onDuplicateItem(): array { $this->abortIfReadOnly(); @@ -582,52 +582,52 @@ public function onCloneItem(): array } /* - * Clone single file + * Duplicate single file */ $library->copyFile($originalPath, $newPath); /** - * @event media.file.clone - * Called after a file is cloned + * @event media.file.duplicate + * Called after a file is duplicated * * Example usage: * - * Event::listen('media.file.clone', function ((\Backend\Widgets\MediaManager) $mediaWidget, (string) $originalPath, (string) $newPath) { - * \Log::info($originalPath . " was cloned to " . $path); + * Event::listen('media.file.duplicate', function ((\Backend\Widgets\MediaManager) $mediaWidget, (string) $originalPath, (string) $newPath) { + * \Log::info($originalPath . " was duplicated to " . $path); * }); * * Or * - * $mediaWidget->bindEvent('file.clone', function ((string) $originalPath, (string) $newPath) { - * \Log::info($originalPath . " was cloned to " . $path); + * $mediaWidget->bindEvent('file.duplicate', function ((string) $originalPath, (string) $newPath) { + * \Log::info($originalPath . " was duplicated to " . $path); * }); * */ - $this->fireSystemEvent('media.file.clone', [$originalPath, $newPath]); + $this->fireSystemEvent('media.file.duplicate', [$originalPath, $newPath]); } else { /* - * Clone single folder + * Duplicate single folder */ $library->copyFolder($originalPath, $newPath); /** - * @event media.folder.clone - * Called after a folder is cloned + * @event media.folder.duplicate + * Called after a folder is duplicated * * Example usage: * - * Event::listen('media.folder.clone', function ((\Backend\Widgets\MediaManager) $mediaWidget, (string) $originalPath, (string) $newPath) { - * \Log::info($originalPath . " was cloned to " . $path); + * Event::listen('media.folder.duplicate', function ((\Backend\Widgets\MediaManager) $mediaWidget, (string) $originalPath, (string) $newPath) { + * \Log::info($originalPath . " was duplicated to " . $path); * }); * * Or * - * $mediaWidget->bindEvent('folder.clone', function ((string) $originalPath, (string) $newPath) { - * \Log::info($originalPath . " was cloned to " . $path); + * $mediaWidget->bindEvent('folder.duplicate', function ((string) $originalPath, (string) $newPath) { + * \Log::info($originalPath . " was duplicated to " . $path); * }); * */ - $this->fireSystemEvent('media.folder.clone', [$originalPath, $newPath]); + $this->fireSystemEvent('media.folder.duplicate', [$originalPath, $newPath]); } $library->resetCache(); @@ -639,12 +639,12 @@ public function onCloneItem(): array } /** - * Clone the selected files or folders without prompting the user + * Duplicate the selected files or folders without prompting the user * The new name will be generated in an incremented sequence * * @throws ApplicationException if the input data is invalid */ - public function onCloneItems(): array + public function onDuplicateItems(): array { $this->abortIfReadOnly(); @@ -656,7 +656,7 @@ public function onCloneItems(): array $library = MediaLibrary::instance(); - $filesToClone = []; + $filesToDuplicate = []; foreach ($paths as $pathInfo) { $path = array_get($pathInfo, 'path'); $type = array_get($pathInfo, 'type'); @@ -669,62 +669,62 @@ public function onCloneItems(): array /* * Add to bulk collection */ - $filesToClone[] = $path; + $filesToDuplicate[] = $path; } elseif ($type === MediaLibraryItem::TYPE_FOLDER) { /* - * Clone single folder + * Duplicate single folder */ - $library->cloneFolder($path); + $library->duplicateFolder($path); /** - * @event media.folder.clone - * Called after a folder is cloned + * @event media.folder.duplicate + * Called after a folder is duplicated * * Example usage: * - * Event::listen('media.folder.clone', function ((\Backend\Widgets\MediaManager) $mediaWidget, (string) $path) { - * \Log::info($path . " was cloned"); + * Event::listen('media.folder.duplicate', function ((\Backend\Widgets\MediaManager) $mediaWidget, (string) $path) { + * \Log::info($path . " was duplicated"); * }); * * Or * - * $mediaWidget->bindEvent('folder.clone', function ((string) $path) { - * \Log::info($path . " was cloned"); + * $mediaWidget->bindEvent('folder.duplicate', function ((string) $path) { + * \Log::info($path . " was duplicated"); * }); * */ - $this->fireSystemEvent('media.folder.clone', [$path]); + $this->fireSystemEvent('media.folder.duplicate', [$path]); } } - if (count($filesToClone) > 0) { + if (count($filesToDuplicate) > 0) { /* - * Clone collection of files + * Duplicate collection of files */ - $library->cloneFiles($filesToClone); + $library->duplicateFiles($filesToDuplicate); /* * Extensibility */ - foreach ($filesToClone as $path) { + foreach ($filesToDuplicate as $path) { /** - * @event media.file.clone - * Called after a file is cloned + * @event media.file.duplicate + * Called after a file is duplicated * * Example usage: * - * Event::listen('media.file.clone', function ((\Backend\Widgets\MediaManager) $mediaWidget, (string) $path) { - * \Log::info($path . " was cloned"); + * Event::listen('media.file.duplicate', function ((\Backend\Widgets\MediaManager) $mediaWidget, (string) $path) { + * \Log::info($path . " was duplicated"); * }); * * Or * - * $mediaWidget->bindEvent('file.clone', function ((string) $path) { - * \Log::info($path . " was cloned"); + * $mediaWidget->bindEvent('file.duplicate', function ((string) $path) { + * \Log::info($path . " was duplicated"); * }); * */ - $this->fireSystemEvent('media.file.clone', [$path]); + $this->fireSystemEvent('media.file.duplicate', [$path]); } } diff --git a/modules/backend/widgets/mediamanager/assets/js/mediamanager-browser-min.js b/modules/backend/widgets/mediamanager/assets/js/mediamanager-browser-min.js index bb79a3b8dc..de1bad4ea9 100644 --- a/modules/backend/widgets/mediamanager/assets/js/mediamanager-browser-min.js +++ b/modules/backend/widgets/mediamanager/assets/js/mediamanager-browser-min.js @@ -64,8 +64,8 @@ this.$el.on('input','[data-control="search"]',this.proxy(this.onSearchChanged)) this.$el.on('mediarefresh',this.proxy(this.refresh)) this.$el.on('shown.oc.popup','[data-command="create-folder"]',this.proxy(this.onFolderPopupShown)) this.$el.on('hidden.oc.popup','[data-command="create-folder"]',this.proxy(this.onFolderPopupHidden)) -this.$el.on('shown.oc.popup','[data-command="clone"]',this.proxy(this.onClonePopupShown)) -this.$el.on('hidden.oc.popup','[data-command="clone"]',this.proxy(this.onClonePopupHidden)) +this.$el.on('shown.oc.popup','[data-command="duplicate"]',this.proxy(this.onDuplicatePopupShown)) +this.$el.on('hidden.oc.popup','[data-command="duplicate"]',this.proxy(this.onDuplicatePopupHidden)) this.$el.on('shown.oc.popup','[data-command="move"]',this.proxy(this.onMovePopupShown)) this.$el.on('hidden.oc.popup','[data-command="move"]',this.proxy(this.onMovePopupHidden)) this.$el.on('keydown',this.proxy(this.onKeyDown)) @@ -79,8 +79,8 @@ this.$el.off('change','[data-control="sorting"]',this.proxy(this.onSortingChange this.$el.off('keyup','[data-control="search"]',this.proxy(this.onSearchChanged)) this.$el.off('shown.oc.popup','[data-command="create-folder"]',this.proxy(this.onFolderPopupShown)) this.$el.off('hidden.oc.popup','[data-command="create-folder"]',this.proxy(this.onFolderPopupHidden)) -this.$el.off('shown.oc.popup','[data-command="clone"]',this.proxy(this.onClonePopupShown)) -this.$el.off('hidden.oc.popup','[data-command="clone"]',this.proxy(this.onClonePopupHidden)) +this.$el.off('shown.oc.popup','[data-command="duplicate"]',this.proxy(this.onDuplicatePopupShown)) +this.$el.off('hidden.oc.popup','[data-command="duplicate"]',this.proxy(this.onDuplicatePopupHidden)) this.$el.off('shown.oc.popup','[data-command="move"]',this.proxy(this.onMovePopupShown)) this.$el.off('hidden.oc.popup','[data-command="move"]',this.proxy(this.onMovePopupHidden)) this.$el.off('keydown',this.proxy(this.onKeyDown)) @@ -104,9 +104,9 @@ MediaManager.prototype.selectItem=function(node,expandSelection){if(!expandSelec for(var i=0,len=items.length;i1){$.wn.confirm(this.options.cloneMultipleConfirm,this.proxy(this.cloneMultipleConfirmation))}else{var data={path:items[0].getAttribute('data-path'),type:items[0].getAttribute('data-item-type')} -$(ev.target).popup({handler:this.options.alias+'::onLoadClonePopup',extraData:data,zIndex:1200})}} -MediaManager.prototype.cloneMultipleConfirmation=function(confirmed){if(!confirmed)return +MediaManager.prototype.duplicateItems=function(ev){var items=this.$el.get(0).querySelectorAll('[data-type="media-item"].selected') +if(!items.length){$.wn.alert(this.options.duplicateEmpty) +return}if(items.length>1){$.wn.confirm(this.options.duplicateMultipleConfirm,this.proxy(this.duplicateMultipleConfirmation))}else{var data={path:items[0].getAttribute('data-path'),type:items[0].getAttribute('data-item-type')} +$(ev.target).popup({handler:this.options.alias+'::onLoadDuplicatePopup',extraData:data,zIndex:1200})}} +MediaManager.prototype.duplicateMultipleConfirmation=function(confirmed){if(!confirmed)return var items=this.$el.get(0).querySelectorAll('[data-type="media-item"].selected'),paths=[] for(var i=0,len=items.length;i 1) { - $.wn.confirm(this.options.cloneMultipleConfirm, this.proxy(this.cloneMultipleConfirmation)) + $.wn.confirm(this.options.duplicateMultipleConfirm, this.proxy(this.duplicateMultipleConfirmation)) } else { var data = { path: items[0].getAttribute('data-path'), @@ -1020,14 +1020,14 @@ } $(ev.target).popup({ - handler: this.options.alias+'::onLoadClonePopup', + handler: this.options.alias+'::onLoadDuplicatePopup', extraData: data, zIndex: 1200 // Media Manager can be opened in a popup, so this new popup should have a higher z-index }) } } - MediaManager.prototype.cloneMultipleConfirmation = function (confirmed) { + MediaManager.prototype.duplicateMultipleConfirmation = function (confirmed) { if (!confirmed) return @@ -1050,18 +1050,18 @@ } $.wn.stripeLoadIndicator.show() - this.$form.request(this.options.alias + '::onCloneItems', { + this.$form.request(this.options.alias + '::onDuplicateItems', { data: data }).always(function () { $.wn.stripeLoadIndicator.hide() }).done(this.proxy(this.afterNavigate)) } - MediaManager.prototype.onClonePopupShown = function (ev, button, popup) { - $(popup).on('submit.media', 'form', this.proxy(this.onCloneItemSubmit)) + MediaManager.prototype.onDuplicatePopupShown = function (ev, button, popup) { + $(popup).on('submit.media', 'form', this.proxy(this.onDuplicateItemSubmit)) } - MediaManager.prototype.onCloneItemSubmit = function (ev) { + MediaManager.prototype.onDuplicateItemSubmit = function (ev) { var item = this.$el.get(0).querySelector('[data-type="media-item"].selected'), data = { newName: $(ev.target).find('input[name=newName]').val(), @@ -1070,22 +1070,22 @@ } $.wn.stripeLoadIndicator.show() - this.$form.request(this.options.alias + '::onCloneItem', { + this.$form.request(this.options.alias + '::onDuplicateItem', { data: data }).always(function () { $.wn.stripeLoadIndicator.hide() - }).done(this.proxy(this.itemCloned)) + }).done(this.proxy(this.itemDuplicated)) ev.preventDefault() return false } - MediaManager.prototype.onClonePopupHidden = function (ev, button, popup) { + MediaManager.prototype.onDuplicatePopupHidden = function (ev, button, popup) { $(popup).off('.media', 'form') } - MediaManager.prototype.itemCloned = function () { - this.$el.find('button[data-command="clone"]').popup('hide') + MediaManager.prototype.itemDuplicated = function () { + this.$el.find('button[data-command="duplicate"]').popup('hide') this.afterNavigate() } @@ -1200,8 +1200,8 @@ case 'create-folder': this.createFolder(ev) break; - case 'clone': - this.cloneItems(ev) + case 'duplicate': + this.duplicateItems(ev) break; case 'move': this.moveItems(ev) @@ -1398,8 +1398,8 @@ url: window.location, uploadHandler: null, alias: '', - cloneEmpty: 'Please select an item to clone.', - cloneMultipleConfirm: 'Multiple items selected, they will be cloned with generated names. Are you sure?', + duplicateEmpty: 'Please select an item to duplicate.', + duplicateMultipleConfirm: 'Multiple items selected, they will be duplicated with generated names. Are you sure?', deleteEmpty: 'Please select files to delete.', deleteConfirm: 'Delete the selected file(s)?', moveEmpty: 'Please select files to move.', diff --git a/modules/backend/widgets/mediamanager/partials/_body.php b/modules/backend/widgets/mediamanager/partials/_body.php index c91f0304dc..ab0ba54b4d 100644 --- a/modules/backend/widgets/mediamanager/partials/_body.php +++ b/modules/backend/widgets/mediamanager/partials/_body.php @@ -3,8 +3,8 @@ class="layout" data-alias="alias ?>" data-upload-handler="getEventHandler('onUpload') ?>" - data-clone-empty="" - data-clone-multiple-confirm="" + data-duplicate-empty="" + data-duplicate-multiple-confirm="" data-delete-empty="" data-delete-confirm="" data-move-empty="" diff --git a/modules/backend/widgets/mediamanager/partials/_clone-form.php b/modules/backend/widgets/mediamanager/partials/_duplicate-form.php similarity index 84% rename from modules/backend/widgets/mediamanager/partials/_clone-form.php rename to modules/backend/widgets/mediamanager/partials/_duplicate-form.php index f9da195c62..f349aabf7d 100644 --- a/modules/backend/widgets/mediamanager/partials/_clone-form.php +++ b/modules/backend/widgets/mediamanager/partials/_duplicate-form.php @@ -1,11 +1,11 @@