[FEATURE] Add duplicate button 01/53101/19
authorWilli Wehmeier <wwwehmeier@gmail.com>
Sat, 3 Jun 2017 18:35:05 +0000 (20:35 +0200)
committerFrank Naegler <frank.naegler@typo3.org>
Tue, 13 Feb 2018 12:24:38 +0000 (13:24 +0100)
This patch provides a new "duplicate" button inside of the topbar of the
edit record form next to the close button. This button creates and opens
a new copy of the current element which is positioned right below the
existing one.
If there are unsaved changes in the currently open record, a modal
appears with options to cancel, dismiss the changes and clone or save
the changes and clone the changed record.

Resolves: #77685
Releases: master
Change-Id: I9e2a0f02fc9c4452f0a7d868ea17aeacb9375d95
Reviewed-on: https://review.typo3.org/53101
Reviewed-by: Wolfgang Klinger <wolfgang@wazum.com>
Tested-by: Wolfgang Klinger <wolfgang@wazum.com>
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Mathias Schreiber <mathias.schreiber@typo3.com>
Tested-by: Mathias Schreiber <mathias.schreiber@typo3.com>
Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Tested-by: Frank Naegler <frank.naegler@typo3.org>
Reviewed-by: Frank Naegler <frank.naegler@typo3.org>
typo3/sysext/backend/Classes/Controller/EditDocumentController.php
typo3/sysext/backend/Resources/Public/JavaScript/ContextMenuActions.js
typo3/sysext/backend/Resources/Public/JavaScript/FormEngine.js
typo3/sysext/core/Documentation/Changelog/master/Feature-77685-CreateASaveAndOpenCopyButtonWhenSavingAContentElement.rst [new file with mode: 0644]
typo3/sysext/lang/Resources/Private/Language/locallang_alt_doc.xlf
typo3/sysext/lang/Resources/Private/Language/locallang_core.xlf

index 0c9c9c0..401b447 100644 (file)
@@ -455,7 +455,7 @@ class EditDocumentController
         $this->columnsOnly = GeneralUtility::_GP('columnsOnly');
         $this->returnUrl = GeneralUtility::sanitizeLocalUrl(GeneralUtility::_GP('returnUrl'));
         $this->closeDoc = (int)GeneralUtility::_GP('closeDoc');
-        $this->doSave = GeneralUtility::_GP('doSave');
+        $this->doSave = (bool)GeneralUtility::_GP('doSave');
         $this->returnEditConf = GeneralUtility::_GP('returnEditConf');
         $this->workspace = GeneralUtility::_GP('workspace');
         $this->uc = GeneralUtility::_GP('uc');
@@ -509,6 +509,7 @@ class EditDocumentController
             || isset($_POST['_saveandclosedok'])
             || isset($_POST['_savedokview'])
             || isset($_POST['_savedoknew'])
+            || isset($_POST['_duplicatedoc'])
             || isset($_POST['_translation_savedok'])
             || isset($_POST['_translation_savedokclear']);
         return $out;
@@ -559,9 +560,11 @@ class EditDocumentController
         }
 
         // Perform the saving operation with DataHandler:
-        $tce->process_uploads($_FILES);
-        $tce->process_datamap();
-        $tce->process_cmdmap();
+        if ($this->doSave === true) {
+            $tce->process_uploads($_FILES);
+            $tce->process_datamap();
+            $tce->process_cmdmap();
+        }
         // If pages are being edited, we set an instruction about updating the page tree after this operation.
         if ($tce->pagetreeNeedsRefresh
             && (isset($this->data['pages']) || $beUser->workspace != 0 && !empty($this->data))
@@ -666,6 +669,75 @@ class EditDocumentController
             // Re-compile the store* values since editconf changed...
             $this->compileStoreDat();
         }
+        // If a document should be duplicated.
+        if (isset($_POST['_duplicatedoc']) && is_array($this->editconf)) {
+            $this->closeDocument(self::DOCUMENT_CLOSE_MODE_NO_REDIRECT);
+            // Finding the current table:
+            reset($this->editconf);
+            $nTable = key($this->editconf);
+            // Finding the first id, getting the records pid+uid
+            reset($this->editconf[$nTable]);
+            $nUid = key($this->editconf[$nTable]);
+            if (!MathUtility::canBeInterpretedAsInteger($nUid)) {
+                $nUid = $tce->substNEWwithIDs[$nUid];
+            }
+
+            $recordFields = 'pid,uid';
+            if (!empty($GLOBALS['TCA'][$nTable]['ctrl']['versioningWS'])) {
+                $recordFields .= ',t3ver_oid';
+            }
+            $nRec = BackendUtility::getRecord($nTable, $nUid, $recordFields);
+
+            // Setting a blank editconf array for a new record:
+            $this->editconf = [];
+
+            if ($nRec['pid'] != -1) {
+                $relatedPageId = -$nRec['uid'];
+            } else {
+                $relatedPageId = -$nRec['t3ver_oid'];
+            }
+
+            /** @var $duplicateTce \TYPO3\CMS\Core\DataHandling\DataHandler */
+            $duplicateTce = GeneralUtility::makeInstance(DataHandler::class);
+
+            $duplicateCmd = [
+                $nTable => [
+                    $nUid => [
+                        'copy' => $relatedPageId
+                    ]
+                ]
+            ];
+
+            $duplicateTce->start([], $duplicateCmd);
+            $duplicateTce->process_cmdmap();
+
+            $duplicateMappingArray = $duplicateTce->copyMappingArray;
+            $duplicateUid = $duplicateMappingArray[$nTable][$nUid];
+
+            if ($nTable === 'pages') {
+                BackendUtility::setUpdateSignal('updatePageTree');
+            }
+
+            $this->editconf[$nTable][$duplicateUid] = 'edit';
+            // Finally, set the editconf array in the "getvars" so they will be passed along in URLs as needed.
+            $this->R_URL_getvars['edit'] = $this->editconf;
+            // Re-compile the store* values since editconf changed...
+            $this->compileStoreDat();
+
+            // Inform the user of the duplication
+            /** @var $flashMessage \TYPO3\CMS\Core\Messaging\FlashMessage */
+            $flashMessage = GeneralUtility::makeInstance(
+                FlashMessage::class,
+                $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.recordDuplicated'),
+                '',
+                FlashMessage::OK
+            );
+            /** @var $flashMessageService \TYPO3\CMS\Core\Messaging\FlashMessageService */
+            $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
+            /** @var $defaultFlashMessageQueue FlashMessageQueue */
+            $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
+            $defaultFlashMessageQueue->enqueue($flashMessage);
+        }
         // If a preview is requested
         if (isset($_POST['_savedokview'])) {
             // Get the first table and id of the data array from DataHandler
@@ -1255,6 +1327,19 @@ class EditDocumentController
                 Icon::SIZE_SMALL
             ));
         $buttonBar->addButton($closeButton);
+        // DUPLICATE button:
+        if ($this->firstEl['cmd'] !== 'new' && MathUtility::canBeInterpretedAsInteger($this->firstEl['uid'])) {
+            $duplicateButton = $buttonBar->makeLinkButton()
+                ->setHref('#')
+                ->setClasses('t3js-editform-duplicate')
+                ->setShowLabelText(true)
+                ->setTitle($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:rm.duplicateDoc'))
+                ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
+                    'actions-document-duplicates-select',
+                    Icon::SIZE_SMALL
+                ));
+            $buttonBar->addButton($duplicateButton, ButtonBar::BUTTON_POSITION_LEFT, 3);
+        }
         // DELETE + UNDO buttons:
         if (!$this->errorC
             && !$GLOBALS['TCA'][$this->firstEl['table']]['ctrl']['readOnly']
@@ -1291,7 +1376,7 @@ class EditDocumentController
                             'uid' => $this->firstEl['uid'],
                             'table' => $this->firstEl['table']
                         ]);
-                    $buttonBar->addButton($deleteButton, ButtonBar::BUTTON_POSITION_LEFT, 3);
+                    $buttonBar->addButton($deleteButton, ButtonBar::BUTTON_POSITION_LEFT, 4);
                 }
                 // Undo:
                 if ($this->getNewIconMode($this->firstEl['table'], 'showHistory')) {
index a60042c..3f7b2a2 100644 (file)
@@ -149,7 +149,7 @@ define(['jquery', 'TYPO3/CMS/Backend/Modal', 'TYPO3/CMS/Backend/Severity'], func
     var url = TYPO3.settings.ajaxUrls['contextmenu_clipboard'];
     url += '&CB[el][' + table + '%7C' + uid + ']=1' + '&CB[setCopyMode]=1';
     $.ajax(url).always(function() {
-      top.TYPO3.Backend.ContentContainer.refresh(true);
+      ContextMenuActions.triggerRefresh(top.TYPO3.Backend.ContentContainer.get().location.href);
     });
   };
 
@@ -157,7 +157,7 @@ define(['jquery', 'TYPO3/CMS/Backend/Modal', 'TYPO3/CMS/Backend/Severity'], func
     var url = TYPO3.settings.ajaxUrls['contextmenu_clipboard'];
     url += '&CB[el][' + table + '%7C' + uid + ']=0';
     $.ajax(url).always(function() {
-      top.TYPO3.Backend.ContentContainer.refresh(true);
+      ContextMenuActions.triggerRefresh(top.TYPO3.Backend.ContentContainer.get().location.href);
     });
   };
 
@@ -165,10 +165,16 @@ define(['jquery', 'TYPO3/CMS/Backend/Modal', 'TYPO3/CMS/Backend/Severity'], func
     var url = TYPO3.settings.ajaxUrls['contextmenu_clipboard'];
     url += '&CB[el][' + table + '%7C' + uid + ']=1' + '&CB[setCopyMode]=0';
     $.ajax(url).always(function() {
-      top.TYPO3.Backend.ContentContainer.refresh(true);
+      ContextMenuActions.triggerRefresh(top.TYPO3.Backend.ContentContainer.get().location.href);
     });
   };
 
+  ContextMenuActions.triggerRefresh = function (iframeUrl) {
+    if (iframeUrl.indexOf("record%2Fedit") === -1) {
+      top.TYPO3.Backend.ContentContainer.refresh(true);
+    }
+  };
+
   /**
    * Clear cache for given page uid
    *
index 108e2f9..0ac3f1f 100644 (file)
@@ -647,6 +647,50 @@ define(['jquery',
       FormEngine.preventExitIfNotSaved(
         FormEngine.preventExitIfNotSavedCallback
       );
+    }).on('click', '.t3js-editform-duplicate', function(e) {
+        e.preventDefault();
+        var $elem = $('<input />').attr('type', 'hidden').attr('name', '_duplicatedoc').attr('value', '1');
+        if ($('form[name="' + FormEngine.formName + '"] .has-change').length > 0) {
+          var title = TYPO3.lang['label.confirm.duplicate_record_changed.title'] || 'Duplicate changed record?';
+          var content = TYPO3.lang['label.confirm.duplicate_record_changed.content'] || 'Do you want to save your changes and duplicate the record?';
+          var $modal = Modal.confirm(title, content, Severity.warning, [
+            {
+              text: TYPO3.lang['buttons.confirm.duplicate_record_changed.cancel'] || 'Cancel',
+              active: true,
+              btnClass: 'btn-default',
+              name: 'cancel'
+            },
+            {
+              text: TYPO3.lang['buttons.confirm.duplicate_record_changed.dismiss_and_duplicate'] || 'Dismiss changes and duplicate',
+              active: true,
+              btnClass: 'btn-default',
+              name: 'dismissDuplicate'
+            },
+            {
+              text: TYPO3.lang['buttons.confirm.duplicate_record_changed.save_and_duplicate'] || 'Save changes and duplicate',
+              btnClass: 'btn-success',
+              name: 'saveDuplicate'
+            }
+        ]);
+          $modal.on('button.clicked', function (e) {
+            if (e.target.name === 'cancel') {
+              Modal.dismiss();
+            } else if (e.target.name === 'dismissDuplicate') {
+              $('form[name=' + FormEngine.formName + ']').append($elem);
+              $('input[name=doSave]').val(0);
+              Modal.dismiss();
+              document.editform.submit();
+            } else if (e.target.name == 'saveDuplicate') {
+              $('form[name=' + FormEngine.formName + ']').append($elem);
+              $('input[name=doSave]').val(1);
+              Modal.dismiss();
+              document.editform.submit();
+            }
+          });
+        } else {
+          $('form[name=' + FormEngine.formName + ']').append($elem);
+          document.editform.submit();
+        }
     }).on('click', '.t3js-editform-delete-record', function(e) {
       e.preventDefault();
       var title = TYPO3.lang['label.confirm.delete_record.title'] || 'Delete this record?';
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-77685-CreateASaveAndOpenCopyButtonWhenSavingAContentElement.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-77685-CreateASaveAndOpenCopyButtonWhenSavingAContentElement.rst
new file mode 100644 (file)
index 0000000..f066cd6
--- /dev/null
@@ -0,0 +1,21 @@
+.. include:: ../../Includes.txt
+
+==================================================================================
+Feature: #77685 - Create a save and open copy button when saving a content element
+==================================================================================
+
+See :issue:`77685`
+
+Description
+===========
+
+This patch adds a "clone content element" icon next to the save icon in the edit record form for already persisted reccords. If there are not persisted changes when pressing the button a modal appears, providing the following 3 options: abort, clone the content element without saving the current changes, save the changes and clones the record afterwards. The copy of the record will by put right below the record itself.
+After saving, the edit record form opens for the copied element.
+
+
+Impact
+======
+
+Editors are able to make a duplicate of a record with just a single click. They don't have to copy & paste.
+
+.. index:: Backend
\ No newline at end of file
index 4698db7..fb1e429 100644 (file)
                        <trans-unit id="noDocuments_listmodule">
                                <source>the Web&gt;List module</source>
                        </trans-unit>
+                       <trans-unit id="buttons.confirm.duplicate_record_changed.cancel">
+                               <source>Cancel</source>
+                       </trans-unit>
+                       <trans-unit id="button.confirm.duplicate_record_changed.dismiss_and_duplicate">
+                               <source>Dismiss changes and duplicate</source>
+                       </trans-unit>
+                       <trans-unit id="buttons.confirm.duplicate_record_changed.save_and_duplicate">
+                               <source>Save changes and duplicate</source>
+                       </trans-unit>
                        <trans-unit id="buttons.confirm.close_without_save.yes">
                                <source>Yes, discard my changes</source>
                        </trans-unit>
                        <trans-unit id="buttons.confirm.close_without_save.no">
                                <source>No, I will continue editing</source>
                        </trans-unit>
+                       <trans-unit id="label.confirm.duplicate_record_changed.title">
+                               <source>Duplicate changed record?</source>
+                       </trans-unit>
+                       <trans-unit id="label.confirm.duplicate_record_changed.content">
+                               <source>Do you want to save your changes and duplicate the record?</source>
+                       </trans-unit>
                        <trans-unit id="label.confirm.close_without_save.title">
                                <source>Do you want to quit without saving?</source>
                        </trans-unit>
index cc9a929..cde5659 100644 (file)
@@ -864,6 +864,12 @@ Do you want to refresh it now?</source>
                        <trans-unit id="rm.saveNewDoc">
                                <source>Save and create a new one</source>
                        </trans-unit>
+                       <trans-unit id="rm.duplicateDoc">
+                               <source>Duplicate</source>
+                       </trans-unit>
+                       <trans-unit id="labels.recordDuplicated">
+                               <source>The record has been duplicated.</source>
+                       </trans-unit>
                        <trans-unit id="rm.closeDoc">
                                <source>Close</source>
                        </trans-unit>