diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/Modal.js b/typo3/sysext/backend/Resources/Public/JavaScript/Modal.js index 21e9f2db8a45eec8488b4241a0be91f9f9a65195..3ae3685b40783413576d9581984024712aa2d422 100644 --- a/typo3/sysext/backend/Resources/Public/JavaScript/Modal.js +++ b/typo3/sysext/backend/Resources/Public/JavaScript/Modal.js @@ -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> diff --git a/typo3/sysext/recycler/Classes/Controller/DeletedRecordsController.php b/typo3/sysext/recycler/Classes/Controller/DeletedRecordsController.php index 690b7fcf69fcf2b2d1431b6aa2e47694d2328fdc..9b41de009896b03178fffe8bebdc801d27cc4cd4 100644 --- a/typo3/sysext/recycler/Classes/Controller/DeletedRecordsController.php +++ b/typo3/sysext/recycler/Classes/Controller/DeletedRecordsController.php @@ -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 diff --git a/typo3/sysext/recycler/Classes/Controller/RecyclerAjaxController.php b/typo3/sysext/recycler/Classes/Controller/RecyclerAjaxController.php index 00277e53bf99a3919318fd6dd1c7f09c8a09f2fa..16e65cad431cf26a6b2aa8c76c154c1c495ea224 100644 --- a/typo3/sysext/recycler/Classes/Controller/RecyclerAjaxController.php +++ b/typo3/sysext/recycler/Classes/Controller/RecyclerAjaxController.php @@ -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']; + } + } diff --git a/typo3/sysext/recycler/Classes/Controller/RecyclerModuleController.php b/typo3/sysext/recycler/Classes/Controller/RecyclerModuleController.php index 6c2cf346fb0446b2fe131aad22251b2fe274a8f5..13bd3a5ae9941d3da7fd4f4347c7671556c1b64f 100644 --- a/typo3/sysext/recycler/Classes/Controller/RecyclerModuleController.php +++ b/typo3/sysext/recycler/Classes/Controller/RecyclerModuleController.php @@ -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 @@ -55,70 +51,33 @@ 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(); } - } diff --git a/typo3/sysext/recycler/Classes/Domain/Model/DeletedRecords.php b/typo3/sysext/recycler/Classes/Domain/Model/DeletedRecords.php index 2820d08df885f2cf8f8d5189588118907135ea04..3cc62b28edccfa72e8e0f21e84b5b4d83950c2ad 100644 --- a/typo3/sysext/recycler/Classes/Domain/Model/DeletedRecords.php +++ b/typo3/sysext/recycler/Classes/Domain/Model/DeletedRecords.php @@ -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']; + } } diff --git a/typo3/sysext/recycler/Classes/Domain/Model/Tables.php b/typo3/sysext/recycler/Classes/Domain/Model/Tables.php index 59429a5c57ab76df4c0597571f421545534f9c4e..827d9a17c0bc86ef8af6e833a884d52cc4a9b76f 100644 --- a/typo3/sysext/recycler/Classes/Domain/Model/Tables.php +++ b/typo3/sysext/recycler/Classes/Domain/Model/Tables.php @@ -23,43 +23,22 @@ 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']; + } + } diff --git a/typo3/sysext/recycler/Classes/Utility/RecyclerUtility.php b/typo3/sysext/recycler/Classes/Utility/RecyclerUtility.php index 8848c8c076c86e1a5f788cabbf9a4818a3133e31..49b1462982468680301209b4415bf21e72caf56c 100644 --- a/typo3/sysext/recycler/Classes/Utility/RecyclerUtility.php +++ b/typo3/sysext/recycler/Classes/Utility/RecyclerUtility.php @@ -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 index 0000000000000000000000000000000000000000..9c786c3bd07357cc02ede357777e98cf1cb54e01 --- /dev/null +++ b/typo3/sysext/recycler/Resources/Private/Language/locallang.xlf @@ -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...<br/>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 ? <br/>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/mod1/locallang_mod.xlf b/typo3/sysext/recycler/Resources/Private/Language/locallang_mod.xlf similarity index 100% rename from typo3/sysext/recycler/mod1/locallang_mod.xlf rename to typo3/sysext/recycler/Resources/Private/Language/locallang_mod.xlf diff --git a/typo3/sysext/recycler/Resources/Private/Layouts/Default.html b/typo3/sysext/recycler/Resources/Private/Layouts/Default.html new file mode 100644 index 0000000000000000000000000000000000000000..11270b3567e370e5a225a1b4fbd902aae37439b9 --- /dev/null +++ b/typo3/sysext/recycler/Resources/Private/Layouts/Default.html @@ -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 index 0000000000000000000000000000000000000000..0db4ea43f0d3b24ce14d2013ea874d0b649924f6 --- /dev/null +++ b/typo3/sysext/recycler/Resources/Private/Partials/RecordsTable/DeletedRecord.html @@ -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 index 0000000000000000000000000000000000000000..01ace236a609c8528f7076ceb20bd72b4ee05a91 --- /dev/null +++ b/typo3/sysext/recycler/Resources/Private/Templates/Ajax/RecordsTable.html @@ -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 index 0000000000000000000000000000000000000000..f6ac036e104d23990952f48af4d1055cb5897eaa --- /dev/null +++ b/typo3/sysext/recycler/Resources/Private/Templates/RecyclerModule/Index.html @@ -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 index 0000000000000000000000000000000000000000..920e5a58630ef6e37481b05339d55691e457b405 --- /dev/null +++ b/typo3/sysext/recycler/Resources/Private/Templates/RecyclerModule/NoAccess.html @@ -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 index 0000000000000000000000000000000000000000..ee97b44123a1badbef887083823db919de5d9af7 --- /dev/null +++ b/typo3/sysext/recycler/Resources/Public/JavaScript/Recycler.js @@ -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; + }(); +}); diff --git a/typo3/sysext/recycler/ext_localconf.php b/typo3/sysext/recycler/ext_localconf.php index 1e8bded9343d2646f0930c370cad82c5da30d7b6..7ec4801a7ee738836498143d3be53e22bcaefc1f 100644 --- a/typo3/sysext/recycler/ext_localconf.php +++ b/typo3/sysext/recycler/ext_localconf.php @@ -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'); } diff --git a/typo3/sysext/recycler/ext_tables.php b/typo3/sysext/recycler/ext_tables.php index caf5395ff3ac1ebd550215b2f42026d3e5b6103f..15ae46e0699015bdfd8a2b3dc6bfd153b26ff6c3 100644 --- a/typo3/sysext/recycler/ext_tables.php +++ b/typo3/sysext/recycler/ext_tables.php @@ -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 index e1d2d83216e249399411d7e5caf10b5ece31db3d..0000000000000000000000000000000000000000 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 index e21d0d20956e507247483ce0037e2ed947830371..0000000000000000000000000000000000000000 --- a/typo3/sysext/recycler/mod1/index.php +++ /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 index 6775f038546879022dc410f33995272369d93a5b..0000000000000000000000000000000000000000 --- a/typo3/sysext/recycler/mod1/locallang.xlf +++ /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...<br/>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 ? <br/>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/mod_template.html b/typo3/sysext/recycler/mod1/mod_template.html deleted file mode 100644 index f5f96bd32ebac23a8b92476cc98fb72daf5985b9..0000000000000000000000000000000000000000 --- a/typo3/sysext/recycler/mod1/mod_template.html +++ /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 index 3fc6c52ba0b8e0204a3c4bf24dd79bdd089510e4..0000000000000000000000000000000000000000 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 index dfad426261c0042d1f01f1c1047fdf80e7cd1ecd..0000000000000000000000000000000000000000 --- a/typo3/sysext/recycler/res/css/customExtJs.css +++ /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 index b3141e1f138d9f77f0d064f69bf94cee0a2509a2..0000000000000000000000000000000000000000 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 index 7504a198237d04a37d8415d920ed783f96ab0a77..0000000000000000000000000000000000000000 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 index 7f8ffc16ba3f4e49eb05f32211dd9f79de6d4a15..0000000000000000000000000000000000000000 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 index 1168cf951bdbf620bdb267ff86043bcd424f6957..0000000000000000000000000000000000000000 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 index e5fae88a5ff9f289723d86f2591b439b648e94df..0000000000000000000000000000000000000000 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 index 16d3b461789d2af0f6c98724057fcb3ef0cc0e89..0000000000000000000000000000000000000000 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 index 9603e3cd06f7becb90d6bf2d6a1c5d836e5581c4..0000000000000000000000000000000000000000 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 index 8b9cc3425a78d05ecd51c57104b643b044951be7..0000000000000000000000000000000000000000 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 index c7c6287e1f2822298d1a6db9f55b7368ff5731f0..0000000000000000000000000000000000000000 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 index 2999ee3e8b1b443d3895c022dd2ac0479f62949f..0000000000000000000000000000000000000000 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 index a7f67befff39e97cd07d63c65ac0884534b2e1dd..0000000000000000000000000000000000000000 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 index ebad828a80b90e076fbad964e39b8d82ab17dcac..0000000000000000000000000000000000000000 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 index 9f0bf9fd76e29be6ecc79840c983587767caaadf..0000000000000000000000000000000000000000 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 index aebe6ef5a09b9e44a428d2105321423b25cc9151..0000000000000000000000000000000000000000 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 index 9025978a4f5965b2359dbbe672ca031399c5868d..0000000000000000000000000000000000000000 --- a/typo3/sysext/recycler/res/js/t3_recycler.js +++ /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 > 0"><div ext:qtip="{table} ({records})" class="x-combo-list-item">{tableTitle} ({records}) </div></tpl><tpl if="records < 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(); -}); diff --git a/typo3/sysext/t3skin/Resources/Private/Styles/TYPO3/_element_table.less b/typo3/sysext/t3skin/Resources/Private/Styles/TYPO3/_element_table.less index 86e7b5c4b00effd1af1bc26b7c2863faae329b6a..8ea76f655057bb42876e0f3fefdafb3d9eef3e1e 100644 --- a/typo3/sysext/t3skin/Resources/Private/Styles/TYPO3/_element_table.less +++ b/typo3/sysext/t3skin/Resources/Private/Styles/TYPO3/_element_table.less @@ -130,6 +130,10 @@ table { position: relative; display: inline-block; + &.btn-group { + float: none; + } + .btn-checkbox { position: absolute; top: 0; diff --git a/typo3/sysext/t3skin/Resources/Public/Css/visual/t3skin.css b/typo3/sysext/t3skin/Resources/Public/Css/visual/t3skin.css index 091e70293266157c5483ca03f1119e0722e4cecf..f83ac13c5029846ff623d70b0a79a198767491b5 100644 --- a/typo3/sysext/t3skin/Resources/Public/Css/visual/t3skin.css +++ b/typo3/sysext/t3skin/Resources/Public/Css/visual/t3skin.css @@ -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;