[TASK] Revamp EXT:recycler 09/36109/22
authorFelix Kopp <felix-source@phorax.com>
Wed, 24 Apr 2013 23:00:20 +0000 (01:00 +0200)
committerChristian Kuhn <lolli@schwarzbu.ch>
Thu, 29 Jan 2015 17:09:40 +0000 (18:09 +0100)
Refactors the recycler extension to a modern architecture. The backend
is based on Extbase and Fluid, the UI is based on jQuery and
Twitter Bootstrap now.

Due to restrictions in the core, non-admin users cannot restore deleted
pages for now.

Kudos to Felix Kopp for porting the base to Extbase and Fluid.

Releases: master
Resolves: #64420
Change-Id: I9d330981af0b42703b8352c1d61bec818e08b38e
Reviewed-on: http://review.typo3.org/36109
Reviewed-by: Benjamin Mack <benni@typo3.org>
Tested-by: Benjamin Mack <benni@typo3.org>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
41 files changed:
typo3/sysext/backend/Resources/Public/JavaScript/Modal.js
typo3/sysext/recycler/Classes/Controller/DeletedRecordsController.php
typo3/sysext/recycler/Classes/Controller/RecyclerAjaxController.php
typo3/sysext/recycler/Classes/Controller/RecyclerModuleController.php
typo3/sysext/recycler/Classes/Domain/Model/DeletedRecords.php
typo3/sysext/recycler/Classes/Domain/Model/Tables.php
typo3/sysext/recycler/Classes/Utility/RecyclerUtility.php
typo3/sysext/recycler/Resources/Private/Language/locallang.xlf [new file with mode: 0644]
typo3/sysext/recycler/Resources/Private/Language/locallang_mod.xlf [new file with mode: 0644]
typo3/sysext/recycler/Resources/Private/Layouts/Default.html [new file with mode: 0644]
typo3/sysext/recycler/Resources/Private/Partials/RecordsTable/DeletedRecord.html [new file with mode: 0644]
typo3/sysext/recycler/Resources/Private/Templates/Ajax/RecordsTable.html [new file with mode: 0644]
typo3/sysext/recycler/Resources/Private/Templates/RecyclerModule/Index.html [new file with mode: 0644]
typo3/sysext/recycler/Resources/Private/Templates/RecyclerModule/NoAccess.html [new file with mode: 0644]
typo3/sysext/recycler/Resources/Public/JavaScript/Recycler.js [new file with mode: 0644]
typo3/sysext/recycler/ext_localconf.php
typo3/sysext/recycler/ext_tables.php
typo3/sysext/recycler/mod1/clear.gif [deleted file]
typo3/sysext/recycler/mod1/index.php [deleted file]
typo3/sysext/recycler/mod1/locallang.xlf [deleted file]
typo3/sysext/recycler/mod1/locallang_mod.xlf [deleted file]
typo3/sysext/recycler/mod1/mod_template.html [deleted file]
typo3/sysext/recycler/mod1/moduleicon.gif [deleted file]
typo3/sysext/recycler/res/css/customExtJs.css [deleted file]
typo3/sysext/recycler/res/icons/accept.png [deleted file]
typo3/sysext/recycler/res/icons/arrow_redo.png [deleted file]
typo3/sysext/recycler/res/icons/arrow_rotate_anticlockwise.png [deleted file]
typo3/sysext/recycler/res/icons/bin.png [deleted file]
typo3/sysext/recycler/res/icons/bin_closed.png [deleted file]
typo3/sysext/recycler/res/icons/bin_empty.png [deleted file]
typo3/sysext/recycler/res/icons/database_save.png [deleted file]
typo3/sysext/recycler/res/icons/delete.gif [deleted file]
typo3/sysext/recycler/res/icons/filter_clear.png [deleted file]
typo3/sysext/recycler/res/icons/filter_refresh.png [deleted file]
typo3/sysext/recycler/res/icons/loading.gif [deleted file]
typo3/sysext/recycler/res/icons/recycler.gif [deleted file]
typo3/sysext/recycler/res/icons/recycler2.gif [deleted file]
typo3/sysext/recycler/res/icons/x_toolbar_bg.gif [deleted file]
typo3/sysext/recycler/res/js/t3_recycler.js [deleted file]
typo3/sysext/t3skin/Resources/Private/Styles/TYPO3/_element_table.less
typo3/sysext/t3skin/Resources/Public/Css/visual/t3skin.css

index 21e9f2d..3ae3685 100644 (file)
@@ -89,7 +89,7 @@ define('TYPO3/CMS/Backend/Modal', ['jquery', 'TYPO3/CMS/Backend/FlashMessages'],
                        Modal.currentModal.trigger('modal-dismiss');
                });
 
-               if (content instanceof $) {
+               if (typeof content === 'object') {
                        Modal.currentModal.find('.modal-body').append(content);
                } else {
                        // we need html, check if we have to wrap content in <p>
index 690b7fc..9b41de0 100644 (file)
@@ -15,6 +15,9 @@ namespace TYPO3\CMS\Recycler\Controller;
  */
 
 use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Backend\Utility\IconUtility;
+use TYPO3\CMS\Core\DataHandling\DataHandler;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Recycler\Utility\RecyclerUtility;
 
 /**
@@ -26,22 +29,25 @@ use TYPO3\CMS\Recycler\Utility\RecyclerUtility;
 class DeletedRecordsController {
 
        /**
-        * @var \TYPO3\CMS\Lang\LanguageService
+        * @var \TYPO3\CMS\Core\Cache\Frontend\VariableFrontend
         */
-       protected $languageService;
+       protected $runtimeCache = NULL;
 
        /**
-        * Constructor
+        * @var DataHandler
         */
+       protected $tce;
+
        public function __construct() {
-               $this->languageService = $GLOBALS['LANG'];
+               $this->runtimeCache = $this->getMemoryCache();
+               $this->tce = GeneralUtility::makeInstance(DataHandler::class);
        }
 
        /**
-        * Transforms the rows for the deleted Records into the Array View necessary for ExtJS Ext.data.ArrayReader
+        * Transforms the rows for the deleted records
         *
         * @param array $deletedRowsArray Array with table as key and array with all deleted rows
-        * @param int $totalDeleted Number of deleted records in total, for PagingToolbar
+        * @param int $totalDeleted Number of deleted records in total
         * @return string JSON array
         */
        public function transform($deletedRowsArray, $totalDeleted) {
@@ -49,30 +55,93 @@ class DeletedRecordsController {
                $jsonArray = array(
                        'rows' => array()
                );
-               // iterate
-               if (is_array($deletedRowsArray) && count($deletedRowsArray) > 0) {
+
+               if (is_array($deletedRowsArray)) {
+                       $lang = $this->getLanguageService();
+                       $backendUser = $this->getBackendUser();
+
                        foreach ($deletedRowsArray as $table => $rows) {
                                $total += count($deletedRowsArray[$table]);
                                foreach ($rows as $row) {
+                                       $pageTitle = $this->getPageTitle((int)$row['pid']);
                                        $backendUser = BackendUtility::getRecord('be_users', $row[$GLOBALS['TCA'][$table]['ctrl']['cruser_id']], 'username', '', FALSE);
                                        $jsonArray['rows'][] = array(
                                                'uid' => $row['uid'],
                                                'pid' => $row['pid'],
+                                               'icon' => IconUtility::getSpriteIconForRecord($table, $row),
+                                               'pageTitle' => RecyclerUtility::getUtf8String($pageTitle),
                                                'table' => $table,
                                                'crdate' => BackendUtility::datetime($row[$GLOBALS['TCA'][$table]['ctrl']['crdate']]),
                                                'tstamp' => BackendUtility::datetime($row[$GLOBALS['TCA'][$table]['ctrl']['tstamp']]),
                                                'owner' => htmlspecialchars($backendUser['username']),
                                                'owner_uid' => $row[$GLOBALS['TCA'][$table]['ctrl']['cruser_id']],
-                                               'tableTitle' => RecyclerUtility::getUtf8String($this->languageService->sL($GLOBALS['TCA'][$table]['ctrl']['title'])),
-                                               'title' => htmlspecialchars(RecyclerUtility::getUtf8String(
-                                                               BackendUtility::getRecordTitle($table, $row))),
+                                               'tableTitle' => RecyclerUtility::getUtf8String($lang->sL($GLOBALS['TCA'][$table]['ctrl']['title'])),
+                                               'title' => htmlspecialchars(RecyclerUtility::getUtf8String(BackendUtility::getRecordTitle($table, $row))),
                                                'path' => RecyclerUtility::getRecordPath($row['pid'])
                                        );
                                }
                        }
                }
                $jsonArray['total'] = $totalDeleted;
-               return json_encode($jsonArray);
+               return $jsonArray;
+       }
+
+       /**
+        * Gets the page title of the given page id
+        *
+        * @param int $pageId
+        * @return string
+        */
+       protected function getPageTitle($pageId) {
+               $cacheId = 'recycler-pagetitle-' . $pageId;
+               if ($this->runtimeCache->has($cacheId)) {
+                       $pageTitle = $this->runtimeCache->get($cacheId);
+               } else {
+                       if ($pageId === 0) {
+                               $pageTitle = $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'];
+                       } else {
+                               $recordInfo = $this->tce->recordInfo('pages', $pageId, 'title');
+                               $pageTitle = $recordInfo['title'];
+                       }
+                       $this->runtimeCache->set($cacheId, $pageTitle);
+               }
+               return $pageTitle;
+       }
+
+       /**
+        * Returns an instance of LanguageService
+        *
+        * @return \TYPO3\CMS\Lang\LanguageService
+        */
+       protected function getLanguageService() {
+               return $GLOBALS['LANG'];
+       }
+
+       /**
+        * Returns the current BE user.
+        *
+        * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
+        */
+       protected function getBackendUser() {
+               return $GLOBALS['BE_USER'];
+       }
+
+       /**
+        * Create and returns an instance of the CacheManager
+        *
+        * @return \TYPO3\CMS\Core\Cache\CacheManager
+        */
+       protected function getCacheManager() {
+               return GeneralUtility::makeInstance(\TYPO3\CMS\Core\Cache\CacheManager::class);
+       }
+
+       /**
+        * Gets an instance of the memory cache.
+        *
+        * @return \TYPO3\CMS\Core\Cache\Frontend\VariableFrontend
+        */
+       protected function getMemoryCache() {
+               return $this->getCacheManager()->getCache('cache_runtime');
        }
 
 }
\ No newline at end of file
index 00277e5..16e65ca 100644 (file)
@@ -14,7 +14,14 @@ namespace TYPO3\CMS\Recycler\Controller;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Core\Http\AjaxRequestHandler;
+use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
+use TYPO3\CMS\Fluid\View\StandaloneView;
+use TYPO3\CMS\Recycler\Domain\Model\Tables;
+use TYPO3\CMS\Recycler\Domain\Model\DeletedRecords;
+use TYPO3\CMS\Recycler\Controller\DeletedRecordsController;
 
 /**
  * Controller class for the 'recycler' extension. Handles the AJAX Requests
@@ -25,133 +32,149 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
 class RecyclerAjaxController {
 
        /**
-        * Stores the content for the ajax output
+        * The local configuration array
         *
-        * @var string
+        * @var array
         */
-       protected $content;
+       protected $conf = array();
 
        /**
-        * Command to be processed
-        *
-        * @var string
-        */
-       protected $command;
-
-       /**
-        * Stores relevant data from extJS
-        * Example: Json format
-        * [ ["pages",1],["pages",2],["tt_content",34] ]
-        *
-        * @var string
+        * The constructor of this class
         */
-       protected $data;
-
-       /**
-        * Initialize method
-        *
-        * @return void
-        */
-       public function init() {
-               $this->mapCommand();
-               $this->getContent();
+       public function __construct() {
+               // Configuration, variable assignment
+               $this->conf['action'] = GeneralUtility::_GP('action');
+               $this->conf['table'] = GeneralUtility::_GP('table') ? GeneralUtility::_GP('table') : '';
+               $this->conf['limit'] = GeneralUtility::_GP('limit') ? (int)GeneralUtility::_GP('limit') : 25;
+               $this->conf['start'] = GeneralUtility::_GP('start') ? (int)GeneralUtility::_GP('limit') : 0;
+               $this->conf['filterTxt'] = GeneralUtility::_GP('filterTxt') ? GeneralUtility::_GP('filterTxt') : '';
+               $this->conf['startUid'] = GeneralUtility::_GP('startUid') ? (int)GeneralUtility::_GP('startUid') : 0;
+               $this->conf['depth'] = GeneralUtility::_GP('depth') ? (int)GeneralUtility::_GP('depth') : 0;
+               $this->conf['records'] = GeneralUtility::_GP('records') ? GeneralUtility::_GP('records') : NULL;
+               $this->conf['recursive'] = GeneralUtility::_GP('recursive') ? (bool)(int)GeneralUtility::_GP('recursive') : FALSE;
        }
 
        /**
-        * Maps the command to the correct Model and View
+        * The main dispatcher function. Collect data and prepare HTML output.
         *
+        * @param array $params array of parameters from the AJAX interface, currently unused
+        * @param AjaxRequestHandler $ajaxObj object of type AjaxRequestHandler
         * @return void
         */
-       public function mapCommand() {
-               $this->command = GeneralUtility::_GP('cmd');
-               $this->data = GeneralUtility::_GP('data');
-               // check params
-               if (!is_string($this->command)) {
-                       // @TODO make devlog output
-                       return;
-               }
-               // Create content
-               $this->createContent();
-       }
+       public function dispatch($params = array(), AjaxRequestHandler $ajaxObj = NULL) {
+               $extPath = ExtensionManagementUtility::extPath('recycler');
+               $view = GeneralUtility::makeInstance(StandaloneView::class);
+               $view->setPartialRootPaths(array('default' => $extPath . 'Resources/Private/Partials'));
 
-       /**
-        * Creates the content
-        *
-        * @return void
-        */
-       protected function createContent() {
-               switch ($this->command) {
+               $content = '';
+               // Determine the scripts to execute
+               switch ($this->conf['action']) {
+                       case 'getTables':
+                               $this->setDataInSession('depthSelection', $this->conf['depth']);
+
+                               /* @var $model Tables */
+                               $model = GeneralUtility::makeInstance(Tables::class);
+                               $content = $model->getTables($this->conf['startUid'], $this->conf['depth']);
+                               break;
                        case 'getDeletedRecords':
-                               $table = GeneralUtility::_GP('table') ? GeneralUtility::_GP('table') : GeneralUtility::_GP('tableDefault');
-                               $limit = GeneralUtility::_GP('limit') ? (int)GeneralUtility::_GP('limit') : (int)GeneralUtility::_GP('pagingSizeDefault');
-                               $start = GeneralUtility::_GP('start') ? (int)GeneralUtility::_GP('start') : 0;
-                               $filter = GeneralUtility::_GP('filterTxt') ? GeneralUtility::_GP('filterTxt') : '';
-                               $startUid = GeneralUtility::_GP('startUid') ? GeneralUtility::_GP('startUid') : '';
-                               $depth = GeneralUtility::_GP('depth') ? GeneralUtility::_GP('depth') : '';
-                               $this->setDataInSession('tableSelection', $table);
-                               /* @var $model \TYPO3\CMS\Recycler\Domain\Model\DeletedRecords */
-                               $model = GeneralUtility::makeInstance(\TYPO3\CMS\Recycler\Domain\Model\DeletedRecords::class);
-                               $model->loadData($startUid, $table, $depth, $start . ',' . $limit, $filter);
+                               $this->setDataInSession('tableSelection', $this->conf['table']);
+                               $this->setDataInSession('depthSelection', $this->conf['depth']);
+                               $this->setDataInSession('resultLimit', $this->conf['limit']);
+
+                               /* @var $model DeletedRecords */
+                               $model = GeneralUtility::makeInstance(DeletedRecords::class);
+                               $model->loadData($this->conf['startUid'], $this->conf['table'], $this->conf['depth'], $this->conf['start'] . ',' . $this->conf['limit'], $this->conf['filterTxt']);
                                $deletedRowsArray = $model->getDeletedRows();
-                               $model = GeneralUtility::makeInstance(\TYPO3\CMS\Recycler\Domain\Model\DeletedRecords::class);
-                               $totalDeleted = $model->getTotalCount($startUid, $table, $depth, $filter);
-                               // load view
-                               /* @var $view \TYPO3\CMS\Recycler\Controller\DeletedRecordsController */
-                               $view = GeneralUtility::makeInstance(\TYPO3\CMS\Recycler\Controller\DeletedRecordsController::class);
-                               $str = $view->transform($deletedRowsArray, $totalDeleted);
+
+                               /* @var $model DeletedRecords */
+                               $model = GeneralUtility::makeInstance(DeletedRecords::class);
+                               $totalDeleted = $model->getTotalCount($this->conf['startUid'], $this->conf['table'], $this->conf['depth'], $this->conf['filter']);
+
+                               /* @var $view DeletedRecordsController */
+                               $controller = GeneralUtility::makeInstance(DeletedRecordsController::class);
+                               $recordsArray = $controller->transform($deletedRowsArray, $totalDeleted);
+
+                               $modTS = $this->getBackendUser()->getTSConfig('mod.recycler');
+                               $allowDelete = (bool)$this->getBackendUser()->user['admin'] ? TRUE : (bool)$modTS['properties']['allowDelete'];
+
+                               $view->setTemplatePathAndFilename($extPath . 'Resources/Private/Templates/Ajax/RecordsTable.html');
+                               $view->assign('records', $recordsArray['rows']);
+                               $view->assign('allowDelete', $allowDelete);
+                               $view->assign('total', $recordsArray['total']);
+                               $content = json_encode(array(
+                                       'rows' => $view->render(),
+                                       'totalItems' => $recordsArray['total']
+                               ));
                                break;
-                       case 'doDelete':
-                               $str = FALSE;
-                               /* @var $model \TYPO3\CMS\Recycler\Domain\Model\DeletedRecords */
-                               $model = GeneralUtility::makeInstance(\TYPO3\CMS\Recycler\Domain\Model\DeletedRecords::class);
-                               if ($model->deleteData($this->data)) {
-                                       $str = TRUE;
+                       case 'undoRecords':
+                               if (empty($this->conf['records']) || !is_array($this->conf['records'])) {
+                                       $content = json_encode(array(
+                                               'success' => FALSE,
+                                               'message' => LocalizationUtility::translate('flashmessage.delete.norecordsselected', 'recycler')
+                                       ));
+                                       break;
                                }
+
+                               /* @var $model DeletedRecords */
+                               $model = GeneralUtility::makeInstance(DeletedRecords::class);
+                               $success = $model->undeleteData($this->conf['records'], $this->conf['recursive']);
+                               $affectedRecords = count($this->conf['records']);
+                               $messageKey = 'flashmessage.undo.' . ($success ? 'success' : 'failure') . '.' . ($affectedRecords === 1 ? 'singular' : 'plural');
+                               $content = json_encode(array(
+                                       'success' => TRUE,
+                                       'message' => sprintf(LocalizationUtility::translate($messageKey, 'recycler'), $affectedRecords)
+                               ));
                                break;
-                       case 'doUndelete':
-                               $str = FALSE;
-                               $recursive = GeneralUtility::_GP('recursive');
-                               /* @var $model \TYPO3\CMS\Recycler\Domain\Model\DeletedRecords */
-                               $model = GeneralUtility::makeInstance(\TYPO3\CMS\Recycler\Domain\Model\DeletedRecords::class);
-                               if ($model->undeleteData($this->data, $recursive)) {
-                                       $str = TRUE;
+                       case 'deleteRecords':
+                               if (empty($this->conf['records']) || !is_array($this->conf['records'])) {
+                                       $content = json_encode(array(
+                                               'success' => FALSE,
+                                               'message' => LocalizationUtility::translate('flashmessage.delete.norecordsselected', 'recycler')
+                                       ));
+                                       break;
                                }
+
+                               /* @var $model DeletedRecords */
+                               $model = GeneralUtility::makeInstance(DeletedRecords::class);
+                               $success = $model->deleteData($this->conf['records']);
+                               $affectedRecords = count($this->conf['records']);
+                               $messageKey = 'flashmessage.delete.' . ($success ? 'success' : 'failure') . '.' . ($affectedRecords === 1 ? 'singular' : 'plural');
+                               $content = json_encode(array(
+                                       'success' => TRUE,
+                                       'message' => sprintf(LocalizationUtility::translate($messageKey, 'recycler'), $affectedRecords)
+                               ));
                                break;
-                       case 'getTables':
-                               $depth = GeneralUtility::_GP('depth') ? GeneralUtility::_GP('depth') : 0;
-                               $startUid = GeneralUtility::_GP('startUid') ? GeneralUtility::_GP('startUid') : '';
-                               $this->setDataInSession('depthSelection', $depth);
-                               /* @var $model \TYPO3\CMS\Recycler\Domain\Model\Tables */
-                               $model = GeneralUtility::makeInstance(\TYPO3\CMS\Recycler\Domain\Model\Tables::class);
-                               $str = $model->getTables('json', TRUE, $startUid, $depth);
-                               break;
-                       default:
-                               $str = 'No command was recognized.';
                }
-               $this->content = $str;
-       }
-
-       /**
-        * Returns the content that was created in the mapCommand method
-        *
-        * @return string
-        */
-       public function getContent() {
-               echo $this->content;
+               $ajaxObj->addContent($this->conf['table'] . '_' . $this->conf['start'], $content);
        }
 
        /**
         * Sets data in the session of the current backend user.
         *
-        * @param string $identifier: The identifier to be used to set the data
-        * @param string $data: The data to be stored in the session
+        * @param string $identifier The identifier to be used to set the data
+        * @param string $data The data to be stored in the session
         * @return void
         */
        protected function setDataInSession($identifier, $data) {
-               /* @var $beUser \TYPO3\CMS\Core\Authentication\BackendUserAuthentication */
-               $beUser = $GLOBALS['BE_USER'];
+               $beUser = $this->getBackendUser();
                $beUser->uc['tx_recycler'][$identifier] = $data;
                $beUser->writeUC();
        }
 
+       /**
+        * Returns the BackendUser
+        *
+        * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
+        */
+       protected function getBackendUser() {
+               return $GLOBALS['BE_USER'];
+       }
+
+       /**
+        * @return \TYPO3\CMS\Lang\LanguageService
+        */
+       protected function getLanguageService() {
+               return $GLOBALS['LANG'];
+       }
+
 }
index 6c2cf34..13bd3a5 100644 (file)
@@ -14,26 +14,22 @@ namespace TYPO3\CMS\Recycler\Controller;
  * The TYPO3 project - inspiring people to share!
  */
 
-use TYPO3\CMS\Backend\Utility\BackendUtility;
-use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
  * Module 'Recycler' for the 'recycler' extension.
- *
- * @author Julian Kleinhans <typo3@kj187.de>
  */
-class RecyclerModuleController extends \TYPO3\CMS\Backend\Module\BaseScriptClass {
+class RecyclerModuleController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionController {
 
        /**
-        * @var \TYPO3\CMS\Backend\Template\DocumentTemplate
+        * @var string
         */
-       public $doc;
+       protected $relativePath;
 
        /**
         * @var string
         */
-       protected $relativePath;
+       public $perms_clause;
 
        /**
         * @var array
@@ -56,69 +52,32 @@ class RecyclerModuleController extends \TYPO3\CMS\Backend\Module\BaseScriptClass
        protected $recordsPageLimit = 50;
 
        /**
-        * @var \TYPO3\CMS\Core\Page\PageRenderer
-        */
-       protected $pageRenderer;
-
-       /**
-        * @var \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
-        */
-       protected $backendUser;
-
-       /**
-        * @var \TYPO3\CMS\Lang\LanguageService
-        */
-       protected $languageService;
-
-       /**
-        * The name of the module
-        *
-        * @var string
-        */
-       protected $moduleName = 'web_txrecyclerM1';
-
-       /**
-        * Constructor
-        */
-       public function __construct() {
-               $this->languageService = $GLOBALS['LANG'];
-               $this->languageService->includeLLFile('EXT:recycler/mod1/locallang.xlf');
-
-               $this->backendUser = $GLOBALS['BE_USER'];
-
-               $this->MCONF = array(
-                       'name' => $this->moduleName,
-               );
-       }
-
-       /**
         * Initializes the Module
         *
         * @return void
         */
-       public function initialize() {
-               parent::init();
-               $this->doc = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Template\DocumentTemplate::class);
-               $this->doc->setModuleTemplate(ExtensionManagementUtility::extPath('recycler') . 'mod1/mod_template.html');
-               $this->doc->backPath = $GLOBALS['BACK_PATH'];
-               $this->doc->setExtDirectStateProvider();
-               $this->pageRenderer = $this->doc->getPageRenderer();
-               $this->relativePath = ExtensionManagementUtility::extRelPath('recycler');
-               $this->pageRecord = BackendUtility::readPageAccess($this->id, $this->perms_clause);
+       public function initializeAction() {
+               $this->id = GeneralUtility::_GP('id');
+               $backendUser = $this->getBackendUser();
+               $this->perms_clause = $backendUser->getPagePermsClause(1);
+               $this->pageRecord = \TYPO3\CMS\Backend\Utility\BackendUtility::readPageAccess($this->id, $this->perms_clause);
                $this->isAccessibleForCurrentUser = $this->id && is_array($this->pageRecord) || !$this->id && $this->isCurrentUserAdmin();
-               //don't access in workspace
-               if ($this->backendUser->workspace !== 0) {
+
+               // don't access in workspace
+               if ($backendUser->workspace !== 0) {
                        $this->isAccessibleForCurrentUser = FALSE;
                }
-               //read configuration
-               $modTS = $this->backendUser->getTSConfig('mod.recycler');
+
+               // read configuration
+               $modTS = $backendUser->getTSConfig('mod.recycler');
                if ($this->isCurrentUserAdmin()) {
                        $this->allowDelete = TRUE;
                } else {
-                       $this->allowDelete = $modTS['properties']['allowDelete'] == '1';
+                       $this->allowDelete = (bool)$modTS['properties']['allowDelete'];
                }
-               if (isset($modTS['properties']['recordsPageLimit']) && (int)$modTS['properties']['recordsPageLimit'] > 0) {
-                       $this->recordsPageLimit = (int)$modTS['properties']['recordsPageLimit'];
+
+               if (isset($modTS['properties']['recordsPageLimit']) && intval($modTS['properties']['recordsPageLimit']) > 0) {
+                       $this->recordsPageLimit = intval($modTS['properties']['recordsPageLimit']);
                }
        }
 
@@ -127,31 +86,15 @@ class RecyclerModuleController extends \TYPO3\CMS\Backend\Module\BaseScriptClass
         *
         * @return void
         */
-       public function render() {
-               $this->content .= $this->doc->header($this->languageService->getLL('title'));
-               $this->content .= '<p class="lead">' . $this->languageService->getLL('description') . '</p>';
-               if ($this->isAccessibleForCurrentUser) {
-                       $this->loadHeaderData();
-                       // div container for renderTo
-                       $this->content .= '<div id="recyclerContent"></div>';
-               } else {
-                       // If no access or if ID == zero
-                       $this->content .= $this->doc->spacer(10);
-               }
-       }
+       public function indexAction() {
+               // Integrate dynamic JavaScript such as configuration or lables:
+               $jsConfiguration = $this->getJavaScriptConfiguration();
+        $this->getPageRenderer()->addInlineSettingArray('Recycler', $jsConfiguration);
+               $this->getPageRenderer()->addInlineLanguageLabelFile('EXT:recycler/Resources/Private/Language/locallang.xlf');
 
-       /**
-        * Flushes the rendered content to browser.
-        *
-        * @return void
-        */
-       public function flush() {
-               $content = $this->doc->moduleBody($this->pageRecord, $this->getDocHeaderButtons(), $this->getTemplateMarkers());
-               // Renders the module page
-               $content = $this->doc->render($this->languageService->getLL('title'), $content);
-               $this->content = NULL;
-               $this->doc = NULL;
-               echo $content;
+               $this->view->assign('title', $this->getLanguageService()->getLL('title'));
+               $this->view->assign('content', $this->content);
+               $this->view->assign('allowDelete', $this->allowDelete);
        }
 
        /**
@@ -160,31 +103,7 @@ class RecyclerModuleController extends \TYPO3\CMS\Backend\Module\BaseScriptClass
         * @return bool Whether the current user is admin
         */
        protected function isCurrentUserAdmin() {
-               return (bool)$this->backendUser->user['admin'];
-       }
-
-       /**
-        * Loads data in the HTML head section (e.g. JavaScript or stylesheet information).
-        *
-        * @return void
-        */
-       protected function loadHeaderData() {
-               // Load CSS Stylesheets:
-               $this->pageRenderer->addCssFile($this->relativePath . 'res/css/customExtJs.css');
-               // Load Ext JS:
-               $this->pageRenderer->loadExtJS();
-               $this->pageRenderer->enableExtJSQuickTips();
-               // Integrate dynamic JavaScript such as configuration or lables:
-               $this->pageRenderer->addInlineSettingArray('Recycler', $this->getJavaScriptConfiguration());
-               $this->pageRenderer->addInlineLanguageLabelArray($this->getJavaScriptLabels());
-               // Load Recycler JavaScript:
-               // Load Plugins
-               $uxPath = $this->doc->backPath . 'js/extjs/ux/';
-               $this->pageRenderer->addJsFile($uxPath . 'Ext.grid.RowExpander.js');
-               $this->pageRenderer->addJsFile($uxPath . 'Ext.app.SearchField.js');
-               $this->pageRenderer->addJsFile($uxPath . 'Ext.ux.FitToParent.js');
-               // Load main script
-               $this->pageRenderer->addJsFile($this->relativePath . 'res/js/t3_recycler.js');
+               return (bool)$this->getBackendUser()->user['admin'];
        }
 
        /**
@@ -196,108 +115,68 @@ class RecyclerModuleController extends \TYPO3\CMS\Backend\Module\BaseScriptClass
                $configuration = array(
                        'pagingSize' => $this->recordsPageLimit,
                        'showDepthMenu' => 1,
-                       'startUid' => $this->id,
-                       'tableDefault' => 'pages',
-                       'renderTo' => 'recyclerContent',
+                       'startUid' => (int)GeneralUtility::_GP('id'),
                        'isSSL' => GeneralUtility::getIndpEnv('TYPO3_SSL'),
-                       'deleteDisable' => $this->allowDelete ? 0 : 1,
+                       'deleteDisable' => !$this->allowDelete,
                        'depthSelection' => $this->getDataFromSession('depthSelection', 0),
-                       'tableSelection' => $this->getDataFromSession('tableSelection', 'pages'),
-                       'States' => $this->backendUser->uc['moduleData']['web_recycler']['States']
+                       'tableSelection' => $this->getDataFromSession('tableSelection', ''),
+                       'States' => $this->getBackendUser()->uc['moduleData']['web_recycler']['States']
                );
                return $configuration;
        }
 
        /**
-        * Gets the labels to be used in JavaScript in the Ext JS interface.
-        *
-        * @return array The labels to be used in JavaScript
-        */
-       protected function getJavaScriptLabels() {
-               $coreLabels = array(
-                       'title' => $this->languageService->getLL('title'),
-                       'path' => $this->languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.path'),
-                       'table' => $this->languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.table'),
-                       'depth' => $this->languageService->sL('LLL:EXT:lang/locallang_mod_web_perm.xlf:Depth'),
-                       'depth_0' => $this->languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.depth_0'),
-                       'depth_1' => $this->languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.depth_1'),
-                       'depth_2' => $this->languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.depth_2'),
-                       'depth_3' => $this->languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.depth_3'),
-                       'depth_4' => $this->languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.depth_4'),
-                       'depth_infi' => $this->languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.depth_infi')
-               );
-               $extensionLabels = $this->languageService->getLabelsWithPrefix('js.', 'label_');
-               $javaScriptLabels = array_merge($coreLabels, $extensionLabels);
-               return $javaScriptLabels;
-       }
-
-       /**
-        * Gets the buttons that shall be rendered in the docHeader.
+        * Gets data from the session of the current backend user.
         *
-        * @return array Available buttons for the docHeader
+        * @param string $identifier The identifier to be used to get the data
+        * @param string $default The default date to be used if nothing was found in the session
+        * @return string The accordant data in the session of the current backend user
         */
-       protected function getDocHeaderButtons() {
-               $buttons = array(
-                       'csh' => BackendUtility::cshItem('_MOD_web_func', ''),
-                       'shortcut' => $this->getShortcutButton(),
-                       'save' => ''
-               );
-               // SAVE button
-               $buttons['save'] = '';
-               return $buttons;
+       protected function getDataFromSession($identifier, $default = NULL) {
+               $sessionData = &$this->getBackendUser()->uc['tx_recycler'];
+               if (isset($sessionData[$identifier]) && $sessionData[$identifier]) {
+                       $data = $sessionData[$identifier];
+               } else {
+                       $data = $default;
+               }
+               return $data;
        }
 
        /**
-        * Gets the button to set a new shortcut in the backend (if current user is allowed to).
+        * Returns the current BE user.
         *
-        * @return string HTML representation of the shortcut button
+        * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
         */
-       protected function getShortcutButton() {
-               $result = '';
-               if ($this->backendUser->mayMakeShortcut()) {
-                       $result = $this->doc->makeShortcutIcon('', 'function', $this->moduleName);
-               }
-               return $result;
+       protected function getBackendUser() {
+               return $GLOBALS['BE_USER'];
        }
 
        /**
-        * Gets the filled markers that are used in the HTML template.
+        * Returns an instance of DocumentTemplate
         *
-        * @return array The filled marker array
+        * @return TYPO3\CMS\Backend\Template\DocumentTemplate
         */
-       protected function getTemplateMarkers() {
-               $markers = array(
-                       'FUNC_MENU' => $this->getFunctionMenu(),
-                       'CONTENT' => $this->content,
-                       'TITLE' => $this->languageService->getLL('title')
-               );
-               return $markers;
+       protected function getDocumentTemplate() {
+               return $GLOBALS['TBE_TEMPLATE'];
        }
 
        /**
-        * Gets the function menu selector for this backend module.
+        * Returns an instance of LanguageService
         *
-        * @return string The HTML representation of the function menu selector
+        * @return \TYPO3\CMS\Lang\LanguageService
         */
-       protected function getFunctionMenu() {
-               return BackendUtility::getFuncMenu(0, 'SET[function]', $this->MOD_SETTINGS['function'], $this->MOD_MENU['function']);
+       protected function getLanguageService() {
+               return $GLOBALS['LANG'];
        }
 
        /**
-        * Gets data from the session of the current backend user.
+        * Returns current PageRenderer
         *
-        * @param string $identifier The identifier to be used to get the data
-        * @param string $default The default date to be used if nothing was found in the session
-        * @return string The accordant data in the session of the current backend user
+        * @return \TYPO3\CMS\Core\Page\PageRenderer
         */
-       protected function getDataFromSession($identifier, $default = NULL) {
-               $sessionData = &$this->backendUser->uc['tx_recycler'];
-               if (isset($sessionData[$identifier]) && $sessionData[$identifier]) {
-                       $data = $sessionData[$identifier];
-               } else {
-                       $data = $default;
-               }
-               return $data;
+       protected function getPageRenderer() {
+               /** @var  \TYPO3\CMS\Backend\Template\DocumentTemplate $documentTemplate */
+               $documentTemplate = $GLOBALS['TBE_TEMPLATE'];
+               return $documentTemplate->getPageRenderer();
        }
-
 }
index 2820d08..3cc62b2 100644 (file)
@@ -14,6 +14,7 @@ namespace TYPO3\CMS\Recycler\Domain\Model;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Core\DataHandling\DataHandler;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Recycler\Utility\RecyclerUtility;
 
@@ -66,20 +67,6 @@ class DeletedRecords {
         */
        public $title;
 
-       /**
-        * Database Connection
-        *
-        * @var \TYPO3\CMS\Core\Database\DatabaseConnection
-        */
-       protected $databaseConnection;
-
-       /**
-        * Constructor
-        */
-       public function __construct() {
-               $this->databaseConnection = $GLOBALS['TYPO3_DB'];
-       }
-
        /************************************************************
         * GET DATA FUNCTIONS
         *
@@ -100,17 +87,17 @@ class DeletedRecords {
                // set the limit
                $this->limit = trim($limit);
                if ($table) {
-                       if (array_key_exists($table, $GLOBALS['TCA'])) {
+                       if (in_array($table, RecyclerUtility::getModifyableTables())) {
                                $this->table[] = $table;
                                $this->setData($id, $table, $depth, $GLOBALS['TCA'][$table]['ctrl'], $filter);
                        }
                } else {
                        foreach ($GLOBALS['TCA'] as $tableKey => $tableValue) {
                                // only go into this table if the limit allows it
-                               if ($this->limit != '') {
+                               if ($this->limit !== '') {
                                        $parts = GeneralUtility::trimExplode(',', $this->limit);
                                        // abort loop if LIMIT 0,0
-                                       if ($parts[0] == 0 && $parts[1] == 0) {
+                                       if ((int)$parts[0] === 0 && (int)$parts[1] === 0) {
                                                break;
                                        }
                                }
@@ -149,111 +136,113 @@ class DeletedRecords {
         * @param string $filter Filter text
         * @return void
         */
-       protected function setData($id = 0, $table, $depth, $tcaCtrl, $filter) {
+       protected function setData($id, $table, $depth, $tcaCtrl, $filter) {
                $id = (int)$id;
-               if (array_key_exists('delete', $tcaCtrl)) {
-                       // find the 'deleted' field for this table
-                       $deletedField = RecyclerUtility::getDeletedField($table);
-                       // create the filter WHERE-clause
-                       $filterWhere = '';
-                       if (trim($filter) != '') {
-                               $filterWhere = ' AND (' . (\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($filter) ? 'uid = ' . $filter . ' OR pid = ' . $filter . ' OR ' : '') . $tcaCtrl['label'] . ' LIKE "%' . $this->escapeValueForLike($filter, $table) . '%"' . ')';
-                       }
+               if (!array_key_exists('delete', $tcaCtrl)) {
+                       return;
+               }
+               $db = $this->getDatabaseConnection();
+               // find the 'deleted' field for this table
+               $deletedField = RecyclerUtility::getDeletedField($table);
+               // create the filter WHERE-clause
+               $filterWhere = '';
+               if (trim($filter) != '') {
+                       $filterWhere = ' AND (' . (\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($filter) ? 'uid = ' . $filter . ' OR pid = ' . $filter . ' OR ' : '') . $tcaCtrl['label'] . ' LIKE "%' . $this->escapeValueForLike($filter, $table) . '%"' . ')';
+               }
 
-                       // get the limit
-                       if ($this->limit != '') {
-                               // count the number of deleted records for this pid
-                               $deletedCount = $this->databaseConnection->exec_SELECTcountRows('uid', $table, $deletedField . '<>0 AND pid = ' . $id . $filterWhere);
-                               // split the limit
-                               $parts = GeneralUtility::trimExplode(',', $this->limit);
-                               $offset = $parts[0];
-                               $rowCount = $parts[1];
-                               // subtract the number of deleted records from the limit's offset
-                               $result = $offset - $deletedCount;
-                               // if the result is >= 0
-                               if ($result >= 0) {
-                                       // store the new offset in the limit and go into the next depth
-                                       $offset = $result;
-                                       $this->limit = implode(',', array($offset, $rowCount));
-                                       // do NOT query this depth; limit also does not need to be set, we set it anyways
-                                       $allowQuery = FALSE;
-                                       $allowDepth = TRUE;
-                                       $limit = '';
+               // get the limit
+               if (!empty($this->limit)) {
+                       // count the number of deleted records for this pid
+                       $deletedCount = $db->exec_SELECTcountRows('uid', $table, $deletedField . '<>0 AND pid = ' . $id . $filterWhere);
+                       // split the limit
+                       $parts = GeneralUtility::trimExplode(',', $this->limit);
+                       $offset = $parts[0];
+                       $rowCount = $parts[1];
+                       // subtract the number of deleted records from the limit's offset
+                       $result = $offset - $deletedCount;
+                       // if the result is >= 0
+                       if ($result >= 0) {
+                               // store the new offset in the limit and go into the next depth
+                               $offset = $result;
+                               $this->limit = implode(',', array($offset, $rowCount));
+                               // do NOT query this depth; limit also does not need to be set, we set it anyways
+                               $allowQuery = FALSE;
+                               $allowDepth = TRUE;
+                               $limit = '';
+                       } else {
+                               // the offset for the temporary limit has to remain like the original offset
+                               // in case the original offset was just crossed by the amount of deleted records
+                               if ($offset !== 0) {
+                                       $tempOffset = $offset;
+                               } else {
+                                       $tempOffset = 0;
+                               }
+                               // set the offset in the limit to 0
+                               $newOffset = 0;
+                               // convert to negative result to the positive equivalent
+                               $absResult = abs($result);
+                               // if the result now is > limit's row count
+                               if ($absResult > $rowCount) {
+                                       // use the limit's row count as the temporary limit
+                                       $limit = implode(',', array($tempOffset, $rowCount));
+                                       // set the limit's row count to 0
+                                       $this->limit = implode(',', array($newOffset, 0));
+                                       // do not go into new depth
+                                       $allowDepth = FALSE;
                                } else {
-                                       // the offset for the temporary limit has to remain like the original offset
-                                       // in case the original offset was just crossed by the amount of deleted records
-                                       if ($offset != 0) {
-                                               $tempOffset = $offset;
+                                       // if the result now is <= limit's row count
+                                       // use the result as the temporary limit
+                                       $limit = implode(',', array($tempOffset, $absResult));
+                                       // subtract the result from the row count
+                                       $newCount = $rowCount - $absResult;
+                                       // store the new result in the limit's row count
+                                       $this->limit = implode(',', array($newOffset, $newCount));
+                                       // if the new row count is > 0
+                                       if ($newCount > 0) {
+                                               // go into new depth
+                                               $allowDepth = TRUE;
                                        } else {
-                                               $tempOffset = 0;
-                                       }
-                                       // set the offset in the limit to 0
-                                       $newOffset = 0;
-                                       // convert to negative result to the positive equivalent
-                                       $absResult = abs($result);
-                                       // if the result now is > limit's row count
-                                       if ($absResult > $rowCount) {
-                                               // use the limit's row count as the temporary limit
-                                               $limit = implode(',', array($tempOffset, $rowCount));
-                                               // set the limit's row count to 0
-                                               $this->limit = implode(',', array($newOffset, 0));
+                                               // if the new row count is <= 0 (only =0 makes sense though)
                                                // do not go into new depth
                                                $allowDepth = FALSE;
-                                       } else {
-                                               // if the result now is <= limit's row count
-                                               // use the result as the temporary limit
-                                               $limit = implode(',', array($tempOffset, $absResult));
-                                               // subtract the result from the row count
-                                               $newCount = $rowCount - $absResult;
-                                               // store the new result in the limit's row count
-                                               $this->limit = implode(',', array($newOffset, $newCount));
-                                               // if the new row count is > 0
-                                               if ($newCount > 0) {
-                                                       // go into new depth
-                                                       $allowDepth = TRUE;
-                                               } else {
-                                                       // if the new row count is <= 0 (only =0 makes sense though)
-                                                       // do not go into new depth
-                                                       $allowDepth = FALSE;
-                                               }
                                        }
-                                       // allow query for this depth
-                                       $allowQuery = TRUE;
                                }
-                       } else {
-                               $limit = '';
-                               $allowDepth = TRUE;
+                               // allow query for this depth
                                $allowQuery = TRUE;
                        }
-                       // query for actual deleted records
-                       if ($allowQuery) {
-                               $recordsToCheck = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecordsByField($table, $deletedField, '1', ' AND pid = ' . $id . $filterWhere, '', '', $limit, FALSE);
-                               if ($recordsToCheck) {
-                                       $this->checkRecordAccess($table, $recordsToCheck);
-                               }
+               } else {
+                       $limit = '';
+                       $allowDepth = TRUE;
+                       $allowQuery = TRUE;
+               }
+               // query for actual deleted records
+               if ($allowQuery) {
+                       $recordsToCheck = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecordsByField($table, $deletedField, '1', ' AND pid = ' . $id . $filterWhere, '', '', $limit, FALSE);
+                       if ($recordsToCheck) {
+                               $this->checkRecordAccess($table, $recordsToCheck);
                        }
-                       // go into depth
-                       if ($allowDepth && $depth >= 1) {
-                               // check recursively for elements beneath this page
-                               $resPages = $this->databaseConnection->exec_SELECTquery('uid', 'pages', 'pid=' . $id, '', 'sorting');
-                               if ($resPages) {
-                                       while ($rowPages = $this->databaseConnection->sql_fetch_assoc($resPages)) {
-                                               $this->setData($rowPages['uid'], $table, $depth - 1, $tcaCtrl, $filter);
-                                               // some records might have been added, check if we still have the limit for further queries
-                                               if ('' != $this->limit) {
-                                                       $parts = GeneralUtility::trimExplode(',', $this->limit);
-                                                       // abort loop if LIMIT 0,0
-                                                       if ($parts[0] == 0 && $parts[1] == 0) {
-                                                               break;
-                                                       }
+               }
+               // go into depth
+               if ($allowDepth && $depth >= 1) {
+                       // check recursively for elements beneath this page
+                       $resPages = $db->exec_SELECTquery('uid', 'pages', 'pid=' . $id, '', 'sorting');
+                       if ($resPages) {
+                               while ($rowPages = $db->sql_fetch_assoc($resPages)) {
+                                       $this->setData($rowPages['uid'], $table, $depth - 1, $tcaCtrl, $filter);
+                                       // some records might have been added, check if we still have the limit for further queries
+                                       if (!empty($this->limit)) {
+                                               $parts = GeneralUtility::trimExplode(',', $this->limit);
+                                               // abort loop if LIMIT 0,0
+                                               if ((int)$parts[0] === 0 && (int)$parts[1] === 0) {
+                                                       break;
                                                }
                                        }
-                                       $this->databaseConnection->sql_free_result($resPages);
                                }
+                               $db->sql_free_result($resPages);
                        }
-                       $this->label[$table] = $tcaCtrl['label'];
-                       $this->title[$table] = $tcaCtrl['title'];
                }
+               $this->label[$table] = $tcaCtrl['label'];
+               $this->title[$table] = $tcaCtrl['title'];
        }
 
        /**
@@ -264,7 +253,7 @@ class DeletedRecords {
         * @return void
         */
        protected function checkRecordAccess($table, array $rows) {
-               foreach ($rows as $key => $row) {
+               foreach ($rows as $row) {
                        if (RecyclerUtility::checkAccess($table, $row)) {
                                $this->setDeletedRows($table, $row);
                        }
@@ -280,7 +269,8 @@ class DeletedRecords {
         * @return string The escaped value to be used for like conditions
         */
        protected function escapeValueForLike($value, $tableName) {
-               return $this->databaseConnection->escapeStrForLike($this->databaseConnection->quoteStr($value, $tableName), $tableName);
+               $db = $this->getDatabaseConnection();
+               return $db->escapeStrForLike($db->quoteStr($value, $tableName), $tableName);
        }
 
        /************************************************************
@@ -293,13 +283,14 @@ class DeletedRecords {
         * @return bool
         */
        public function deleteData($recordsArray) {
-               $recordsArray = json_decode($recordsArray);
                if (is_array($recordsArray)) {
-                       $tce = GeneralUtility::makeInstance(\TYPO3\CMS\Core\DataHandling\DataHandler::class);
+                       /** @var $tce DataHandler **/
+                       $tce = GeneralUtility::makeInstance(DataHandler::class);
                        $tce->start('', '');
                        $tce->disableDeleteClause();
-                       foreach ($recordsArray as $key => $record) {
-                               $tce->deleteEl($record[0], $record[1], TRUE, TRUE);
+                       foreach ($recordsArray as $record) {
+                               list($table, $uid) = explode(':', $record);
+                               $tce->deleteEl($table, (int)$uid, TRUE, TRUE);
                        }
                        return TRUE;
                }
@@ -320,26 +311,26 @@ class DeletedRecords {
        public function undeleteData($recordsArray, $recursive = FALSE) {
                $result = FALSE;
                $depth = 999;
-               $recordsArray = json_decode($recordsArray);
                if (is_array($recordsArray)) {
                        $this->deletedRows = array();
                        $cmd = array();
-                       foreach ($recordsArray as $key => $row) {
-                               $cmd[$row[0]][$row[1]]['undelete'] = 1;
-                               if ($row[0] == 'pages' && $recursive == TRUE) {
-                                       $this->loadData($row[1], '', $depth, '');
+                       foreach ($recordsArray as $record) {
+                               list($table, $uid) = explode(':', $record);
+                               $cmd[$table][$uid]['undelete'] = 1;
+                               if ($table === 'pages' && $recursive) {
+                                       $this->loadData($uid, '', $depth, '');
                                        $childRecords = $this->getDeletedRows();
                                        if (count($childRecords) > 0) {
-                                               foreach ($childRecords as $table => $childRows) {
-                                                       foreach ($childRows as $childKey => $childRow) {
-                                                               $cmd[$table][$childRow['uid']]['undelete'] = 1;
+                                               foreach ($childRecords as $childTable => $childRows) {
+                                                       foreach ($childRows as $childRow) {
+                                                               $cmd[$childTable][$childRow['uid']]['undelete'] = 1;
                                                        }
                                                }
                                        }
                                }
                        }
                        if ($cmd) {
-                               $tce = GeneralUtility::makeInstance(\TYPO3\CMS\Core\DataHandling\DataHandler::class);
+                               $tce = GeneralUtility::makeInstance(DataHandler::class);
                                $tce->start(array(), $cmd);
                                $tce->process_cmdmap();
                                $result = TRUE;
@@ -383,4 +374,12 @@ class DeletedRecords {
                return $this->table;
        }
 
+       /**
+        * Returns an instance of DatabaseConnection
+        *
+        * @return \TYPO3\CMS\Core\Database\DatabaseConnection
+        */
+       protected function getDatabaseConnection() {
+               return $GLOBALS['TYPO3_DB'];
+       }
 }
index 59429a5..827d9a1 100644 (file)
@@ -24,42 +24,21 @@ use TYPO3\CMS\Recycler\Utility\RecyclerUtility;
 class Tables {
 
        /**
-        * @var \TYPO3\CMS\Lang\LanguageService
-        */
-       protected $languageService;
-
-       /**
-        * Database Connection
-        *
-        * @var \TYPO3\CMS\Core\Database\DatabaseConnection
-        */
-       protected $databaseConnection;
-
-       /**
-        * Constructor
-        */
-       public function __construct() {
-               $this->languageService = $GLOBALS['LANG'];
-               $this->databaseConnection = $GLOBALS['TYPO3_DB'];
-       }
-
-       /**
         * Get tables for menu example
         *
-        * @param string $format Return format (example: json) - currently unused
-        * @param bool $withAllOption FALSE: no, TRUE: return tables with a "all" option
         * @param int $startUid UID from selected page
         * @param int $depth How many levels recursive
         * @return string The tables to be displayed
         */
-       public function getTables($format, $withAllOption = TRUE, $startUid, $depth = 0) {
+       public function getTables($startUid, $depth = 0) {
                $deletedRecordsTotal = 0;
+               $lang = $this->getLanguageService();
                $tables = array();
-               foreach ($GLOBALS['TCA'] as $tableName => $_) {
+               foreach (RecyclerUtility::getModifyableTables() as $tableName) {
                        $deletedField = RecyclerUtility::getDeletedField($tableName);
                        if ($deletedField) {
                                // Determine whether the table has deleted records:
-                               $deletedCount = $this->databaseConnection->exec_SELECTcountRows('uid', $tableName, $deletedField . '<>0');
+                               $deletedCount = $this->getDatabaseConnection()->exec_SELECTcountRows('uid', $tableName, $deletedField . '<>0');
                                if ($deletedCount) {
                                        /* @var $deletedDataObject \TYPO3\CMS\Recycler\Domain\Model\DeletedRecords */
                                        $deletedDataObject = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Recycler\Domain\Model\DeletedRecords::class);
@@ -70,8 +49,7 @@ class Tables {
                                                        $tables[] = array(
                                                                $tableName,
                                                                $deletedRecordsInTable,
-                                                               $tableName,
-                                                               RecyclerUtility::getUtf8String($this->languageService->sL($GLOBALS['TCA'][$tableName]['ctrl']['title']))
+                                                               RecyclerUtility::getUtf8String($lang->sL($GLOBALS['TCA'][$tableName]['ctrl']['title']))
                                                        );
                                                }
                                        }
@@ -79,16 +57,31 @@ class Tables {
                        }
                }
                $jsonArray = $tables;
-               if ($withAllOption) {
-                       array_unshift($jsonArray, array(
-                               '',
-                               $deletedRecordsTotal,
-                               '',
-                               $this->languageService->sL('LLL:EXT:recycler/mod1/locallang.xlf:label_alltables')
-                       ));
-               }
+               array_unshift($jsonArray, array(
+                       '',
+                       $deletedRecordsTotal,
+                       $lang->sL('LLL:EXT:recycler/mod1/locallang.xlf:label_alltables')
+               ));
                $output = json_encode($jsonArray);
                return $output;
        }
 
+       /**
+        * Returns an instance of DatabaseConnection
+        *
+        * @return \TYPO3\CMS\Core\Database\DatabaseConnection
+        */
+       protected function getDatabaseConnection() {
+               return $GLOBALS['TYPO3_DB'];
+       }
+
+       /**
+        * Returns an instance of LanguageService
+        *
+        * @return \TYPO3\CMS\Lang\LanguageService
+        */
+       protected function getLanguageService() {
+               return $GLOBALS['LANG'];
+       }
+
 }
index 8848c8c..49b1462 100644 (file)
@@ -38,8 +38,7 @@ class RecyclerUtility {
         * @return bool Returns TRUE is the user has access, or FALSE if not
         */
        static public function checkAccess($table, $row) {
-               /* @var $backendUser \TYPO3\CMS\Core\Authentication\BackendUserAuthentication */
-               $backendUser = $GLOBALS['BE_USER'];
+               $backendUser = static::getBackendUser();
 
                // Checking if the user has permissions? (Only working as a precaution, because the final permission check is always down in TCE. But it's good to notify the user on beforehand...)
                // First, resetting flags.
@@ -47,7 +46,7 @@ class RecyclerUtility {
                $calcPRec = $row;
                BackendUtility::fixVersioningPid($table, $calcPRec);
                if (is_array($calcPRec)) {
-                       if ($table == 'pages') {
+                       if ($table === 'pages') {
                                // If pages:
                                $calculatedPermissions = $backendUser->calcPerms($calcPRec);
                                $hasAccess = $calculatedPermissions & 2 ? TRUE : FALSE;
@@ -79,24 +78,27 @@ class RecyclerUtility {
         * @return mixed Path of record (string) OR array with short/long title if $fullTitleLimit is set.
         */
        static public function getRecordPath($uid, $clause = '', $titleLimit = 1000, $fullTitleLimit = 0) {
-               /* @var $databaseConnection \TYPO3\CMS\Core\Database\DatabaseConnection */
-               $databaseConnection = $GLOBALS['TYPO3_DB'];
-
-               $loopCheck = 100;
+               $uid = (int)$uid;
                $output = ($fullOutput = '/');
-               while ($uid != 0 && $loopCheck > 0) {
+               if ($uid === 0) {
+                       return $output;
+               }
+               $databaseConnection = static::getDatabaseConnection();
+               $clause = trim($clause) !== '' ? ' AND ' . $clause : '';
+               $loopCheck = 100;
+               while ($loopCheck > 0) {
                        $loopCheck--;
-                       $res = $databaseConnection->exec_SELECTquery('uid,pid,title,deleted,t3ver_oid,t3ver_wsid', 'pages', 'uid=' . (int)$uid . (trim($clause) !== '' ? ' AND ' . $clause : ''));
-                       if (is_resource($res)) {
+                       $res = $databaseConnection->exec_SELECTquery('uid,pid,title,deleted,t3ver_oid,t3ver_wsid', 'pages', 'uid=' . $uid . $clause);
+                       if ($res !== FALSE) {
                                $row = $databaseConnection->sql_fetch_assoc($res);
                                $databaseConnection->sql_free_result($res);
                                BackendUtility::workspaceOL('pages', $row);
                                if (is_array($row)) {
                                        BackendUtility::fixVersioningPid('pages', $row);
-                                       $uid = $row['pid'];
+                                       $uid = (int)$row['pid'];
                                        $output = '/' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($row['title'], $titleLimit)) . $output;
                                        if ($row['deleted']) {
-                                               $output = '<span class="deletedPath">' . $output . '</span>';
+                                               $output = '<span class="text-danger">' . $output . '</span>';
                                        }
                                        if ($fullTitleLimit) {
                                                $fullOutput = '/' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($row['title'], $fullTitleLimit)) . $fullOutput;
@@ -149,7 +151,8 @@ class RecyclerUtility {
         * @return string The current backend charset
         */
        static public function getCurrentCharset() {
-               return $GLOBALS['LANG']->csConvObj->parse_charset($GLOBALS['LANG']->charSet);
+               $lang = static::getLanguageService();
+               return $lang->csConvObj->parse_charset($lang->charSet);
        }
 
        /**
@@ -169,9 +172,48 @@ class RecyclerUtility {
         */
        static public function getUtf8String($string) {
                if (self::isNotUtf8Charset()) {
-                       $string = $GLOBALS['LANG']->csConvObj->utf8_encode($string, self::getCurrentCharset());
+                       $string = static::getLanguageService()->csConvObj->utf8_encode($string, self::getCurrentCharset());
                }
                return $string;
        }
 
+       /**
+        * Returns an instance of DatabaseConnection
+        *
+        * @return \TYPO3\CMS\Core\Database\DatabaseConnection
+        */
+       static protected function getDatabaseConnection() {
+               return $GLOBALS['TYPO3_DB'];
+       }
+
+       /**
+        * Returns the BackendUser
+        *
+        * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
+        */
+       static protected function getBackendUser() {
+               return $GLOBALS['BE_USER'];
+       }
+
+       /**
+        * Returns an instance of LanguageService
+        *
+        * @return \TYPO3\CMS\Lang\LanguageService
+        */
+       static protected function getLanguageService() {
+               return $GLOBALS['LANG'];
+       }
+
+       /**
+        * Returns the modifyable tables of the current user
+        */
+       static public function getModifyableTables() {
+               if ((bool)$GLOBALS['BE_USER']->user['admin']) {
+                       $tables = array_keys($GLOBALS['TCA']);
+               } else {
+                       $tables = explode(',', $GLOBALS['BE_USER']->groupData['tables_modify']);
+               }
+               return $tables;
+       }
+
 }
diff --git a/typo3/sysext/recycler/Resources/Private/Language/locallang.xlf b/typo3/sysext/recycler/Resources/Private/Language/locallang.xlf
new file mode 100644 (file)
index 0000000..9c786c3
--- /dev/null
@@ -0,0 +1,221 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xliff version="1.0" xmlns:t3="http://typo3.org/schemas/xliff">
+       <file t3:id="1415814907" source-language="en" datatype="plaintext" original="messages" date="2011-10-17T20:22:35Z" product-name="recycler">
+               <header/>
+               <body>
+                       <trans-unit id="title" xml:space="preserve">
+                               <source>Recycler</source>
+                       </trans-unit>
+                       <trans-unit id="description" xml:space="preserve">
+                               <source>The recycler allows you to select any deleted data and undelete it. You can undelete recursive if the parent of the element is deleted too.</source>
+                       </trans-unit>
+                       <trans-unit id="label_alltables" xml:space="preserve">
+                               <source>All tables</source>
+                       </trans-unit>
+                       <trans-unit id="button.cancel" xml:space="preserve">
+                               <source>Cancel</source>
+                       </trans-unit>
+                       <trans-unit id="button.delete" xml:space="preserve">
+                               <source>Delete</source>
+                       </trans-unit>
+                       <trans-unit id="button.deleteselected" xml:space="preserve">
+                               <source>Delete {0} records</source>
+                       </trans-unit>
+                       <trans-unit id="button.undo" xml:space="preserve">
+                               <source>Recover</source>
+                       </trans-unit>
+                       <trans-unit id="button.expand" xml:space="preserve">
+                               <source>Expand record</source>
+                       </trans-unit>
+                       <trans-unit id="button.undoselected" xml:space="preserve">
+                               <source>Recover {0} records</source>
+                       </trans-unit>
+                       <trans-unit id="button.reload" xml:space="preserve">
+                               <source>Reload</source>
+                       </trans-unit>
+                       <trans-unit id="modal.delete.header" xml:space="preserve">
+                               <source>Confirm deletion</source>
+                       </trans-unit>
+                       <trans-unit id="modal.undo.header" xml:space="preserve">
+                               <source>Recover records</source>
+                       </trans-unit>
+                       <trans-unit id="modal.undo.recursive" xml:space="preserve">
+                               <source>Recover recursively</source>
+                       </trans-unit>
+                       <trans-unit id="modal.deletecontent.text" xml:space="preserve">
+                               <source>Do you really want to delete the record "{0}" {1} permanently? You cannot revert this action!</source>
+                       </trans-unit>
+                       <trans-unit id="modal.massdelete.text" xml:space="preserve">
+                               <source>Do you really want to delete all selected records and potential subpages? You cannot revert this action!</source>
+                       </trans-unit>
+                       <trans-unit id="modal.deletepage.text" xml:space="preserve">
+                               <source>Do you really want to delete the page "{0}" {1} and all of its contents (records and pages) permanently? You cannot revert this action!</source>
+                       </trans-unit>
+                       <trans-unit id="modal.undocontent.text" xml:space="preserve">
+                               <source>Do you really want to recover the record "{0}" {1}?</source>
+                       </trans-unit>
+                       <trans-unit id="modal.undopage.text" xml:space="preserve">
+                               <source>Do you really want to recover the page "{0}" {1}, its contents and optionally all of its subpages?</source>
+                       </trans-unit>
+                       <trans-unit id="modal.massundo.text" xml:space="preserve">
+                               <source>Do you really want to recover all selected records and optionally potential subpages?</source>
+                       </trans-unit>
+                       <trans-unit id="flashmessage.delete.norecordsselected" xml:space="preserve">
+                               <source>No records set to delete.</source>
+                       </trans-unit>
+                       <trans-unit id="flashmessage.delete.success.singular" xml:space="preserve">
+                               <source>One record was deleted.</source>
+                       </trans-unit>
+                       <trans-unit id="flashmessage.delete.success.plural" xml:space="preserve">
+                               <source>%d records were deleted.</source>
+                       </trans-unit>
+                       <trans-unit id="flashmessage.delete.failure.singular" xml:space="preserve">
+                               <source>Could not delete one record.</source>
+                       </trans-unit>
+                       <trans-unit id="flashmessage.delete.failure.plural" xml:space="preserve">
+                               <source>Could not delete %d records.</source>
+                       </trans-unit>
+                       <trans-unit id="flashmessage.undo.norecordsselected" xml:space="preserve">
+                               <source>No records set to recover.</source>
+                       </trans-unit>
+                       <trans-unit id="flashmessage.undo.success.singular" xml:space="preserve">
+                               <source>One record was recovered.</source>
+                       </trans-unit>
+                       <trans-unit id="flashmessage.undo.success.plural" xml:space="preserve">
+                               <source>%d records were recovered.</source>
+                       </trans-unit>
+                       <trans-unit id="flashmessage.undo.failure.singular" xml:space="preserve">
+                               <source>Could not recover one record.</source>
+                       </trans-unit>
+                       <trans-unit id="flashmessage.undo.failure.plural" xml:space="preserve">
+                               <source>Could not recover %d records.</source>
+                       </trans-unit>
+                       <trans-unit id="table.header.uid" xml:space="preserve">
+                               <source>UID</source>
+                       </trans-unit>
+                       <trans-unit id="table.header.pid" xml:space="preserve">
+                               <source>PID</source>
+                       </trans-unit>
+                       <trans-unit id="table.header.record" xml:space="preserve">
+                               <source>Record</source>
+                       </trans-unit>
+                       <trans-unit id="table.header.actions" xml:space="preserve">
+                               <source>Actions</source>
+                       </trans-unit>
+                       <trans-unit id="table.header.table" xml:space="preserve">
+                               <source>Table</source>
+                       </trans-unit>
+                       <trans-unit id="table.header.tstamp" xml:space="preserve">
+                               <source>Last edit</source>
+                       </trans-unit>
+                       <trans-unit id="table.header.actions" xml:space="preserve">
+                               <source>Actions</source>
+                       </trans-unit>
+                       <trans-unit id="table.header.crdate" xml:space="preserve">
+                               <source>Created on</source>
+                       </trans-unit>
+                       <trans-unit id="table.header.owner" xml:space="preserve">
+                               <source>Owner</source>
+                       </trans-unit>
+                       <trans-unit id="table.header.path" xml:space="preserve">
+                               <source>Path</source>
+                       </trans-unit>
+                       <trans-unit id="js.label_lostandfound" xml:space="preserve" deprecated="Unused since CMS 7">
+                               <source>Lost and found</source>
+                       </trans-unit>
+                       <trans-unit id="js.label_deletedTab" xml:space="preserve" deprecated="Unused since CMS 7">
+                               <source>Deleted data</source>
+                       </trans-unit>
+                       <trans-unit id="js.label_loadMessage" xml:space="preserve" deprecated="Unused since CMS 7">
+                               <source>Please wait...&lt;br/&gt;Records would be loaded!</source>
+                       </trans-unit>
+                       <trans-unit id="js.label_doDelete_confirmText" xml:space="preserve" deprecated="Unused since CMS 7">
+                               <source>Delete this record AND if page ALL subrecords ? &lt;br/&gt;ATTENTION: Data will be finally deleted from the database</source>
+                       </trans-unit>
+                       <trans-unit id="js.label_deleteButton_text" xml:space="preserve" deprecated="Unused since CMS 7">
+                               <source>Delete</source>
+                       </trans-unit>
+                       <trans-unit id="js.label_deleteButton_tooltip" xml:space="preserve" deprecated="Unused since CMS 7">
+                               <source>Delete selected items</source>
+                       </trans-unit>
+                       <trans-unit id="js.label_undeleteButton_text" xml:space="preserve" deprecated="Unused since CMS 7">
+                               <source>Undelete</source>
+                       </trans-unit>
+                       <trans-unit id="js.label_undeleteButton_tooltip" xml:space="preserve" deprecated="Unused since CMS 7">
+                               <source>Undelete selected items</source>
+                       </trans-unit>
+                       <trans-unit id="js.label_error_NoSelectedRows_title" xml:space="preserve" deprecated="Unused since CMS 7">
+                               <source>No row selected</source>
+                       </trans-unit>
+                       <trans-unit id="js.label_error_NoSelectedRows_msg" xml:space="preserve" deprecated="Unused since CMS 7">
+                               <source>You must select a row!</source>
+                       </trans-unit>
+                       <trans-unit id="js.label_yes" xml:space="preserve" deprecated="Unused since CMS 7">
+                               <source>Yes</source>
+                       </trans-unit>
+                       <trans-unit id="js.label_no" xml:space="preserve" deprecated="Unused since CMS 7">
+                               <source>No</source>
+                       </trans-unit>
+                       <trans-unit id="js.label_sure" xml:space="preserve" deprecated="Unused since CMS 7">
+                               <source>Are you Sure?</source>
+                       </trans-unit>
+                       <trans-unit id="js.label_crdate" xml:space="preserve" deprecated="Unused since CMS 7">
+                               <source>Created</source>
+                       </trans-unit>
+                       <trans-unit id="js.label_owner" xml:space="preserve" deprecated="Unused since CMS 7">
+                               <source>Owner</source>
+                       </trans-unit>
+                       <trans-unit id="js.label_tstamp" xml:space="preserve" deprecated="Unused since CMS 7">
+                               <source>Last edit</source>
+                       </trans-unit>
+                       <trans-unit id="js.label_clear" xml:space="preserve" deprecated="Unused since CMS 7">
+                               <source>Clear</source>
+                       </trans-unit>
+                       <trans-unit id="js.label_filter" xml:space="preserve" deprecated="Unused since CMS 7">
+                               <source>Filter</source>
+                       </trans-unit>
+                       <trans-unit id="js.label_title_undelete" xml:space="preserve" deprecated="Unused since CMS 7">
+                               <source>Undelete?</source>
+                       </trans-unit>
+                       <trans-unit id="js.label_text_undelete" xml:space="preserve" deprecated="Unused since CMS 7">
+                               <source>Undelete records from tables: </source>
+                       </trans-unit>
+                       <trans-unit id="js.label_title_delete" xml:space="preserve" deprecated="Unused since CMS 7">
+                               <source>Delete?</source>
+                       </trans-unit>
+                       <trans-unit id="js.label_text_delete" xml:space="preserve" deprecated="Unused since CMS 7">
+                               <source>Delete records from tables: </source>
+                       </trans-unit>
+                       <trans-unit id="js.label_boxLabel_undelete_recursive" xml:space="preserve" deprecated="Unused since CMS 7">
+                               <source>Undelete recursively</source>
+                       </trans-unit>
+                       <trans-unit id="js.label_tableMenu_label" xml:space="preserve" deprecated="Unused since CMS 7">
+                               <source>Table:</source>
+                       </trans-unit>
+                       <trans-unit id="js.label_tableMenu_emptyText" xml:space="preserve" deprecated="Unused since CMS 7">
+                               <source>Choose a table...</source>
+                       </trans-unit>
+                       <trans-unit id="js.label_filter_emptyText" xml:space="preserve" deprecated="Unused since CMS 7">
+                               <source>Keyword</source>
+                       </trans-unit>
+                       <trans-unit id="js.label_search" xml:space="preserve" deprecated="Unused since CMS 7">
+                               <source>Search:</source>
+                       </trans-unit>
+                       <trans-unit id="js.label_pagingMessage" xml:space="preserve" deprecated="Unused since CMS 7">
+                               <source>Displaying records {0} - {1} of {2}</source>
+                       </trans-unit>
+                       <trans-unit id="js.label_pagingEmpty" xml:space="preserve" deprecated="Unused since CMS 7">
+                               <source>No records to display</source>
+                       </trans-unit>
+                       <trans-unit id="js.label_noValueFound" xml:space="preserve" deprecated="Unused since CMS 7">
+                               <source>No records for {0}</source>
+                       </trans-unit>
+                       <trans-unit id="js.label_records" xml:space="preserve" deprecated="Unused since CMS 7">
+                               <source>Records</source>
+                       </trans-unit>
+                       <trans-unit id="js.label_table" xml:space="preserve" deprecated="Unused since CMS 7">
+                               <source>Table</source>
+                       </trans-unit>
+               </body>
+       </file>
+</xliff>
diff --git a/typo3/sysext/recycler/Resources/Private/Language/locallang_mod.xlf b/typo3/sysext/recycler/Resources/Private/Language/locallang_mod.xlf
new file mode 100644 (file)
index 0000000..7cdb061
--- /dev/null
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xliff version="1.0" xmlns:t3="http://typo3.org/schemas/xliff">
+       <file t3:id="1415814908" source-language="en" datatype="plaintext" original="messages" date="2011-10-17T20:22:35Z" product-name="recycler">
+               <header/>
+               <body>
+                       <trans-unit id="mlang_tabs_tab" xml:space="preserve">
+                               <source>Recycler</source>
+                       </trans-unit>
+                       <trans-unit id="mlang_labels_tablabel" xml:space="preserve">
+                               <source>TYPO3 Recycler</source>
+                       </trans-unit>
+                       <trans-unit id="mlang_labels_tabdescr" xml:space="preserve">
+                               <source>The recycler offers the possibility to restore deleted records or remove them from the database permanently. These actions can be applied to a single record, multiple records, and recursively to child records (ex. restoring a page can restore all content elements on that page). Filtering by page and by table provides a quick overview of deleted records before taking action on them.</source>
+                       </trans-unit>
+               </body>
+       </file>
+</xliff>
diff --git a/typo3/sysext/recycler/Resources/Private/Layouts/Default.html b/typo3/sysext/recycler/Resources/Private/Layouts/Default.html
new file mode 100644 (file)
index 0000000..11270b3
--- /dev/null
@@ -0,0 +1,36 @@
+<f:be.container
+       includeRequireJsModules="{
+               0:'TYPO3/CMS/Recycler/Recycler'
+       }"
+>
+       <div class="typo3-fullDoc">
+               <div id="typo3-docheader">
+                       <div class="typo3-docheader-functions">
+                               <div class="left">
+                                       <f:be.buttons.csh />
+                               </div>
+                               <div class="right">
+                               </div>
+                       </div>
+
+                       <div class="typo3-docheader-buttons">
+                               <div class="left">
+                                       <f:render section="iconButtons" />
+                               </div>
+                               <div class="right">
+                                       <f:be.buttons.shortcut />
+                               </div>
+                       </div>
+               </div>
+
+               <div id="typo3-docbody">
+                       <div id="typo3-inner-docbody">
+                               <h1><f:translate key="title" /></h1>
+
+                               <f:flashMessages renderMode="div" />
+
+                               <f:render section="content" />
+                       </div>
+               </div>
+       </div>
+</f:be.container>
\ No newline at end of file
diff --git a/typo3/sysext/recycler/Resources/Private/Partials/RecordsTable/DeletedRecord.html b/typo3/sysext/recycler/Resources/Private/Partials/RecordsTable/DeletedRecord.html
new file mode 100644 (file)
index 0000000..0db4ea4
--- /dev/null
@@ -0,0 +1,50 @@
+<tr data-uid="{record.uid}" data-table="{record.table}" data-recordtitle="{record.title}">
+       <td>
+               <div class="btn-group">
+                       <div class="btn-group btn-checkbox-holder">
+                               <input type="checkbox" class="smallCheckboxes btn-checkbox">
+                               <span class="btn">
+                                       <span class="t3-icon fa"></span>
+                               </span>
+                       </div>
+                       <a class="btn" data-action="expand" data-toggle="collapse" data-target="#{record.table}_{record.uid}">
+                               <f:be.buttons.icon icon="apps-pagetree-collapse" title="{f:translate(key: 'LLL:EXT:recycler/Resources/Private/Language/locallang.xlf:button.expand')}" />
+                       </a>
+                       <a class="btn" data-action="undo">
+                               <f:be.buttons.icon icon="actions-edit-undo" title="{f:translate(key: 'LLL:EXT:recycler/Resources/Private/Language/locallang.xlf:button.undo')}" />
+                       </a>
+                       <f:if condition="{allowDelete}">
+                               <a class="btn" data-action="delete">
+                                       <f:be.buttons.icon icon="actions-edit-delete" title="{f:translate(key: 'LLL:EXT:recycler/Resources/Private/Language/locallang.xlf:button.delete')}" />
+                               </a>
+                       </f:if>
+               </div>
+       </td>
+       <td>{record.tableTitle}</td>
+       <td><f:format.html>{record.icon}</f:format.html> {record.title}</td>
+       <td>{record.tstamp}</td>
+       <td>{record.uid}</td>
+       <td>{record.pageTitle} ({record.pid})</td>
+</tr>
+<tr class="collapse" id="{record.table}_{record.uid}">
+       <td colspan="6">
+               <table class="table">
+                       <thead>
+                               <tr>
+                                       <th><f:translate key="LLL:EXT:recycler/Resources/Private/Language/locallang.xlf:table.header.table" /></th>
+                                       <th><f:translate key="LLL:EXT:recycler/Resources/Private/Language/locallang.xlf:table.header.crdate" /></th>
+                                       <th><f:translate key="LLL:EXT:recycler/Resources/Private/Language/locallang.xlf:table.header.owner" /></th>
+                                       <th><f:translate key="LLL:EXT:recycler/Resources/Private/Language/locallang.xlf:table.header.path" /></th>
+                               </tr>
+                       </thead>
+                       <tbody>
+                               <tr>
+                                       <td>{record.table}</td>
+                                       <td>{record.crdate}</td>
+                                       <td>{record.owner} ({record.owner_uid})</td>
+                                       <td><f:format.html>{record.path}</f:format.html></td>
+                               </tr>
+                       </tbody>
+               </table>
+       </td>
+</tr>
\ No newline at end of file
diff --git a/typo3/sysext/recycler/Resources/Private/Templates/Ajax/RecordsTable.html b/typo3/sysext/recycler/Resources/Private/Templates/Ajax/RecordsTable.html
new file mode 100644 (file)
index 0000000..01ace23
--- /dev/null
@@ -0,0 +1,9 @@
+<f:for each="{records}" as="record">
+       <f:render
+               partial="RecordsTable/DeletedRecord"
+               arguments="{
+                       record: '{record}',
+                       allowDelete: '{allowDelete}'
+               }"
+       />
+</f:for>
diff --git a/typo3/sysext/recycler/Resources/Private/Templates/RecyclerModule/Index.html b/typo3/sysext/recycler/Resources/Private/Templates/RecyclerModule/Index.html
new file mode 100644 (file)
index 0000000..f6ac036
--- /dev/null
@@ -0,0 +1,51 @@
+<f:layout name="Default" />
+
+<f:section name="iconButtons">
+       <a data-action="reload"><f:be.buttons.icon icon="actions-system-refresh" title="{f:translate(key:'button.reload')}" /></a>
+</f:section>
+
+<f:section name="content">
+       <div id="recycler-index">
+               <form id="recycler-form" class="form-inline">
+                       <div class="input-group">
+                               <input type="text" name="search-text" class="form-control">
+                               <span class="input-group-btn">
+                                       <button type="submit" class="btn btn-default disabled"><span class="t3-icon fa fa-search"></span></button>
+                               </span>
+                       </div>
+                       <select name="depth" class="form-control">
+                               <option value="0"><f:translate key="LLL:EXT:lang/locallang_core.xlf:labels.depth_0" /></option>
+                               <option value="1"><f:translate key="LLL:EXT:lang/locallang_core.xlf:labels.depth_1" /></option>
+                               <option value="2"><f:translate key="LLL:EXT:lang/locallang_core.xlf:labels.depth_2" /></option>
+                               <option value="3"><f:translate key="LLL:EXT:lang/locallang_core.xlf:labels.depth_3" /></option>
+                               <option value="4"><f:translate key="LLL:EXT:lang/locallang_core.xlf:labels.depth_4" /></option>
+                               <option value="999"><f:translate key="LLL:EXT:lang/locallang_core.xlf:labels.depth_infi" /></option>
+                       </select>
+                       <select name="pages" class="form-control"></select>
+               </form>
+               <div class="table-fit">
+                       <table class="table table-hover" id="itemsInRecycler">
+                               <thead>
+                                       <tr>
+                                               <th><f:translate key="table.header.actions" /></th>
+                                               <th><f:translate key="table.header.table" /></th>
+                                               <th><f:translate key="table.header.record" /></th>
+                                               <th><f:translate key="table.header.tstamp" /></th>
+                                               <th><f:translate key="table.header.uid" /></th>
+                                               <th><f:translate key="table.header.pid" /></th>
+                                       </tr>
+                               </thead>
+                               <tbody>
+                               </tbody>
+                       </table>
+               </div>
+               <div class="text-right">
+                       <button class="btn btn-success disabled" data-action="massundo"><f:translate key="button.undo" /></button>
+                       <f:if condition="{allowDelete}">
+                               <button class="btn btn-danger disabled" data-action="massdelete"><f:translate key="button.delete" /></button>
+                       </f:if>
+               </div>
+               <nav>
+               </nav>
+       </div>
+</f:section>
\ No newline at end of file
diff --git a/typo3/sysext/recycler/Resources/Private/Templates/RecyclerModule/NoAccess.html b/typo3/sysext/recycler/Resources/Private/Templates/RecyclerModule/NoAccess.html
new file mode 100644 (file)
index 0000000..920e5a5
--- /dev/null
@@ -0,0 +1,8 @@
+<f:layout name="Default" />
+
+<f:section name="iconButtons">
+</f:section>
+
+<f:section name="content">
+       --No Access--
+</f:section>
\ No newline at end of file
diff --git a/typo3/sysext/recycler/Resources/Public/JavaScript/Recycler.js b/typo3/sysext/recycler/Resources/Public/JavaScript/Recycler.js
new file mode 100644 (file)
index 0000000..ee97b44
--- /dev/null
@@ -0,0 +1,539 @@
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+/**
+ * RequireJS module for Recycler
+ */
+define(['jquery', 'nprogress'], function($, NProgress) {
+       var Recycler = {
+               identifiers: {
+                       searchForm: '#recycler-form',
+                       searchText: '#recycler-form [name=search-text]',
+                       searchSubmitBtn: '#recycler-form button[type=submit]',
+                       depthSelector: '#recycler-form [name=depth]',
+                       tableSelector: '#recycler-form [name=pages]',
+                       recyclerTable: '#itemsInRecycler',
+                       paginator: '#recycler-index nav',
+                       reloadAction: 'a[data-action=reload]',
+                       massUndo: 'button[data-action=massundo]',
+                       massDelete: 'button[data-action=massdelete]'
+               },
+               elements: {}, // filled in getElements()
+               paging: {
+                       currentPage: 1,
+                       totalPages: 1,
+                       totalItems: 0,
+                       itemsPerPage: 20
+               },
+               markedRecordsForMassAction: []
+       };
+
+       /**
+        * Gets required elements
+        */
+       Recycler.getElements = function() {
+               Recycler.elements = {
+                       $searchForm: $(Recycler.identifiers.searchForm),
+                       $searchTextField: $(Recycler.identifiers.searchText),
+                       $searchSubmitBtn: $(Recycler.identifiers.searchSubmitBtn),
+                       $depthSelector: $(Recycler.identifiers.depthSelector),
+                       $tableSelector: $(Recycler.identifiers.tableSelector),
+                       $recyclerTable: $(Recycler.identifiers.recyclerTable),
+                       $tableBody: $(Recycler.identifiers.recyclerTable).find('tbody'),
+                       $paginator: $(Recycler.identifiers.paginator),
+                       $reloadAction: $(Recycler.identifiers.reloadAction),
+                       $massUndo: $(Recycler.identifiers.massUndo),
+                       $massDelete: $(Recycler.identifiers.massDelete)
+               };
+       };
+
+       /**
+        * Register events
+        */
+       Recycler.registerEvents = function() {
+               // submitting the form
+               Recycler.elements.$searchForm.on('submit', function(e) {
+                       e.preventDefault();
+                       if (Recycler.elements.$searchTextField.val() !== '') {
+                               Recycler.loadDeletedElements();
+                       }
+               });
+
+               // changing the search field
+               Recycler.elements.$searchTextField.on('keyup', function() {
+                       var $me = $(this);
+
+                       if ($me.val() !== '') {
+                               Recycler.elements.$searchSubmitBtn.removeClass('disabled');
+                       } else {
+                               Recycler.elements.$searchSubmitBtn.addClass('disabled');
+                               Recycler.loadDeletedElements();
+                       }
+               });
+
+               // changing "depth"
+               Recycler.elements.$depthSelector.on('change', function() {
+                       Recycler.loadAvailableTables();
+                       Recycler.loadDeletedElements();
+               });
+
+               // changing "table"
+               Recycler.elements.$tableSelector.on('change', function() {
+                       Recycler.loadDeletedElements();
+               });
+
+               // clicking "recover" in single row
+               Recycler.elements.$recyclerTable.on('click', '[data-action=undo]', Recycler.undoRecord);
+
+               // clicking "delete" in single row
+               Recycler.elements.$recyclerTable.on('click', '[data-action=delete]', Recycler.deleteRecord);
+
+               Recycler.elements.$reloadAction.on('click', function(e) {
+                       e.preventDefault();
+                       Recycler.loadDeletedElements();
+               });
+
+               // clicking an action in the paginator
+               Recycler.elements.$paginator.on('click', 'a[data-action]', function(e) {
+                       e.preventDefault();
+
+                       var $el = $(this),
+                               reload = false;
+
+                       switch ($el.data('action')) {
+                               case 'previous':
+                                       if (Recycler.paging.currentPage > 1) {
+                                               Recycler.paging.currentPage--;
+                                               reload = true;
+                                       }
+                                       break;
+                               case 'next':
+                                       if (Recycler.paging.currentPage < Recycler.paging.totalPages) {
+                                               Recycler.paging.currentPage++;
+                                               reload = true;
+                                       }
+                                       break;
+                               case 'page':
+                                       Recycler.paging.currentPage = parseInt($el.find('span').text());
+                                       reload = true;
+                                       break;
+                       }
+
+                       if (reload) {
+                               Recycler.loadDeletedElements();
+                       }
+               });
+
+               if (!TYPO3.settings.Recycler.deleteDisable) {
+                       Recycler.elements.$massDelete.show();
+               } else {
+                       Recycler.elements.$massDelete.remove();
+               }
+
+               Recycler.elements.$recyclerTable.on('show.bs.collapse hide.bs.collapse', 'tr.collapse', function(e) {
+                       var $trigger = $(e.currentTarget).prev('tr').find('[data-action=expand]'),
+                               $iconEl = $trigger.find('.t3-icon'),
+                               removeClass,
+                               addClass;
+
+                       switch (e.type) {
+                               case 'show':
+                                       removeClass = 't3-icon-pagetree-collapse';
+                                       addClass = 't3-icon-pagetree-expand';
+                                       break;
+                               case 'hide':
+                                       removeClass = 't3-icon-pagetree-expand';
+                                       addClass = 't3-icon-pagetree-collapse';
+                                       break;
+                       }
+
+                       $iconEl.removeClass(removeClass).addClass(addClass);
+               });
+
+               // checkboxes in the table
+               Recycler.elements.$recyclerTable.on('click', 'tr input[type=checkbox]', Recycler.handleCheckboxSelects);
+
+               Recycler.elements.$massUndo.on('click', Recycler.undoRecord);
+               Recycler.elements.$massDelete.on('click', Recycler.deleteRecord);
+       };
+
+       /**
+        * Initialize the recycler module
+        */
+       Recycler.initialize = function() {
+               NProgress.configure({parent: '#typo3-docheader', showSpinner: false});
+
+               Recycler.getElements();
+               Recycler.registerEvents();
+
+               if (TYPO3.settings.Recycler.depthSelection > 0) {
+                       Recycler.elements.$depthSelector.val(TYPO3.settings.Recycler.depthSelection).trigger('change');
+               } else {
+                       Recycler.loadAvailableTables();
+                       Recycler.loadDeletedElements();
+               }
+       };
+
+       /**
+        * Handles the clicks on checkboxes in the records table
+        */
+       Recycler.handleCheckboxSelects = function() {
+               var $checkbox = $(this),
+                       $tr = $checkbox.parents('tr'),
+                       table = $tr.data('table'),
+                       uid = $tr.data('uid'),
+                       record = table + ':' + uid;
+
+               if ($checkbox.prop('checked')) {
+                       Recycler.markedRecordsForMassAction.push(record);
+                       $tr.addClass('warning');
+               } else {
+                       var index = Recycler.markedRecordsForMassAction.indexOf(record);
+                       if (index > -1) {
+                               Recycler.markedRecordsForMassAction.splice(index, 1);
+                       }
+                       $tr.removeClass('warning');
+               }
+
+               if (Recycler.markedRecordsForMassAction.length > 0) {
+                       if (Recycler.elements.$massUndo.hasClass('disabled')) {
+                               Recycler.elements.$massUndo.removeClass('disabled');
+                       }
+                       if (Recycler.elements.$massDelete.hasClass('disabled')) {
+                               Recycler.elements.$massDelete.removeClass('disabled');
+                       }
+
+                       var btnTextUndo = Recycler.createMessage(TYPO3.lang['button.undoselected'], [Recycler.markedRecordsForMassAction.length]),
+                               btnTextDelete = Recycler.createMessage(TYPO3.lang['button.deleteselected'], [Recycler.markedRecordsForMassAction.length]);
+
+                       Recycler.elements.$massUndo.text(btnTextUndo);
+                       Recycler.elements.$massDelete.text(btnTextDelete);
+               } else {
+                       Recycler.resetMassActionButtons();
+               }
+       };
+
+       /**
+        * Resets the mass action state
+        */
+       Recycler.resetMassActionButtons = function() {
+               Recycler.markedRecordsForMassAction = [];
+               Recycler.elements.$massUndo.addClass('disabled').text(TYPO3.lang['button.undo']);
+               Recycler.elements.$massDelete.addClass('disabled').text(TYPO3.lang['button.delete']);
+       };
+
+       /**
+        * Loads all tables which contain deleted records. This call is not asynchronous
+        * due to settings the stored table before loading the deleted records.
+        */
+       Recycler.loadAvailableTables = function() {
+               $.ajax({
+                       url: TYPO3.settings.ajaxUrls['RecyclerAjaxController::dispatch'],
+                       dataType: 'json',
+                       async: false,
+                       data: {
+                               action: 'getTables',
+                               startUid: TYPO3.settings.Recycler.startUid,
+                               depth: Recycler.elements.$depthSelector.find('option:selected').val()
+                       },
+                       beforeSend: function() {
+                               NProgress.start();
+                               Recycler.elements.$tableSelector.val('');
+                               Recycler.paging.currentPage = 1;
+                       },
+                       success: function(data) {
+                               var tables = [];
+                               Recycler.elements.$tableSelector.children().remove();
+                               $.each(data, function(_, value) {
+                                       var tableName = value[0],
+                                               deletedRecords = value[1],
+                                               tableDescription = value[2];
+
+                                       if (tableDescription === '') {
+                                               tableDescription = TYPO3.lang['label_alltables'];
+                                       }
+                                       var optionText = tableDescription + ' (' + deletedRecords + ')';
+                                       tables.push($('<option />').val(tableName).text(optionText))
+                               });
+
+                               if (tables.length > 0) {
+                                       Recycler.elements.$tableSelector.append(tables);
+                                       if (TYPO3.settings.Recycler.tableSelection !== '') {
+                                               Recycler.elements.$tableSelector.val(TYPO3.settings.Recycler.tableSelection);
+                                       }
+                               }
+                       },
+                       complete: function() {
+                               NProgress.done();
+                       }
+               });
+       };
+
+       /**
+        * Loads the deleted elements, based on the filters
+        */
+       Recycler.loadDeletedElements = function() {
+               $.ajax({
+                       url: TYPO3.settings.ajaxUrls['RecyclerAjaxController::dispatch'],
+                       dataType: 'json',
+                       data: {
+                               action: 'getDeletedRecords',
+                               depth: Recycler.elements.$depthSelector.find('option:selected').val(),
+                               startUid: TYPO3.settings.Recycler.startUid,
+                               table: Recycler.elements.$tableSelector.find('option:selected').val(),
+                               filterTxt: Recycler.elements.$searchTextField.val(),
+                               start: (Recycler.paging.currentPage -1) * Recycler.paging.itemsPerPage,
+                               limit: Recycler.paging.itemsPerPage
+                       },
+                       beforeSend: function() {
+                               NProgress.start();
+                               Recycler.resetMassActionButtons();
+                       },
+                       success: function(data) {
+                               Recycler.elements.$tableBody.html(data.rows);
+                               Recycler.buildPaginator(data.totalItems);
+                       },
+                       complete: function() {
+                               NProgress.done();
+                       }
+               });
+       };
+
+       Recycler.deleteRecord = function() {
+               if (TYPO3.settings.Recycler.deleteDisable) {
+                       return;
+               }
+
+               var $tr = $(this).parents('tr'),
+                       isMassDelete = $tr.parent().prop('tagName') !== 'TBODY'; // deleteRecord() was invoked by the mass delete button
+
+               if (isMassDelete) {
+                       var records = Recycler.markedRecordsForMassAction,
+                               message = TYPO3.lang['modal.massdelete.text'];
+               } else {
+                       var uid = $tr.data('uid'),
+                               table = $tr.data('table'),
+                               records = table + ':' + uid,
+                               recordTitle = $tr.data('recordtitle'),
+                               message = table === 'pages' ? TYPO3.lang['modal.deletepage.text'] : TYPO3.lang['modal.deletecontent.text'];
+                               message = Recycler.createMessage(message, [recordTitle, '[' + records + ']']);
+               }
+
+               top.TYPO3.Modal.confirm(TYPO3.lang['modal.delete.header'], message, top.TYPO3.Severity.error, [
+                       {
+                               text: TYPO3.lang['button.cancel'],
+                               trigger: function() {
+                                       top.TYPO3.Modal.dismiss();
+                               }
+                       }, {
+                               text: TYPO3.lang['button.delete'],
+                               btnClass: 'btn-danger',
+                               trigger: function() {
+                                       Recycler.callAjaxAction('delete', typeof records === 'object' ? records : [records], isMassDelete);
+                               }
+                       }
+               ]);
+       };
+
+       Recycler.undoRecord = function() {
+               var $tr = $(this).parents('tr'),
+                       isMassUndo = $tr.parent().prop('tagName') !== 'TBODY'; // undoRecord() was invoked by the mass delete button
+
+               if (isMassUndo) {
+                       var records = Recycler.markedRecordsForMassAction,
+                               messageText = TYPO3.lang['modal.massundo.text'],
+                               recoverPages = true;
+               } else {
+                       var uid = $tr.data('uid'),
+                               table = $tr.data('table'),
+                               records = table + ':' + uid,
+                               recordTitle = $tr.data('recordtitle'),
+                               $message = null,
+                               recoverPages = table === 'pages',
+                               messageText = recoverPages ? TYPO3.lang['modal.undopage.text'] : TYPO3.lang['modal.undocontent.text'];
+                               messageText = Recycler.createMessage(messageText, [recordTitle, '[' + records + ']']);
+               }
+
+               if (recoverPages) {
+                       $message = $('<div />').append(
+                               $('<p />').text(messageText),
+                               $('<div />', {class: 'checkbox'}).append(
+                                       $('<label />').append(TYPO3.lang['modal.undo.recursive']).prepend($('<input />', {id: 'undo-recursive', type: 'checkbox'}))
+                               )
+                       );
+               } else {
+                       $message = messageText;
+               }
+
+               top.TYPO3.Modal.confirm(TYPO3.lang['modal.undo.header'], $message, top.TYPO3.Severity.ok, [
+                       {
+                               text: TYPO3.lang['button.cancel'],
+                               trigger: function() {
+                                       top.TYPO3.Modal.dismiss();
+                               }
+                       }, {
+                               text: TYPO3.lang['button.undo'],
+                               btnClass: 'btn-success',
+                               trigger: function() {
+                                       Recycler.callAjaxAction('undo', typeof records === 'object' ? records : [records], isMassUndo);
+                               }
+                       }
+               ]);
+       };
+
+       /**
+        * Method that really calls the action via AJAX
+        */
+       Recycler.callAjaxAction = function(action, records, isMassAction) {
+               var data = {
+                               records: records,
+                               action: ''
+                       },
+                       reloadPageTree = false;
+               if (action === 'undo') {
+                       data.action = 'undoRecords';
+                       data.recursive = top.TYPO3.jQuery('#undo-recursive').prop('checked')  ? 1 : 0;
+                       reloadPageTree = true;
+               } else if (action === 'delete') {
+                       data.action = 'deleteRecords';
+               } else {
+                       return;
+               }
+
+               $.ajax({
+                       url: TYPO3.settings.ajaxUrls['RecyclerAjaxController::dispatch'],
+                       dataType: 'json',
+                       data: data,
+                       beforeSend: function() {
+                               NProgress.start();
+                       },
+                       success: function(data) {
+                               var severity = data.success ? top.TYPO3.Severity.ok : top.TYPO3.Severity.error;
+                               top.TYPO3.Flashmessage.display(severity, '', data.message);
+
+                               // reload recycler data
+                               Recycler.paging.currentPage = 1;
+                               Recycler.loadAvailableTables();
+                               Recycler.loadDeletedElements();
+
+                               if (isMassAction) {
+                                       Recycler.resetMassActionButtons();
+                               }
+
+                               if (reloadPageTree) {
+                                       Recycler.refreshPageTree();
+                               }
+                       },
+                       complete: function() {
+                               top.TYPO3.Modal.dismiss();
+                               NProgress.done();
+                       }
+               });
+       };
+
+       /**
+        * Replaces the placeholders with actual values
+        */
+       Recycler.createMessage = function(message, placeholders) {
+               if (typeof message === 'undefined') {
+                       return '';
+               }
+
+               return message.replace(
+                       /\{([0-9]+)\}/g,
+                       function(_, index) {
+                               return placeholders[index];
+                       }
+               );
+       };
+
+       /**
+        * Reloads the page tree
+        */
+       Recycler.refreshPageTree = function() {
+               if (top.TYPO3 && top.TYPO3.Backend && top.TYPO3.Backend.NavigationContainer && top.TYPO3.Backend.NavigationContainer.PageTree) {
+                       top.TYPO3.Backend.NavigationContainer.PageTree.refreshTree();
+               }
+       };
+
+       /**
+        * Build the paginator
+        */
+       Recycler.buildPaginator = function(totalItems) {
+               if (totalItems === 0) {
+                       Recycler.elements.$paginator.contents().remove();
+                       return;
+               }
+
+               Recycler.paging.totalItems = totalItems;
+               Recycler.paging.totalPages = Math.ceil(totalItems / Recycler.paging.itemsPerPage);
+
+               if (Recycler.paging.totalPages === 1) {
+                       // early abort if only one page is available
+                       return;
+               }
+
+               var $ul = $('<ul />', {class: 'pagination pagination-block'}),
+                       liElements = [],
+                       $controlFirstPage = $('<li />').append(
+                               $('<a />', {'data-action': 'previous'}).append(
+                                       $('<span />', {class: 't3-icon fa fa-arrow-left'})
+                               )
+                       ),
+                       $controlLastPage = $('<li />').append(
+                               $('<a />', {'data-action': 'next'}).append(
+                                       $('<span />', {class: 't3-icon fa fa-arrow-right'})
+                               )
+                       );
+
+               if (Recycler.paging.currentPage === 1) {
+                       $controlFirstPage.disablePagingAction();
+               }
+
+               if (Recycler.paging.currentPage === Recycler.paging.totalPages) {
+                       $controlLastPage.disablePagingAction();
+               }
+
+               for (var i = 1; i <= Recycler.paging.totalPages; i++) {
+                       var $li = $('<li />', {class: Recycler.paging.currentPage === i ? 'active' : ''});
+                       $li.append(
+                               $('<a />', {'data-action': 'page'}).append(
+                                       $('<span />').text(i)
+                               )
+                       );
+                       liElements.push($li);
+               }
+
+               $ul.append($controlFirstPage, liElements, $controlLastPage);
+               Recycler.elements.$paginator.contents().replaceWith($ul);
+       };
+
+       /**
+        * Changes the markup of a pagination action being disabled
+        */
+       $.fn.disablePagingAction = function() {
+               $(this).addClass('disabled').find('.t3-icon').unwrap().wrap($('<span />'));
+       };
+
+       /**
+        * return the main Recycler object
+        * initialize once on document ready
+        */
+       return function() {
+               $(document).ready(function() {
+                       Recycler.initialize();
+               });
+
+               return Recycler;
+       }();
+});
index 1e8bded..7ec4801 100644 (file)
@@ -2,5 +2,5 @@
 defined('TYPO3_MODE') or die();
 
 if (TYPO3_MODE === 'BE') {
-       \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::registerAjaxHandler('RecyclerAjaxController::init', \TYPO3\CMS\Recycler\Controller\RecyclerAjaxController::class . '->init');
+       \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::registerAjaxHandler('RecyclerAjaxController::dispatch', \TYPO3\CMS\Recycler\Controller\RecyclerAjaxController::class . '->dispatch');
 }
index caf5395..15ae46e 100644 (file)
@@ -2,26 +2,18 @@
 defined('TYPO3_MODE') or die();
 
 if (TYPO3_MODE === 'BE') {
-       // Add module
-       \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addModulePath(
-               'web_txrecyclerM1',
-               \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath($_EXTKEY) . 'mod1/'
-       );
-       \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addModule(
+       \TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerModule(
+               'TYPO3.CMS.' . $_EXTKEY,
                'web',
-               'txrecyclerM1',
+               'Recycler',
                '',
-               \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath($_EXTKEY) . 'mod1/',
                array(
-                       'script' => '_DISPATCH',
+                       'RecyclerModule' => 'index',
+               ),
+               array(
                        'access' => 'user,group',
-                       'name' => 'web_txrecyclerM1',
-                       'labels' => array(
-                               'tabs_images' => array(
-                                       'tab' => '../Resources/Public/Icons/module-recycler.png',
-                               ),
-                               'll_ref' => 'LLL:EXT:recycler/mod1/locallang_mod.xlf',
-                       ),
+                       'icon' => 'EXT:recycler/Resources/Public/Icons/module-recycler.png',
+                       'labels' => 'LLL:EXT:' . $_EXTKEY . '/Resources/Private/Language/locallang_mod.xlf',
                )
        );
-}
+}
\ No newline at end of file
diff --git a/typo3/sysext/recycler/mod1/clear.gif b/typo3/sysext/recycler/mod1/clear.gif
deleted file mode 100644 (file)
index e1d2d83..0000000
Binary files a/typo3/sysext/recycler/mod1/clear.gif and /dev/null differ
diff --git a/typo3/sysext/recycler/mod1/index.php b/typo3/sysext/recycler/mod1/index.php
deleted file mode 100644 (file)
index e21d0d2..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-<?php
-/*
- * This file is part of the TYPO3 CMS project.
- *
- * It is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License, either version 2
- * of the License, or any later version.
- *
- * For the full copyright and license information, please read the
- * LICENSE.txt file that was distributed with this source code.
- *
- * The TYPO3 project - inspiring people to share!
- */
-
-$SOBE = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Recycler\Controller\RecyclerModuleController::class);
-$SOBE->initialize();
-$SOBE->render();
-$SOBE->flush();
diff --git a/typo3/sysext/recycler/mod1/locallang.xlf b/typo3/sysext/recycler/mod1/locallang.xlf
deleted file mode 100644 (file)
index 6775f03..0000000
+++ /dev/null
@@ -1,113 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<xliff version="1.0" xmlns:t3="http://typo3.org/schemas/xliff">
-       <file t3:id="1415814907" source-language="en" datatype="plaintext" original="messages" date="2011-10-17T20:22:35Z" product-name="recycler">
-               <header/>
-               <body>
-                       <trans-unit id="title" xml:space="preserve">
-                               <source>Recycler</source>
-                       </trans-unit>
-                       <trans-unit id="description" xml:space="preserve">
-                               <source>The recycler allows you to select any deleted data and undelete it. You can undelete recursive if the parent of the element is deleted too.</source>
-                       </trans-unit>
-                       <trans-unit id="label_alltables" xml:space="preserve">
-                               <source>All tables</source>
-                       </trans-unit>
-                       <trans-unit id="js.label_lostandfound" xml:space="preserve">
-                               <source>Lost and found</source>
-                       </trans-unit>
-                       <trans-unit id="js.label_deletedTab" xml:space="preserve">
-                               <source>Deleted data</source>
-                       </trans-unit>
-                       <trans-unit id="js.label_loadMessage" xml:space="preserve">
-                               <source>Please wait...&lt;br/&gt;Records would be loaded!</source>
-                       </trans-unit>
-                       <trans-unit id="js.label_doDelete_confirmText" xml:space="preserve">
-                               <source>Delete this record AND if page ALL subrecords ? &lt;br/&gt;ATTENTION: Data will be finally deleted from the database</source>
-                       </trans-unit>
-                       <trans-unit id="js.label_deleteButton_text" xml:space="preserve">
-                               <source>Delete</source>
-                       </trans-unit>
-                       <trans-unit id="js.label_deleteButton_tooltip" xml:space="preserve">
-                               <source>Delete selected items</source>
-                       </trans-unit>
-                       <trans-unit id="js.label_undeleteButton_text" xml:space="preserve">
-                               <source>Undelete</source>
-                       </trans-unit>
-                       <trans-unit id="js.label_undeleteButton_tooltip" xml:space="preserve">
-                               <source>Undelete selected items</source>
-                       </trans-unit>
-                       <trans-unit id="js.label_error_NoSelectedRows_title" xml:space="preserve">
-                               <source>No row selected</source>
-                       </trans-unit>
-                       <trans-unit id="js.label_error_NoSelectedRows_msg" xml:space="preserve">
-                               <source>You must select a row!</source>
-                       </trans-unit>
-                       <trans-unit id="js.label_yes" xml:space="preserve">
-                               <source>Yes</source>
-                       </trans-unit>
-                       <trans-unit id="js.label_no" xml:space="preserve">
-                               <source>No</source>
-                       </trans-unit>
-                       <trans-unit id="js.label_sure" xml:space="preserve">
-                               <source>Are you Sure?</source>
-                       </trans-unit>
-                       <trans-unit id="js.label_crdate" xml:space="preserve">
-                               <source>Created</source>
-                       </trans-unit>
-                       <trans-unit id="js.label_owner" xml:space="preserve">
-                               <source>Owner</source>
-                       </trans-unit>
-                       <trans-unit id="js.label_tstamp" xml:space="preserve">
-                               <source>Last edit</source>
-                       </trans-unit>
-                       <trans-unit id="js.label_clear" xml:space="preserve">
-                               <source>Clear</source>
-                       </trans-unit>
-                       <trans-unit id="js.label_filter" xml:space="preserve">
-                               <source>Filter</source>
-                       </trans-unit>
-                       <trans-unit id="js.label_title_undelete" xml:space="preserve">
-                               <source>Undelete?</source>
-                       </trans-unit>
-                       <trans-unit id="js.label_text_undelete" xml:space="preserve">
-                               <source>Undelete records from tables: </source>
-                       </trans-unit>
-                       <trans-unit id="js.label_title_delete" xml:space="preserve">
-                               <source>Delete?</source>
-                       </trans-unit>
-                       <trans-unit id="js.label_text_delete" xml:space="preserve">
-                               <source>Delete records from tables: </source>
-                       </trans-unit>
-                       <trans-unit id="js.label_boxLabel_undelete_recursive" xml:space="preserve">
-                               <source>Undelete recursively</source>
-                       </trans-unit>
-                       <trans-unit id="js.label_tableMenu_label" xml:space="preserve">
-                               <source>Table:</source>
-                       </trans-unit>
-                       <trans-unit id="js.label_tableMenu_emptyText" xml:space="preserve">
-                               <source>Choose a table...</source>
-                       </trans-unit>
-                       <trans-unit id="js.label_filter_emptyText" xml:space="preserve">
-                               <source>Keyword</source>
-                       </trans-unit>
-                       <trans-unit id="js.label_search" xml:space="preserve">
-                               <source>Search:</source>
-                       </trans-unit>
-                       <trans-unit id="js.label_pagingMessage" xml:space="preserve">
-                               <source>Displaying records {0} - {1} of {2}</source>
-                       </trans-unit>
-                       <trans-unit id="js.label_pagingEmpty" xml:space="preserve">
-                               <source>No records to display</source>
-                       </trans-unit>
-                       <trans-unit id="js.label_noValueFound" xml:space="preserve">
-                               <source>No records for {0}</source>
-                       </trans-unit>
-                       <trans-unit id="js.label_records" xml:space="preserve">
-                               <source>Records</source>
-                       </trans-unit>
-                       <trans-unit id="js.label_table" xml:space="preserve">
-                               <source>Table</source>
-                       </trans-unit>
-               </body>
-       </file>
-</xliff>
diff --git a/typo3/sysext/recycler/mod1/locallang_mod.xlf b/typo3/sysext/recycler/mod1/locallang_mod.xlf
deleted file mode 100644 (file)
index 7cdb061..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<xliff version="1.0" xmlns:t3="http://typo3.org/schemas/xliff">
-       <file t3:id="1415814908" source-language="en" datatype="plaintext" original="messages" date="2011-10-17T20:22:35Z" product-name="recycler">
-               <header/>
-               <body>
-                       <trans-unit id="mlang_tabs_tab" xml:space="preserve">
-                               <source>Recycler</source>
-                       </trans-unit>
-                       <trans-unit id="mlang_labels_tablabel" xml:space="preserve">
-                               <source>TYPO3 Recycler</source>
-                       </trans-unit>
-                       <trans-unit id="mlang_labels_tabdescr" xml:space="preserve">
-                               <source>The recycler offers the possibility to restore deleted records or remove them from the database permanently. These actions can be applied to a single record, multiple records, and recursively to child records (ex. restoring a page can restore all content elements on that page). Filtering by page and by table provides a quick overview of deleted records before taking action on them.</source>
-                       </trans-unit>
-               </body>
-       </file>
-</xliff>
diff --git a/typo3/sysext/recycler/mod1/mod_template.html b/typo3/sysext/recycler/mod1/mod_template.html
deleted file mode 100644 (file)
index f5f96bd..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-<!-- ###FULLDOC### begin -->
-<div class="typo3-fullDoc">
-       <div id="typo3-docheader">
-               <div class="typo3-docheader-functions">
-                       <div class="left">###FUNC_MENU###</div>
-                       <div class="right">###PAGEPATH######PAGEINFO###</div>
-               </div>
-               <div class="typo3-docheader-buttons">
-                       <div class="left">###BUTTONLIST_LEFT###</div>
-                       <div class="right">###BUTTONLIST_RIGHT###</div>
-               </div>
-       </div>
-
-       <div id="typo3-docbody">
-               <div id="typo3-inner-docbody">
-                       ###CONTENT###
-               </div>
-       </div>
-</div>
-<!-- ###FULLDOC### end -->
-
-<!-- Grouping the icons on top -->
-
-<!-- ###BUTTON_GROUP_WRAP### -->
-<div class="buttongroup">###BUTTONS###</div>
-<!-- ###BUTTON_GROUP_WRAP### -->
-
-<!-- ###BUTTON_GROUPS_LEFT### -->
-<!-- ###BUTTON_GROUP1### -->###SAVE###<!-- ###BUTTON_GROUP1### -->
-<!-- ###BUTTON_GROUPS_LEFT### -->
-
-<!-- ###BUTTON_GROUPS_RIGHT### -->
-<!-- ###BUTTON_GROUP1### -->###SHORTCUT###<!-- ###BUTTON_GROUP1### -->
-<!-- ###BUTTON_GROUPS_RIGHT### -->
\ No newline at end of file
diff --git a/typo3/sysext/recycler/mod1/moduleicon.gif b/typo3/sysext/recycler/mod1/moduleicon.gif
deleted file mode 100644 (file)
index 3fc6c52..0000000
Binary files a/typo3/sysext/recycler/mod1/moduleicon.gif and /dev/null differ
diff --git a/typo3/sysext/recycler/res/css/customExtJs.css b/typo3/sysext/recycler/res/css/customExtJs.css
deleted file mode 100644 (file)
index dfad426..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-#recyclerContent {
-       margin-top: 10px;
-}
-
-.recycler-messagebox {
-       padding: 10px;
-}
-
-ul.recycler-table-list {
-       list-style: disc;
-       margin: 5px 0 5px 15px;
-}
-
-ul.recycler-table-list li {
-       margin-left: 10px;
-}
-
-dl.recycler-table-list-entry-details {
-       margin-left: 45px;
-}
-
-dl.recycler-table-list-entry-details dt {
-       font-weight: bold;
-       float: left;
-       clear: left;
-       margin-right: 3px;
-}
-
-button.delete {
-       background-image: url('../icons/delete.gif');
-}
-
-button.undelete {
-       background-image: url('../icons/arrow_rotate_anticlockwise.png');
-}
-
-button.backup {
-       background-image: url('../icons/database_save.png');
-}
-
-button.filter_refresh {
-       background-image: url('../icons/filter_refresh.png');
-}
-
-button.filter_clear {
-       background-image: url('../icons/filter_clear.png');
-}
\ No newline at end of file
diff --git a/typo3/sysext/recycler/res/icons/accept.png b/typo3/sysext/recycler/res/icons/accept.png
deleted file mode 100644 (file)
index b3141e1..0000000
Binary files a/typo3/sysext/recycler/res/icons/accept.png and /dev/null differ
diff --git a/typo3/sysext/recycler/res/icons/arrow_redo.png b/typo3/sysext/recycler/res/icons/arrow_redo.png
deleted file mode 100644 (file)
index 7504a19..0000000
Binary files a/typo3/sysext/recycler/res/icons/arrow_redo.png and /dev/null differ
diff --git a/typo3/sysext/recycler/res/icons/arrow_rotate_anticlockwise.png b/typo3/sysext/recycler/res/icons/arrow_rotate_anticlockwise.png
deleted file mode 100644 (file)
index 7f8ffc1..0000000
Binary files a/typo3/sysext/recycler/res/icons/arrow_rotate_anticlockwise.png and /dev/null differ
diff --git a/typo3/sysext/recycler/res/icons/bin.png b/typo3/sysext/recycler/res/icons/bin.png
deleted file mode 100644 (file)
index 1168cf9..0000000
Binary files a/typo3/sysext/recycler/res/icons/bin.png and /dev/null differ
diff --git a/typo3/sysext/recycler/res/icons/bin_closed.png b/typo3/sysext/recycler/res/icons/bin_closed.png
deleted file mode 100644 (file)
index e5fae88..0000000
Binary files a/typo3/sysext/recycler/res/icons/bin_closed.png and /dev/null differ
diff --git a/typo3/sysext/recycler/res/icons/bin_empty.png b/typo3/sysext/recycler/res/icons/bin_empty.png
deleted file mode 100644 (file)
index 16d3b46..0000000
Binary files a/typo3/sysext/recycler/res/icons/bin_empty.png and /dev/null differ
diff --git a/typo3/sysext/recycler/res/icons/database_save.png b/typo3/sysext/recycler/res/icons/database_save.png
deleted file mode 100644 (file)
index 9603e3c..0000000
Binary files a/typo3/sysext/recycler/res/icons/database_save.png and /dev/null differ
diff --git a/typo3/sysext/recycler/res/icons/delete.gif b/typo3/sysext/recycler/res/icons/delete.gif
deleted file mode 100644 (file)
index 8b9cc34..0000000
Binary files a/typo3/sysext/recycler/res/icons/delete.gif and /dev/null differ
diff --git a/typo3/sysext/recycler/res/icons/filter_clear.png b/typo3/sysext/recycler/res/icons/filter_clear.png
deleted file mode 100644 (file)
index c7c6287..0000000
Binary files a/typo3/sysext/recycler/res/icons/filter_clear.png and /dev/null differ
diff --git a/typo3/sysext/recycler/res/icons/filter_refresh.png b/typo3/sysext/recycler/res/icons/filter_refresh.png
deleted file mode 100644 (file)
index 2999ee3..0000000
Binary files a/typo3/sysext/recycler/res/icons/filter_refresh.png and /dev/null differ
diff --git a/typo3/sysext/recycler/res/icons/loading.gif b/typo3/sysext/recycler/res/icons/loading.gif
deleted file mode 100644 (file)
index a7f67be..0000000
Binary files a/typo3/sysext/recycler/res/icons/loading.gif and /dev/null differ
diff --git a/typo3/sysext/recycler/res/icons/recycler.gif b/typo3/sysext/recycler/res/icons/recycler.gif
deleted file mode 100644 (file)
index ebad828..0000000
Binary files a/typo3/sysext/recycler/res/icons/recycler.gif and /dev/null differ
diff --git a/typo3/sysext/recycler/res/icons/recycler2.gif b/typo3/sysext/recycler/res/icons/recycler2.gif
deleted file mode 100644 (file)
index 9f0bf9f..0000000
Binary files a/typo3/sysext/recycler/res/icons/recycler2.gif and /dev/null differ
diff --git a/typo3/sysext/recycler/res/icons/x_toolbar_bg.gif b/typo3/sysext/recycler/res/icons/x_toolbar_bg.gif
deleted file mode 100644 (file)
index aebe6ef..0000000
Binary files a/typo3/sysext/recycler/res/icons/x_toolbar_bg.gif and /dev/null differ
diff --git a/typo3/sysext/recycler/res/js/t3_recycler.js b/typo3/sysext/recycler/res/js/t3_recycler.js
deleted file mode 100644 (file)
index 9025978..0000000
+++ /dev/null
@@ -1,534 +0,0 @@
-/*
- * This file is part of the TYPO3 CMS project.
- *
- * It is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License, either version 2
- * of the License, or any later version.
- *
- * For the full copyright and license information, please read the
- * LICENSE.txt file that was distributed with this source code.
- *
- * The TYPO3 project - inspiring people to share!
- */
-
-/**
- * ExtJS for the 'recycler' extension.
- * Contains the Recycler functions
- *
- * @author     Julian Kleinhans <typo3@kj187.de>
- * @author  Erik Frister <erik_frister@otq-solutions.com>
- * @author  Steffen Kamper <steffen@typo3.org>
- */
-
-Ext.ns('Recycler');
-
-/****************************************************
- * row expander
- ****************************************************/
-Recycler.Expander = new Ext.grid.RowExpander({
-       tpl : new Ext.Template(
-               '<dl class="recycler-table-list-entry-details">' +
-                       '<dt>' + TYPO3.l10n.localize('table') + ': </dt><dd>{table}</dd>' +
-                       '<dt>' + TYPO3.l10n.localize('crdate') + ': </dt><dd>{crdate}</dd>' +
-                       '<dt>' + TYPO3.l10n.localize('tstamp') + ': </dt><dd>{tstamp}</dd>' +
-                       '<dt>' + TYPO3.l10n.localize('owner') + ': </dt><dd>{owner} (UID: {owner_uid})</dd>' +
-                       '<dt>' + TYPO3.l10n.localize('path') + ': </dt><dd>{path}</dd>' +
-               '</dl>'
-       )
-});
-
-
-/****************************************************
- * Main store
- ****************************************************/
-Recycler.MainStore = new Ext.data.Store({
-       storeId: 'deletedRecordsStore',
-       reader: new Ext.data.JsonReader({
-               totalProperty: 'total',
-               root: 'rows'
-       }, [
-               {name: 'uid', type: 'int'},
-               {name: 'pid', type: 'int'},
-               {name: 'record', mapping: 'title'},
-               {name: 'crdate'},
-               {name: 'tstamp'},
-               {name: 'owner'},
-               {name: 'owner_uid'},
-               {name: 'tableTitle'},
-               {name: 'table'},
-               {name: 'path'}
-       ]),
-       sortInfo: {
-               field: 'record',
-               direction: "ASC"
-       },
-       groupField: 'table',
-       url: TYPO3.settings.ajaxUrls['RecyclerAjaxController::init'] + '&cmd=getDeletedRecords',
-       baseParams: {
-               depth: TYPO3.settings.Recycler.depthSelection,
-               startUid: TYPO3.settings.Recycler.startUid,
-               pagingSizeDefault: TYPO3.settings.Recycler.pagingSize,
-               table: TYPO3.settings.Recycler.tableSelection
-       }
-
-});
-
-/****************************************************
- * Simple table store
- ****************************************************/
-Recycler.TableStore = new Ext.data.Store({
-       url: TYPO3.settings.ajaxUrls['RecyclerAjaxController::init'] + '&startUid=' + TYPO3.settings.Recycler.startUid + '&cmd=getTables' + '&depth=' + TYPO3.settings.Recycler.depthSelection,
-       reader: new Ext.data.ArrayReader({}, [
-               {name: 'table', type: 'string'},
-               {name: 'records', type: 'int'},
-               {name: 'valueField', type: 'string'},
-               {name: 'tableTitle', type: 'string'},
-               {name: 'tstamp', type: 'int'}
-       ]),
-       listeners: {
-               'load': {
-                       fn: function(store, records) {
-                               Ext.getCmp('tableSelector').setValue(TYPO3.settings.Recycler.tableSelection);
-                       },
-                       single: true
-               }
-       }
-})
-
-/****************************************************
- * Confirmation Window
- ****************************************************/
-Recycler.ConfirmWindow = Ext.extend(Ext.Window, {
-
-       width: 300,
-       height: 200,
-
-       title: '',
-       confirmText: '',
-       confirmQuestion: '',
-       records: [],
-       hideRecursive: false,
-       showRecursiveCheckbox: false,
-       arePagesAffected: false,
-       command: '',
-       template: new Ext.XTemplate(
-                       '<ul class="recycler-table-list">',
-                       '<tpl for=".">',
-                               '<li>{[values]}</li>',
-                       '</tpl>',
-                       '</ul>'
-       ),
-       initComponent:function() {
-               Ext.apply(this, {
-                       xtype: 'form',
-                       bodyCssClass: 'recycler-messagebox',
-                       modal: true,
-
-                       items: [
-                               {
-                                       xtype: 'label',
-                                       text: this.confirmText
-                               }, {
-                                       xtype: 'displayfield',
-                                       tpl:  this.template,
-                                       data: this.tables
-                               }, {
-                                       xtype: 'label',
-                                       text:  this.confirmQuestion
-                               }, {
-                                       xtype: 'checkbox',
-                                       boxLabel: TYPO3.l10n.localize('boxLabel_undelete_recursive'),
-                                       name: 'recursiveCheckbox',
-                                       disabled: !this.showRecursiveCheckbox,
-                                       itemId: 'recursiveCheck',
-                                       hidden: this.hideRecursive // hide the checkbox when frm is used to permanently delete
-                               }
-                       ],
-                       buttons: [
-                               {
-                                       text: TYPO3.l10n.localize('yes'),
-                                       scope: this,
-                                       handler: function(button, event) {
-                                               var tcemainData = [];
-
-                                               for (var i=0; i < this.records.length; i++) {
-                                                       tcemainData[i] = [this.records[i].data.table, this.records[i].data.uid];
-                                               }
-                                               Ext.Ajax.request({
-                                                       url: TYPO3.settings.ajaxUrls['RecyclerAjaxController::init'] + '&cmd=' + this.command,
-                                                       params: {
-                                                               'data': Ext.encode(tcemainData),
-                                                               'recursive': this.getComponent('recursiveCheck').getValue()
-                                                       },
-                                                       callback: function(options, success, response) {
-                                                               if (response.responseText === "1") {
-                                                                       // reload the records and the table selector
-                                                                       Recycler.MainStore.reload();
-                                                                       Recycler.TableStore.reload();
-                                                                       if (this.arePagesAffected) {
-                                                                               Recycler.Utility.updatePageTree();
-                                                                       }
-                                                               } else {
-                                                                       Ext.MessageBox.show({
-                                                                               title: 'ERROR',
-                                                                               msg: response.responseText,
-                                                                               buttons: Ext.MessageBox.OK,
-                                                                               icon: Ext.MessageBox.ERROR
-                                                                       });
-                                                               }
-                                                       }
-                                               });
-
-                                               this.close();
-                                       }
-                               },{
-                                       text: TYPO3.l10n.localize('no'),
-                                       scope: this,
-                                       handler: function(button, event) {
-                                               this.close();
-                                       }
-                               }
-                       ]
-               });
-               Recycler.ConfirmWindow.superclass.initComponent.apply(this, arguments);
-       }
-});
-
-/****************************************************
- * Utility functions
- ****************************************************/
-Recycler.Utility = {
-       updatePageTree: function() {
-               if (top && top.content && top.content.nav_frame && top.content.nav_frame.Tree) {
-                       top.content.nav_frame.Tree.refresh();
-               }
-       },
-
-       // not used?
-       filterGrid: function(grid, component) {
-               var filterText = component.getValue();
-
-               Recycler.MainStore.setBaseParam('filterTxt', filterText);
-               // load the datastore
-               Recycler.MainStore.load({
-                       params: {
-                               start: 0
-                       }
-               });
-       },
-
-       /****************************************************
-        * permanent deleting function
-        ****************************************************/
-
-       function_delete: function(button, event) {
-               Recycler.Utility.rowAction(
-                       'doDelete',
-                       TYPO3.l10n.localize('cmd_doDelete_confirmText'),
-                       TYPO3.l10n.localize('title_delete'),
-                       TYPO3.l10n.localize('text_delete')
-               );
-       },
-
-       /****************************************************
-        * Undeleting function
-        ****************************************************/
-
-       function_undelete: function(button, event) {
-               Recycler.Utility.rowAction(
-                       'doUndelete',
-                       TYPO3.l10n.localize('sure'),
-                       TYPO3.l10n.localize('title_undelete'),
-                       TYPO3.l10n.localize('text_undelete')
-               );
-       },
-
-       /****************************************************
-        * Row action function   ( deleted or undeleted )
-        ****************************************************/
-
-       rowAction: function(command, confirmQuestion, confirmTitle, confirmText) {
-                       // get the 'undeleted records' grid object
-               var records = Recycler.Grid.getSelectionModel().getSelections();
-
-               if (records.length > 0) {
-
-                               // check if a page is checked
-                       var recursiveCheckbox = false;
-                       var arePagesAffected = false;
-                       var tables = [];
-                       var hideRecursive = ('doDelete' == command);
-
-                       for (iterator=0; iterator < records.length; iterator++) {
-                               if (tables.indexOf(records[iterator].data.table) < 0) {
-                                       tables.push(records[iterator].data.table);
-                               }
-                               if (command == 'doUndelete' && records[iterator].data.table == 'pages' ) {
-                                       recursiveCheckbox = true;
-                                       arePagesAffected = true;
-                               }
-                       }
-
-                       var frmConfirm = new Recycler.ConfirmWindow({
-                               title: confirmTitle,
-                               records: records,
-                               tables: tables,
-                               confirmText: confirmText,
-                               confirmQuestion: confirmQuestion,
-                               hideRecursive: hideRecursive,
-                               recursiveCheckbox: recursiveCheckbox,
-                               arePagesAffected: arePagesAffected,
-                               command: command
-                       }).show();
-
-               } else {
-                               // no row selected
-                       Ext.MessageBox.show({
-                               title: TYPO3.l10n.localize('error_NoSelectedRows_title'),
-                               msg: TYPO3.l10n.localize('error_NoSelectedRows_msg'),
-                               buttons: Ext.MessageBox.OK,
-                               minWidth: 300,
-                               minHeight: 200,
-                               icon: Ext.MessageBox.ERROR
-                       });
-               }
-       },
-
-       /****************************************************
-        * pluggable renderer
-        ****************************************************/
-
-       renderTopic: function (value, p, record) {
-               return String.format('{0}', value, record.data.table, record.data.uid, record.data.pid);
-       }
-};
-
-/****************************************************
- * Grid SelectionModel
- ****************************************************/
-Recycler.SelectionModel = new Ext.grid.CheckboxSelectionModel({
-       singleSelect: false
-});
-
-/****************************************************
- * Grid container
- ****************************************************/
-Recycler.GridContainer = Ext.extend(Ext.grid.GridPanel, {
-       layout: 'fit',
-       renderTo: TYPO3.settings.Recycler.renderTo,
-       width: '98%',
-       frame: true,
-       border: false,
-       defaults: {autoScroll: false},
-       plain: true,
-
-       initComponent : function() {
-               Ext.apply(this, {
-                       id: 'delRecordId',
-                       stateful: true,
-                       stateId: 'recyclerGrid',
-                       stateEvents: ['columnmove', 'columnresize', 'sortchange', 'expand', 'collapse'],
-                       loadMask: true,
-                       stripeRows: true,
-                       collapsible: false,
-                       animCollapse: false,
-                       store: Recycler.MainStore,
-                       cm: new Ext.grid.ColumnModel([
-                               Recycler.SelectionModel,
-                               Recycler.Expander,
-                               {header: "UID", width: 10, sortable: true, dataIndex: 'uid'},
-                               {header: "PID", width: 10, sortable: true, dataIndex: 'pid'},
-                               {id: 'record', header: TYPO3.l10n.localize('records'), width: 50, sortable: true, dataIndex: 'record', renderer: Recycler.Utility.renderTopic},
-                               {id: 'table', header: TYPO3.l10n.localize('table'), width: 15, sortable: true, dataIndex: 'tableTitle'},
-                               {id: 'tstamp', header: TYPO3.l10n.localize('tstamp'), width: 15, sortable: true, dataIndex: 'tstamp'}
-                       ]),
-                       viewConfig: {
-                               forceFit: true
-                       },
-                       sm: Recycler.SelectionModel,
-                       plugins: [Recycler.Expander, new Ext.ux.plugins.FitToParent()],
-                       bbar: [
-                               {
-
-                                       /****************************************************
-                                        * Paging toolbar
-                                        ****************************************************/
-                                       id: 'recordPaging',
-                                       xtype: 'paging',
-                                       store: Recycler.MainStore,
-                                       pageSize: TYPO3.settings.Recycler.pagingSize,
-                                       displayInfo: true,
-                                       displayMsg: TYPO3.l10n.localize('pagingMessage'),
-                                       emptyMsg: TYPO3.l10n.localize('pagingEmpty')
-                               }, '-', {
-                                       /****************************************************
-                                        * Delete button
-                                        ****************************************************/
-                                       xtype: 'button',
-                                       width: 80,
-                                       id: 'deleteButton',
-                                       text: TYPO3.l10n.localize('deleteButton_text'),
-                                       tooltip: TYPO3.l10n.localize('deleteButton_tooltip'),
-                                       iconCls: 'delete',
-                                       disabled: TYPO3.settings.Recycler.deleteDisable,
-                                       handler: Recycler.Utility.function_delete
-                               }, {
-                                       /****************************************************
-                                        * Undelete button
-                                        ****************************************************/
-                                       xtype: 'button',
-                                       width: 80,
-                                       id: 'undeleteButton',
-                                       text: TYPO3.l10n.localize('undeleteButton_text'),
-                                       tooltip: TYPO3.l10n.localize('undeleteButton_tooltip'),
-                                       iconCls: 'undelete',
-                                       handler: Recycler.Utility.function_undelete
-                               }
-                       ],
-
-                       tbar: [
-                               TYPO3.l10n.localize('search'), ' ',
-                                       new Ext.app.SearchField({
-                                       store: Recycler.MainStore,
-                                       width: 200
-                               }),
-                               '-', {
-                                       xtype: 'tbtext',
-                                       text: TYPO3.l10n.localize('depth') + ':'
-                               },{
-
-                                       /****************************************************
-                                        * Depth menu
-                                        ****************************************************/
-
-                                       xtype: 'combo',
-                                       stateful: true,
-                                       stateId: 'depthCombo',
-                                       stateEvents: ['select'],
-                                       width: 150,
-                                       lazyRender: true,
-                                       valueField: 'depth',
-                                       displayField: 'label',
-                                       id: 'depthSelector',
-                                       mode: 'local',
-                                       emptyText: TYPO3.l10n.localize('depth'),
-                                       selectOnFocus: true,
-                                       triggerAction: 'all',
-                                       editable: false,
-                                       forceSelection: true,
-                                       hidden: TYPO3.l10n.localize('showDepthMenu'),
-                                       store: new Ext.data.SimpleStore({
-                                               autoLoad: true,
-                                               fields: ['depth','label'],
-                                               data : [
-                                                       ['0', TYPO3.l10n.localize('depth_0')],
-                                                       ['1', TYPO3.l10n.localize('depth_1')],
-                                                       ['2', TYPO3.l10n.localize('depth_2')],
-                                                       ['3', TYPO3.l10n.localize('depth_3')],
-                                                       ['4', TYPO3.l10n.localize('depth_4')],
-                                                       ['999', TYPO3.l10n.localize('depth_infi')]
-                                               ]
-                                       }),
-                                       value: TYPO3.settings.Recycler.depthSelection,
-                                       listeners: {
-                                               'select': {
-                                                       fn: function(cmp, rec, index) {
-                                                               var depth = rec.get('depth');
-                                                               Recycler.MainStore.setBaseParam('depth', depth);
-                                                               Recycler.MainStore.load({
-                                                                       params: {
-                                                                               start: 0
-                                                                       }
-                                                               });
-
-                                                               Ext.getCmp('tableSelector').store.load({
-                                                                       params: {
-                                                                               depth: depth
-                                                                       }
-                                                               });
-                                                       }
-                                               }
-                                       }
-                               },'-',{
-                                       xtype: 'tbtext',
-                                       text: TYPO3.l10n.localize('tableMenu_label')
-                               },{
-
-                                       /****************************************************
-                                        * Table menu
-                                        ****************************************************/
-
-                                       xtype: 'combo',
-                                       lazyRender: true,
-                                       stateful: true,
-                                       stateId: 'tableCombo',
-                                       stateEvents: ['select'],
-                                       valueField: 'valueField',
-                                       displayField: 'tableTitle',
-                                       id: 'tableSelector',
-                                       width: 220,
-                                       mode: 'local',
-                                       emptyText: TYPO3.l10n.localize('tableMenu_emptyText'),
-                                       selectOnFocus: true,
-                                       triggerAction: 'all',
-                                       editable: false,
-                                       forceSelection: true,
-
-                                       store: Recycler.TableStore,
-                                       valueNotFoundText: String.format(TYPO3.l10n.localize('noValueFound'), TYPO3.settings.Recycler.tableSelection),
-                                       tpl: '<tpl for="."><tpl if="records &gt; 0"><div ext:qtip="{table} ({records})" class="x-combo-list-item">{tableTitle} ({records}) </div></tpl><tpl if="records &lt; 1"><div ext:qtip="{table} ({records})" class="x-combo-list-item x-item-disabled">{tableTitle} ({records}) </div></tpl></tpl>',
-                                       listeners: {
-                                               'select': {
-                                                       fn: function(component, record, index) {
-                                                               var table = record.get('valueField');
-
-                                                               // do not reload if the table selected has no deleted records - hide all records
-                                                               if (record.get('records') <= 0) {
-                                                                       Recycler.MainStore.filter('uid', '-1'); // never true
-                                                                       return false;
-                                                               }
-                                                               Recycler.MainStore.setBaseParam('table', table);
-                                                               Recycler.MainStore.load({
-                                                                       params: {
-                                                                               start: 0
-                                                                       }
-                                                               });
-                                                       }
-                                               }
-                                       }
-                               }
-                       ]
-               });
-               Recycler.GridContainer.superclass.initComponent.apply(this, arguments);
-               Recycler.TableStore.load();
-       }
-});
-
-Recycler.App = {
-       /**
-        * Initializes the recycler
-        *
-        * @return void
-        **/
-       init: function() {
-               Recycler.Grid = new Recycler.GridContainer();
-               Recycler.MainStore.load();
-       }
-};
-
-Ext.onReady(function(){
-
-               //save states in BE_USER->uc
-       Ext.state.Manager.setProvider(new TYPO3.state.ExtDirectProvider({
-               key: 'moduleData.web_recycler.States'
-       }));
-
-       if (Ext.isObject(TYPO3.settings.Recycler.States)) {
-               Ext.state.Manager.getProvider().initState(TYPO3.settings.Recycler.States);
-       }
-
-       // disable loadindicator
-       Ext.UpdateManager.defaults.showLoadIndicator = false;
-       // fire recycler grid
-       Recycler.App.init();
-});
index 86e7b5c..8ea76f6 100644 (file)
@@ -130,6 +130,10 @@ table {
                position: relative;
                display: inline-block;
 
+               &.btn-group {
+                       float: none;
+               }
+
                .btn-checkbox {
                        position: absolute;
                        top: 0;
index 091e702..f83ac13 100644 (file)
@@ -8808,6 +8808,9 @@ fieldset[disabled] .table .btn.btn-primary.active {
   position: relative;
   display: inline-block;
 }
+.table .btn-checkbox-holder.btn-group {
+  float: none;
+}
 .table .btn-checkbox-holder .btn-checkbox {
   position: absolute;
   top: 0;