From: Oliver Hader Date: Tue, 10 Mar 2009 22:19:14 +0000 (+0000) Subject: Added feature #10455: Improve recycling of deleted records X-Git-Tag: TYPO3_4-3-0alpha2~18 X-Git-Url: http://git.typo3.org/Packages/TYPO3.CMS.git/commitdiff_plain/c2ca6fedefe4dd3453ab738a4f019174292f0be6 Added feature #10455: Improve recycling of deleted records git-svn-id: https://svn.typo3.org/TYPO3v4/Core/trunk@5180 709f56b5-9817-0410-a4d7-c38de5d9e867 --- diff --git a/ChangeLog b/ChangeLog index bb9c13a10e60..2ac7e0073f63 100755 --- a/ChangeLog +++ b/ChangeLog @@ -7,6 +7,7 @@ * Cleanup: Updated NEWS.txt * Follow-up to issue #10614: Fixed typing error in memcached backend * Added feature #9078: Integrate hook in page module to render preview of records with own CType + * Added feature #10455: Improve recycling of deleted records (thanks to Julian Kleinhans & Erik Frister) 2009-03-10 Francois Suter diff --git a/typo3/sysext/recycler/ChangeLog b/typo3/sysext/recycler/ChangeLog new file mode 100644 index 000000000000..b001d22c0b34 --- /dev/null +++ b/typo3/sysext/recycler/ChangeLog @@ -0,0 +1,61 @@ +2009-03-10 Oliver Hader + + * Fixed bug: Selection of depth and table is not stored to session + * Cleanup: Fixed PHPdoc and styling issues + * Cleanup: Use ext-all.js instead of ext-all-debug.js + +2009-03-09 Oliver Hader + + * Fixed bug: Styling issues in paging input field and top toolbar input fields + * Cleanup: Fixed formatting + * Added feature: Reload page tree when pages are affected in restore action + * Cleanup: Removed empty README.txt file and files generated by the kickstarter + * Cleanup: Fixed class names accordant to file names + * Cleanup: Updated ext_emconf.php and set version to 1.0.1 + * Follow-up: Reverted change of styling of the top toolbar input fields + +2009-02-16 Julian Kleinhans + + * Cleanup: Fixed formatting concerning CGL + * Add doc/manual.sxw + +2009-02-14 Oliver Hader + + * Fixed bug #2563 (Forge): Clean up index.php + * Cleanup: Removed superfluous prefix 'label_' from localisation strings + * Cleanup: Removed superfluous whitespace characters + * Fixed bug: Core labels do not have the correct name + * Cleanup: Renamed recycler_model_delRecords to recycler_model_deleteRecords + * Cleanup: Fixed formatting concerning CGL + * Fixed bug: Filter values is not escaped correctly for database disposal + * Fixed bug: Hanging result sets after database queries + +2009-02-07 Julian Kleinhans + + * Fixed Bug: Wrong total count in pagenavigation + * Fixed Bug: Filter doesnt work correct + * Fixed Bug: All table view + +2009-02-04 Julian Kleinhans + + * Coding guidelines clean up after first review + +2009-01-26 Julian Kleinhans + + * Fixed bug: Change extension to sysext extension + +2009-01-23 Oliver Hader + + * Fixed bug: Show only tables in table menu that have deleted records + * Fixed bug: Use $TCA['ctr']['delete'] to determine the field with the deleted information + * Fixed bug: Use json_encode() instead of string concatenation + * Fixed bug: Displaying of deleted records showd "null" as title when backend is not running in UTF-8 (JSON data must be UTF-8 encoded!) + +2009-01-08 Oliver Hader + + * Fixed bug: Use json_encode() instead of string concatenation in recycler_view_deletedRecords to generate JSON information + * Fixed bug: View is not updated correctly when all deleted records of a table had been removed permanently + +2008-10-20 Julian Kleinhans + + * Initial release \ No newline at end of file diff --git a/typo3/sysext/recycler/classes/controller/class.tx_recycler_controller_ajax.php b/typo3/sysext/recycler/classes/controller/class.tx_recycler_controller_ajax.php new file mode 100644 index 000000000000..d3886c5ac812 --- /dev/null +++ b/typo3/sysext/recycler/classes/controller/class.tx_recycler_controller_ajax.php @@ -0,0 +1,190 @@ + +* All rights reserved +* +* This script is part of the TYPO3 project. The TYPO3 project is +* free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* The GNU General Public License can be found at +* http://www.gnu.org/copyleft/gpl.html. +* +* This script is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* This copyright notice MUST APPEAR in all copies of the script! +***************************************************************/ + + // Pre-Include all models and views +require_once(t3lib_extMgm::extPath('recycler', 'classes/model/class.tx_recycler_model_deletedRecords.php')); +require_once(t3lib_extMgm::extPath('recycler', 'classes/model/class.tx_recycler_model_tables.php')); +require_once(t3lib_extMgm::extPath('recycler', 'classes/view/class.tx_recycler_view_deletedRecords.php')); +require_once(t3lib_extMgm::extPath('recycler', 'classes/helper/class.tx_recycler_helper.php')); + +/** + * Controller class for the 'recycler' extension. Handles the AJAX Requests + * + * @author Julian Kleinhans + * @author Erik Frister + * @package TYPO3 + * @subpackage tx_recycler + * @version $Id: class.tx_recycler_controller_ajax.php 17681 2009-03-10 18:28:28Z ohader $ + */ +class tx_recycler_controller_ajax { + /** + * Stores the content for the ajax output + * + * @var string + */ + protected $content; + + /** + * 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 + */ + protected $data; + + /** + * Initialize method + * + * @return void + */ + public function init() { + $this->mapCommand(); + $this->getContent(); + } + + /** + * Maps the command to the correct Model and View + * + * @return void + **/ + public function mapCommand() { + $this->command = t3lib_div::_GP('cmd'); + $this->data = t3lib_div::_GP('data'); + + // check params + if (!is_string($this->command)) { + // @TODO make devlog output + return false; + } + + // Create content + $this->createContent(); + } + + /** + * Creates the content + * + * @return void + **/ + protected function createContent() { + $str = ''; + + switch ($this->command) { + // Get deleted records + case 'getDeletedRecords': + $table = ( t3lib_div::_GP('table') ? t3lib_div::_GP('table') : t3lib_div::_GP('tableDefault')); + $limit = ( t3lib_div::_GP('limit') ? (int)t3lib_div::_GP('limit') : (int)t3lib_div::_GP('pagingSizeDefault')); + $start = ( t3lib_div::_GP('start') ? (int)t3lib_div::_GP('start') : 0); + $filter = ( t3lib_div::_GP('filterTxt') ? t3lib_div::_GP('filterTxt') : ''); + $startUid = ( t3lib_div::_GP('startUid') ? t3lib_div::_GP('startUid') : ''); + $depth = ( t3lib_div::_GP('depth') ? t3lib_div::_GP('depth') : ''); + $this->setDataInSession('tableSelection', $table); + + $model = t3lib_div::makeInstance('tx_recycler_model_deletedRecords'); + $model->loadData($startUid, $table, $depth, $start . ',' . $limit, $filter); + + $deletedRowsArray = $model->getDeletedRows(); + + $model = t3lib_div::makeInstance('tx_recycler_model_deletedRecords'); + $totalDeleted = $model->getTotalCount($startUid, $table, $depth, $filter); + + // load view + $view = t3lib_div::makeInstance('tx_recycler_view_deletedRecords'); + $str = $view->transform($deletedRowsArray, $totalDeleted); + break; + + // Delete records + case 'doDelete': + $str = false; + $model = t3lib_div::makeInstance('tx_recycler_model_deletedRecords'); + if ($model->deleteData($this->data)) { + $str = true; + } + break; + + // Undelete records + case 'doUndelete': + $str = false; + $recursive = t3lib_div::_GP('recursive'); + $model = t3lib_div::makeInstance('tx_recycler_model_deletedRecords'); + if ($model->undeleteData($this->data, $recursive)) { + $str = true; + } + break; + + // getTables for menu + case 'getTables': + $depth = ( t3lib_div::_GP('depth') ? t3lib_div::_GP('depth') : 0); + $startUid = ( t3lib_div::_GP('startUid') ? t3lib_div::_GP('startUid') : ''); + $this->setDataInSession('depthSelection', $depth); + + $model = t3lib_div::makeInstance('tx_recycler_model_tables'); + $str = $model->getTables('json', 1, $startUid, $depth); + break; + + // No cmd + default: + $str = 'No command was recognized.'; + break; + } + + $this->content = $str; + } + + /** + * Returns the content that was created in the mapCommand method + * + * @return string + **/ + public function getContent() { + echo $this->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 + * @return void + */ + protected function setDataInSession($identifier, $data) { + $GLOBALS['BE_USER']->uc['tx_recycler'][$identifier] = $data; + $GLOBALS['BE_USER']->writeUC(); + } +} + + +if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/recycler/classes/controller/class.tx_recycler_controller_ajax.php']) { + include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/recycler/classes/controller/class.tx_recycler_controller_ajax.php']); +} + +?> \ No newline at end of file diff --git a/typo3/sysext/recycler/classes/helper/class.tx_recycler_helper.php b/typo3/sysext/recycler/classes/helper/class.tx_recycler_helper.php new file mode 100644 index 000000000000..85fa8a1f2015 --- /dev/null +++ b/typo3/sysext/recycler/classes/helper/class.tx_recycler_helper.php @@ -0,0 +1,214 @@ + +* All rights reserved +* +* This script is part of the TYPO3 project. The TYPO3 project is +* free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* The GNU General Public License can be found at +* http://www.gnu.org/copyleft/gpl.html. +* +* This script is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* This copyright notice MUST APPEAR in all copies of the script! +***************************************************************/ + +require_once(PATH_t3lib.'class.t3lib_tcemain.php'); + +/** + * Helper class for the 'recycler' extension. + * + * @author Julian Kleinhans + * @package TYPO3 + * @subpackage tx_recycler + * @version $Id: class.tx_recycler_helper.php 17681 2009-03-10 18:28:28Z ohader $ + */ +class tx_recycler_helper { + + /************************************************************ + * USER ACCESS + * + * + ************************************************************/ + + /** + * Checks the page access rights (Code for access check mostly taken from alt_doc.php) + * as well as the table access rights of the user. + * + * @param string $cmd: The command that sould be performed ('new' or 'edit') + * @param string $table: The table to check access for + * @param string $theUid: The record uid of the table + * @return boolean Returns true is the user has access, or false if not + */ + public static function checkAccess($table, $row) { + // 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. + $hasAccess = 0; + $deniedAccessReason = ''; + + $calcPRec = $row; + t3lib_BEfunc::fixVersioningPid($table,$calcPRec); + if (is_array($calcPRec)) { + if ($table=='pages') { // If pages: + $CALC_PERMS = $GLOBALS['BE_USER']->calcPerms($calcPRec); + $hasAccess = $CALC_PERMS & 2 ? 1 : 0; + } else { + $CALC_PERMS = $GLOBALS['BE_USER']->calcPerms(t3lib_BEfunc::getRecord('pages',$calcPRec['pid'])); // Fetching pid-record first. + $hasAccess = $CALC_PERMS & 16 ? 1 : 0; + } + // Check internals regarding access: + if ($hasAccess) { + $hasAccess = $GLOBALS['BE_USER']->recordEditAccessInternals($table, $calcPRec); + } + } + + + if (!$GLOBALS['BE_USER']->check('tables_modify', $table)) { + $hasAccess = 0; + } + + if (!$hasAccess) { + $deniedAccessReason = $GLOBALS['BE_USER']->errorMsg; + if ($deniedAccessReason) { + //fb($deniedAccessReason); + } + } + + return $hasAccess ? true : false; + } + + /** + * Returns the path (visually) of a page $uid, fx. "/First page/Second page/Another subpage" + * Each part of the path will be limited to $titleLimit characters + * Deleted pages are filtered out. + * + * @param integer Page uid for which to create record path + * @param string $clause is additional where clauses, eg. " + * @param integer Title limit + * @param integer Title limit of Full title (typ. set to 1000 or so) + * @return mixed Path of record (string) OR array with short/long title if $fullTitleLimit is set. + */ + public static function getRecordPath($uid, $clause='', $titleLimit=1000, $fullTitleLimit = 0) { + $loopCheck = 100; + $output = $fullOutput = '/'; + + while ($uid != 0 && $loopCheck > 0) { + $loopCheck--; + $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery( + 'uid,pid,title,deleted,t3ver_oid,t3ver_wsid,t3ver_swapmode', + 'pages', + 'uid=' . intval($uid) . (strlen(trim($clause)) ? ' AND ' . $clause : '') + ); + + if (is_resource($res)) { + $row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res); + $GLOBALS['TYPO3_DB']->sql_free_result($res); + + t3lib_BEfunc::workspaceOL('pages', $row); + if (is_array($row)) { + t3lib_BEfunc::fixVersioningPid('pages', $row); + + if ($row['_ORIG_pid'] && $row['t3ver_swapmode'] > 0) { // Branch points + $output = ' [#VEP#]' . $output; // Adding visual token - Versioning Entry Point - that tells that THIS position was where the versionized branch got connected to the main tree. I will have to find a better name or something... + } + $uid = $row['pid']; + $output = '/' . t3lib_div::fixed_lgd_cs(strip_tags($row['title']), $titleLimit) . $output; + + if ($row['deleted']) { + $output = '' . $output . ''; + } + + if ($fullTitleLimit) $fullOutput = '/' . t3lib_div::fixed_lgd_cs(strip_tags($row['title']), $fullTitleLimit) . $fullOutput; + } else { + break; + } + } else { + break; + } + } + + if ($fullTitleLimit) { + return array($output, $fullOutput); + } else { + return $output; + } + } + + /** + * Gets the name of the field with the information whether a record is deleted. + * + * @param string $tableName: Name of the table to get the deleted field for + * @return string Name of the field with the information whether a record is deleted + */ + public static function getDeletedField($tableName) { + $TCA = self::getTableTCA($tableName); + if ($TCA && isset($TCA['ctrl']['delete']) && $TCA['ctrl']['delete']) { + return $TCA['ctrl']['delete']; + } + } + + /** + * Gets the TCA of the table used in the current context. + * + * @param string $tableName: Name of the table to get TCA for + * @return mixed TCA of the table used in the current context (array) + * or false (boolean) if something went wrong + */ + public static function getTableTCA($tableName) { + $TCA = false; + if (isset($GLOBALS['TCA'][$tableName])) { + $TCA = $GLOBALS['TCA'][$tableName]; + } + return $TCA; + } + + /** + * Gets the current backend charset. + * + * @return string The current backend charset + */ + public static function getCurrentCharset() { + return $GLOBALS['LANG']->csConvObj->parse_charset($GLOBALS['LANG']->charSet); + } + + /** + * Determines whether the current charset is not UTF-8 + * + * @return boolean Whether the current charset is not UTF-8 + */ + public static function isNotUtf8Charset() { + return (self::getCurrentCharset() !== 'utf-8'); + } + + /** + * Gets an UTF-8 encoded string (only if the current charset is not UTF-8!). + * + * @param string $string: String to be converted to UTF-8 if required + * @return string UTF-8 encoded string + */ + public static function getUtf8String($string) { + if (self::isNotUtf8Charset()) { + $string = $GLOBALS['LANG']->csConvObj->utf8_encode( + $string, + self::getCurrentCharset() + ); + } + return $string; + } +} + + +if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/recycler/classes/helper/class.tx_recycler_helper.php']) { + include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/recycler/classes/helper/class.tx_recycler_helper.php']); +} + +?> \ No newline at end of file diff --git a/typo3/sysext/recycler/classes/model/class.tx_recycler_model_deletedRecords.php b/typo3/sysext/recycler/classes/model/class.tx_recycler_model_deletedRecords.php new file mode 100644 index 000000000000..f524dfce2229 --- /dev/null +++ b/typo3/sysext/recycler/classes/model/class.tx_recycler_model_deletedRecords.php @@ -0,0 +1,442 @@ + +* All rights reserved +* +* This script is part of the TYPO3 project. The TYPO3 project is +* free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* The GNU General Public License can be found at +* http://www.gnu.org/copyleft/gpl.html. +* +* This script is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* This copyright notice MUST APPEAR in all copies of the script! +***************************************************************/ + +require_once(t3lib_extMgm::extPath('recycler', 'classes/helper/class.tx_recycler_helper.php')); + +/** + * Model class for the 'recycler' extension. + * + * @author Julian Kleinhans + * @package TYPO3 + * @subpackage tx_recycler + * @version $Id: class.tx_recycler_model_deletedRecords.php 17681 2009-03-10 18:28:28Z ohader $ + */ +class tx_recycler_model_deletedRecords { + /** + * Array with all deleted rows + * @var array + */ + protected $deletedRows = array(); + + /** + * String with the global limit + * @var string + */ + protected $limit = ''; + + /** + * Array with all avaiable FE tables + * @var array + */ + protected $table = array(); + + /** + * Object from helper class + * @var tx_recycler_helper + */ + protected $recyclerHelper; + + /** + * Array with all label fields drom different tables + * @var array + */ + public $label; + + /** + * Array with all title fields drom different tables + * @var array + */ + public $title; + + + /************************************************************ + * GET DATA FUNCTIONS + * + * + ************************************************************/ + + /** + * Load all deleted rows from $table + * If table is not set, it iterates the TCA tables + * + * @param integer $id: UID from selected page + * @param string $table: Tablename + * @param integer $depth: How many levels recursive + * @param integer $limit: MySQL LIMIT + * @param string $filter: Filter text + * @return recycler_model_delRecords + */ + public function loadData($id, $table, $depth, $limit='', $filter = '') { + // set the limit + $this->limit = trim($limit); + + if ($table) { + if (array_key_exists($table, $GLOBALS['TCA'])) { + $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 != '') { + $parts = t3lib_div::trimExplode(',', $this->limit); + + // abort loop if LIMIT 0,0 + if ($parts[0] == 0 && $parts[1] == 0) { + break; + } + } + + $this->table[] = $tableKey; + $this->setData($id, $tableKey, $depth, $tableValue['ctrl'], $filter); + } + } + + return $this; + } + + /** + * Find the total count of deleted records + * + * @param integer $id: UID from record + * @param string $table: Tablename from record + * @param integer $depth: How many levels recursive + * @param string $filter: Filter text + * @return void + */ + public function getTotalCount($id, $table, $depth, $filter) { + $deletedRecords = $this->loadData($id, $table, $depth, '', $filter)->getDeletedRows(); + $countTotal = 0; + + foreach($this->table as $tableName) { + $countTotal += count($deletedRecords[$tableName]); + } + + return $countTotal; + } + + /** + * Set all deleted rows + * + * @param integer $id: UID from record + * @param string $table: Tablename from record + * @param integer $depth: How many levels recursive + * @param array $ctrl: TCA CTRL Array + * @param string $filter: Filter text + * @return void + */ + protected function setData($id = 0, $table, $depth, $tcaCtrl, $filter) { + $id = intval($id); + + if (array_key_exists('delete', $tcaCtrl)) { + // find the 'deleted' field for this table + $deletedField = tx_recycler_helper::getDeletedField($table); + + // create the filter WHERE-clause + if (trim($filter) != '') { + $filterWhere = ' AND (' . + (t3lib_div::testInt($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 = $GLOBALS['TYPO3_DB']->exec_SELECTcountRows( + 'uid', + $table, + $deletedField . '!=0 AND pid = ' . $id . $filterWhere + ); + + // split the limit + $parts = t3lib_div::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 = ''; // won't be queried anyways + // if the result is < 0 + } 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 { + // 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; + $allowQuery = true; + } + + // query for actual deleted records + if ($allowQuery) { + $recordsToCheck = t3lib_BEfunc::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 = $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid', 'pages', 'pid=' . $id, '', 'sorting'); + if (is_resource($resPages)) { + while ($rowPages = $GLOBALS['TYPO3_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 ('' != $this->limit) { + $parts = t3lib_div::trimExplode(',', $this->limit); + + // abort loop if LIMIT 0,0 + if ($parts[0] == 0 && $parts[1] == 0) { + break; + } + } + } + $GLOBALS['TYPO3_DB']->sql_free_result($resPages); + } + } + + $this->label[$table] = $tcaCtrl['label']; + $this->title[$table] = $tcaCtrl['title']; + } + } + + /** + * Checks whether the current backend user has access to the given records. + * + * @param string $table: Name of the table + * @param array $rows: Record row + * @return void + */ + protected function checkRecordAccess($table, array $rows) { + foreach ($rows as $key => $row) { + if (tx_recycler_helper::checkAccess($table, $row)) { + $this->setDeletedRows($table, $row); + } + } + } + + /** + * Escapes a value to be used for like in a database query. + * There is a special handling for the characters '%' and '_'. + * + * @param string $value: The value to be escaped for like conditions + * @paran string $tableName: The name of the table the query should be used for + * @return string The escaped value to be used for like conditions + */ + protected function escapeValueForLike($value, $tableName) { + return $GLOBALS['TYPO3_DB']->escapeStrForLike( + $GLOBALS['TYPO3_DB']->quoteStr($value, $tableName), + $tableName + ); + } + + + /************************************************************ + * DELETE FUNCTIONS + ************************************************************/ + + /** + * Delete element from any table + * + * @param string $recordArray: Representation of the records + * @return void + */ + public function deleteData($recordsArray) { + $recordsArray = json_decode($recordsArray); + if (is_array($recordsArray)) { + require_once(PATH_t3lib."class.t3lib_tcemain.php"); + $tce = t3lib_div::makeInstance('t3lib_TCEmain'); + $tce->start('', ''); + $tce->disableDeleteClause(); + foreach ($recordsArray as $key => $record) { + $tce->deleteEl($record[0], $record[1], true, true); + } + return true; + } + return false; + } + + + /************************************************************ + * UNDELETE FUNCTIONS + ************************************************************/ + + /** + * Undelete records + * If $recursive is true all records below the page uid would be undelete too + * + * @param string $recordArray: Representation of the records + * @param boolean $recursive: true/false + * @return boolean + */ + public function undeleteData($recordsArray, $recursive = false) { + require_once PATH_t3lib . 'class.t3lib_tcemain.php'; + $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, ''); + $childRecords = $this->getDeletedRows(); + if (count($childRecords)>0) { + foreach ($childRecords as $table => $childRows) { + foreach ($childRows as $childKey => $childRow) { + $cmd[$table][$childRow['uid']]['undelete'] = 1; + } + } + } + } + } + + if ($cmd) { + $tce = t3lib_div::makeInstance('t3lib_TCEmain'); + $tce->start(array(), $cmd); + $tce->process_cmdmap(); + $result = true; + } + } + + return $result; + } + + + /************************************************************ + * SETTER FUNCTIONS + ************************************************************/ + + /** + * Set deleted rows + * + * @param string $table: Tablename + * @param array $row: Deleted record row + * @return void + */ + public function setDeletedRows($table, array $row) { + $this->deletedRows[$table][] = $row; + } + + + /************************************************************ + * GETTER FUNCTIONS + ************************************************************/ + + /** + * Get deleted Rows + * + * @return array $this->deletedRows: Array with all deleted rows from TCA + */ + public function getDeletedRows() { + return $this->deletedRows; + } + + /** + * Get table + * + * @return array $this->table: Array with table from TCA + */ + public function getTable() { + return $this->table; + } +} + +if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/recycler/classes/model/class.tx_recycler_model_deletedRecords.php']) { + include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/recycler/classes/model/class.tx_recycler_model_deletedRecords.php']); +} + +?> \ No newline at end of file diff --git a/typo3/sysext/recycler/classes/model/class.tx_recycler_model_tables.php b/typo3/sysext/recycler/classes/model/class.tx_recycler_model_tables.php new file mode 100644 index 000000000000..2cbf39e400f8 --- /dev/null +++ b/typo3/sysext/recycler/classes/model/class.tx_recycler_model_tables.php @@ -0,0 +1,100 @@ + +* All rights reserved +* +* This script is part of the TYPO3 project. The TYPO3 project is +* free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* The GNU General Public License can be found at +* http://www.gnu.org/copyleft/gpl.html. +* +* This script is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* This copyright notice MUST APPEAR in all copies of the script! +***************************************************************/ + +require_once(t3lib_extMgm::extPath('recycler', 'classes/model/class.tx_recycler_model_deletedRecords.php')); + +/** + * Model class for the 'recycler' extension. + * + * @author Julian Kleinhans + * @package TYPO3 + * @subpackage tx_recycler + * @version $Id: class.tx_recycler_model_tables.php 17681 2009-03-10 18:28:28Z ohader $ + */ +class tx_recycler_model_tables { + /** + * Get tables for menu example + * + * @param string $format: Return format (example: json) + * @param boolean $withAllOption: 0 no, 1 return tables with a "all" option + * @param integer $id: UID from selected page + * @param integer $depth: How many levels recursive + * @return string The tables to be displayed + */ + public function getTables($format, $withAllOption=0, $startUid, $depth=0) { + $deletedRecordsTotal = 0; + $tables = array(); + + foreach (array_keys($GLOBALS['TCA']) as $tableName) { + $deletedField = tx_recycler_helper::getDeletedField($tableName); + if ($deletedField) { + // Determine whether the table has deleted records: + $deletedCount = $GLOBALS['TYPO3_DB']->exec_SELECTcountRows( + 'uid', + $tableName, + $deletedField . '!=0' + ); + if ($deletedCount) { + $deletedDataObject = t3lib_div::makeInstance('tx_recycler_model_deletedRecords'); + $deletedData = $deletedDataObject->loadData($startUid, $tableName, $depth)->getDeletedRows(); + if (isset($deletedData[$tableName])) { + if ($deletedRecordsInTable = count($deletedData[$tableName])) { + $deletedRecordsTotal += $deletedRecordsInTable; + $tables[] = array( + $tableName, + $deletedRecordsInTable, + $tableName, + tx_recycler_helper::getUtf8String( + $GLOBALS['LANG']->sL($GLOBALS['TCA'][$tableName]['ctrl']['title']) + ), + ); + } + } + } + } + } + + $jsonArray = $tables; + if ($withAllOption) { + array_unshift( + $jsonArray, + array( + '', + $deletedRecordsTotal, + '', + $GLOBALS['LANG']->sL('LLL:EXT:recycler/mod1/locallang.xml:label_alltables'), + ) + ); + } + $output = json_encode($jsonArray); + + return $output; + } +} + +if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/recycler/classes/model/class.tx_recycler_model_tables.php']) { + include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/recycler/classes/model/class.tx_recycler_model_tables.php']); +} + +?> \ No newline at end of file diff --git a/typo3/sysext/recycler/classes/view/class.tx_recycler_view_deletedRecords.php b/typo3/sysext/recycler/classes/view/class.tx_recycler_view_deletedRecords.php new file mode 100644 index 000000000000..cf56a6ed5dbd --- /dev/null +++ b/typo3/sysext/recycler/classes/view/class.tx_recycler_view_deletedRecords.php @@ -0,0 +1,88 @@ + + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +require_once(t3lib_extMgm::extPath('recycler', 'classes/helper/class.tx_recycler_helper.php')); + +/** + * Deleted Records View + * + * @author Erik Frister + * @author Julian Kleinhans + * @package TYPO3 + * @subpackage tx_recycler + * @version $Id: class.tx_recycler_view_deletedRecords.php 17635 2009-03-09 13:21:13Z ohader $ + **/ +class tx_recycler_view_deletedRecords { + + /** + * Transforms the rows for the deleted Records into the Array View necessary for ExtJS Ext.data.ArrayReader + * + * @param array $rows Array with table as key and array with all deleted rows + * @param integer $totalDeleted: Number of deleted records in total, for PagingToolbar + * @return string JSON Array + **/ + public function transform ($deletedRowsArray, $totalDeleted) { + $total = 0; + + $jsonArray = array( + 'rows' => array(), + ); + + // iterate + if (is_array($deletedRowsArray) && count($deletedRowsArray) > 0) { + foreach($deletedRowsArray as $table => $rows) { + $total += count($deletedRowsArray[$table]); + + foreach($rows as $row) { + $feuser = t3lib_BEfunc::getRecord('be_users', $row[$GLOBALS['TCA'][$table]['ctrl']['cruser_id']], 'username', '', false); + $jsonArray['rows'][] = array( + 'uid' => $row['uid'], + 'pid' => $row['pid'], + 'table' => $table, + 'crdate' => date($GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] . ' ' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'], $row[$GLOBALS['TCA'][$table]['ctrl']['crdate']]), + 'tstamp' => date($GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] . ' ' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'], $row[$GLOBALS['TCA'][$table]['ctrl']['tstamp']]), + 'owner' => $feuser['username'], + 'owner_uid' => $row[$GLOBALS['TCA'][$table]['ctrl']['cruser_id']], + 'tableTitle' => tx_recycler_helper::getUtf8String( + $GLOBALS['LANG']->sL($GLOBALS['TCA'][$table]['ctrl']['title']) + ), + 'title' => tx_recycler_helper::getUtf8String( + t3lib_BEfunc::getRecordTitle($table, $row) + ), + 'path' => tx_recycler_helper::getRecordPath($row['pid']), + ); + } + } + } + + $jsonArray['total'] = $totalDeleted; + return json_encode($jsonArray); + } +} + +if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/recycler/classes/view/class.tx_recycler_view_deletedRecords.php']) { + include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/recycler/classes/view/class.tx_recycler_view_deletedRecords.php']); +} + +?> \ No newline at end of file diff --git a/typo3/sysext/recycler/doc/manual.sxw b/typo3/sysext/recycler/doc/manual.sxw new file mode 100644 index 000000000000..df7f59955f5e Binary files /dev/null and b/typo3/sysext/recycler/doc/manual.sxw differ diff --git a/typo3/sysext/recycler/ext_emconf.php b/typo3/sysext/recycler/ext_emconf.php new file mode 100644 index 000000000000..20b7a92a2879 --- /dev/null +++ b/typo3/sysext/recycler/ext_emconf.php @@ -0,0 +1,47 @@ + 'Recycler', + 'description' => 'The recycler offers the possibilities of cleaning up the garbage collection or to restore data again. Based on an ExtJS interface its possible to get a quick overview of the accordant records, filter the resultset and execute the required actions. This new feature is the modernized and core-specific version of the kj_recycler extension, that has been available in the TER for years now.', + 'category' => 'module', + 'author' => 'Julian Kleinhans', + 'author_email' => 'typo3@kj187.de', + 'shy' => '', + 'dependencies' => 'cms', + 'conflicts' => '', + 'priority' => '', + 'module' => 'mod1', + 'state' => 'alpha', + 'internal' => '', + 'uploadfolder' => 0, + 'createDirs' => '', + 'modify_tables' => '', + 'clearCacheOnLoad' => 0, + 'lockType' => '', + 'author_company' => '', + 'version' => '1.0.1', + 'constraints' => array( + 'depends' => array( + 'cms' => '', + ), + 'conflicts' => array( + ), + 'suggests' => array( + ), + ), + '_md5_values_when_last_written' => 'a:34:{s:9:"ChangeLog";s:4:"7b94";s:12:"ext_icon.gif";s:4:"cf3b";s:17:"ext_localconf.php";s:4:"cee4";s:14:"ext_tables.php";s:4:"2275";s:16:"locallang_db.xml";s:4:"a06e";s:56:"classes/controller/class.tx_recycler_controller_ajax.php";s:4:"566a";s:43:"classes/helper/class.tx_recycler_helper.php";s:4:"411c";s:56:"classes/model/class.tx_recycler_model_deletedRecords.php";s:4:"b7b0";s:48:"classes/model/class.tx_recycler_model_tables.php";s:4:"9cc8";s:54:"classes/view/class.tx_recycler_view_deletedRecords.php";s:4:"c552";s:14:"doc/manual.sxw";s:4:"3528";s:14:"mod1/clear.gif";s:4:"cc11";s:13:"mod1/conf.php";s:4:"e060";s:14:"mod1/index.php";s:4:"cb21";s:18:"mod1/locallang.xml";s:4:"11a1";s:22:"mod1/locallang_mod.xml";s:4:"188b";s:22:"mod1/mod_template.html";s:4:"08a6";s:19:"mod1/moduleicon.gif";s:4:"cf3b";s:23:"res/css/customExtJs.css";s:4:"8697";s:20:"res/icons/accept.png";s:4:"8bfe";s:24:"res/icons/arrow_redo.png";s:4:"343b";s:40:"res/icons/arrow_rotate_anticlockwise.png";s:4:"a7db";s:17:"res/icons/bin.png";s:4:"728a";s:24:"res/icons/bin_closed.png";s:4:"c5b3";s:23:"res/icons/bin_empty.png";s:4:"2e76";s:27:"res/icons/database_save.png";s:4:"8303";s:20:"res/icons/delete.gif";s:4:"5a2a";s:26:"res/icons/filter_clear.png";s:4:"3862";s:28:"res/icons/filter_refresh.png";s:4:"b051";s:22:"res/icons/recycler.gif";s:4:"7b41";s:23:"res/icons/recycler2.gif";s:4:"cf3b";s:22:"res/js/ext_expander.js";s:4:"bb02";s:22:"res/js/search_field.js";s:4:"efae";s:21:"res/js/t3_recycler.js";s:4:"6021";}', + 'suggests' => array( + ), +); + +?> \ No newline at end of file diff --git a/typo3/sysext/recycler/ext_icon.gif b/typo3/sysext/recycler/ext_icon.gif new file mode 100644 index 000000000000..9f0bf9fd76e2 Binary files /dev/null and b/typo3/sysext/recycler/ext_icon.gif differ diff --git a/typo3/sysext/recycler/ext_localconf.php b/typo3/sysext/recycler/ext_localconf.php new file mode 100644 index 000000000000..66de57de77ba --- /dev/null +++ b/typo3/sysext/recycler/ext_localconf.php @@ -0,0 +1,6 @@ +init'; + +?> \ No newline at end of file diff --git a/typo3/sysext/recycler/ext_tables.php b/typo3/sysext/recycler/ext_tables.php new file mode 100644 index 000000000000..db2ae70ce7c2 --- /dev/null +++ b/typo3/sysext/recycler/ext_tables.php @@ -0,0 +1,13 @@ + \ No newline at end of file diff --git a/typo3/sysext/recycler/locallang_db.xml b/typo3/sysext/recycler/locallang_db.xml new file mode 100644 index 000000000000..7cdd4f0564c3 --- /dev/null +++ b/typo3/sysext/recycler/locallang_db.xml @@ -0,0 +1,12 @@ + + + + database + Language labels for database tables/fields belonging to extension 'recycler' + + + + + + + \ No newline at end of file diff --git a/typo3/sysext/recycler/mod1/clear.gif b/typo3/sysext/recycler/mod1/clear.gif new file mode 100644 index 000000000000..9ed1269764b8 Binary files /dev/null and b/typo3/sysext/recycler/mod1/clear.gif differ diff --git a/typo3/sysext/recycler/mod1/conf.php b/typo3/sysext/recycler/mod1/conf.php new file mode 100644 index 000000000000..28c1b1b70278 --- /dev/null +++ b/typo3/sysext/recycler/mod1/conf.php @@ -0,0 +1,12 @@ + \ No newline at end of file diff --git a/typo3/sysext/recycler/mod1/index.php b/typo3/sysext/recycler/mod1/index.php new file mode 100644 index 000000000000..61b327560364 --- /dev/null +++ b/typo3/sysext/recycler/mod1/index.php @@ -0,0 +1,345 @@ + +* All rights reserved +* +* This script is part of the TYPO3 project. The TYPO3 project is +* free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* The GNU General Public License can be found at +* http://www.gnu.org/copyleft/gpl.html. +* +* This script is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* This copyright notice MUST APPEAR in all copies of the script! +***************************************************************/ + +require_once(PATH_t3lib . 'class.t3lib_scbase.php'); + +$LANG->includeLLFile('EXT:recycler/mod1/locallang.xml'); +$BE_USER->modAccess($MCONF, 1); // This checks permissions and exits if the users has no permission for entry. + +/** + * Module 'Recycler' for the 'recycler' extension. + * + * @author Julian Kleinhans + * @package TYPO3 + * @subpackage tx_recycler + * @version $Id: index.php 17692 2009-03-10 21:55:38Z ohader $ + */ +class tx_recycler_module1 extends t3lib_SCbase { + /** + * @var template + */ + public $doc; + + protected $relativePath; + protected $pageRecord = array(); + protected $isAccessibleForCurrentUser = false; + + protected $allowDelete = false; + protected $recordsPageLimit = 50; + + /** + * Initializes the Module + * + * @return void + */ + public function initialize() { + parent::init(); + $this->doc = t3lib_div::makeInstance('template'); + $this->doc->setModuleTemplate(t3lib_extMgm::extPath('recycler') . 'mod1/mod_template.html'); + $this->doc->backPath = $GLOBALS['BACK_PATH']; + + $this->relativePath = t3lib_extMgm::extRelPath('recycler'); + $this->pageRecord = t3lib_BEfunc::readPageAccess($this->id, $this->perms_clause); + $this->isAccessibleForCurrentUser = ( + $this->id && is_array($this->pageRecord) || !$this->id && $this->isCurrentUserAdmin() + ); + + //don't access in workspace + if ($GLOBALS['BE_USER']->workspace !== 0) { + $this->isAccessibleForCurrentUser = false; + } + + //read configuration + $modTS = $GLOBALS['BE_USER']->getTSConfig('mod.recycler'); + if ($this->isCurrentUserAdmin()) { + $this->allowDelete = true; + } else { + $this->allowDelete = ($modTS['properties']['allowDelete'] == '1'); + } + if (isset($modTS['properties']['recordsPageLimit']) && intval($modTS['properties']['recordsPageLimit']) > 0) { + $this->recordsPageLimit = intval($modTS['properties']['recordsPageLimit']); + } + } + + /** + * Renders the contente of the module. + * + * @return void + */ + public function render() { + global $BE_USER,$LANG,$BACK_PATH,$TCA_DESCR,$TCA,$CLIENT,$TYPO3_CONF_VARS; + + if ($this->isAccessibleForCurrentUser) { + $this->loadHeaderData(); + // div container for renderTo + $this->content.= '
'; + } else { + // If no access or if ID == zero + $this->content.= $this->doc->spacer(10); + } + } + + /** + * Flushes the rendered content to browser. + * + * @return void + */ + public function flush() { + $content = $this->doc->startPage($GLOBALS['LANG']->getLL('title')); + $content.= $this->doc->moduleBody( + $this->pageRecord, + $this->getDocHeaderButtons(), + $this->getTemplateMarkers() + ); + $content.= $this->doc->endPage(); + $content.= $this->doc->insertStylesAndJS($this->content); + + $this->content = null; + $this->doc = null; + + echo $content; + } + + /** + * Determines whether the current user is admin. + * + * @return boolean Whether the current user is admin + */ + protected function isCurrentUserAdmin() { + return (bool)$GLOBALS['BE_USER']->user['admin']; + } + + /** + * Loads data in the HTML head section (e.g. JavaScript or stylesheet information). + * + * @return void + */ + protected function loadHeaderData() { + // Load CSS Stylesheets: + $this->loadStylesheet('contrib/extjs/resources/css/ext-all.css'); + $this->loadStylesheet('contrib/extjs/resources/css/xtheme-gray.css'); + $this->loadStylesheet($this->relativePath . 'res/css/customExtJs.css'); + // Load Ext JS: + $this->doc->loadJavascriptLib('contrib/extjs/adapter/ext/ext-base.js'); + $this->doc->loadJavascriptLib('contrib/extjs/ext-all.js'); + // Integrate dynamic JavaScript such as configuration or lables: + $this->doc->JScode.= t3lib_div::wrapJS(' + Ext.namespace("Recycler"); + Recycler.statics = ' . json_encode($this->getJavaScriptConfiguration()) . '; + Recycler.lang = ' . json_encode($this->getJavaScriptLabels()) . ';' + ); + // Load Recycler JavaScript: + $this->loadJavaScript($this->relativePath . 'res/js/ext_expander.js'); + $this->loadJavaScript($this->relativePath . 'res/js/search_field.js'); + $this->loadJavaScript($this->relativePath . 'res/js/t3_recycler.js'); + } + + /** + * Loads a stylesheet by adding it to the HTML head section. + * + * @param string $fileName: Name of the file to be loaded + * @return void + */ + protected function loadStylesheet($fileName) { + $fileName = t3lib_div::resolveBackPath($this->doc->backPath . $fileName); + $this->doc->JScode.= "\t" . '' . "\n"; + } + + /** + * Loads a JavaScript file. + * + * @param string $fileName: Name of the file to be loaded + * @return void + */ + protected function loadJavaScript($fileName) { + $fileName = t3lib_div::resolveBackPath($this->doc->backPath . $fileName); + $this->doc->JScode.= "\t" . '' . "\n"; + } + + /** + * Gets the JavaScript configuration for the Ext JS interface. + * + * @return array The JavaScript configuration + */ + protected function getJavaScriptConfiguration() { + $configuration = array( + 'pagingSize' => $this->recordsPageLimit, + 'showDepthMenu' => 1, + 'startUid' => $this->id, + 'tableDefault' => 'pages', + 'renderTo' => 'recyclerContent', + 'isSSL' => t3lib_div::getIndpEnv('TYPO3_SSL'), + 'ajaxController' => $this->doc->backPath . 'ajax.php?ajaxID=tx_recycler::controller', + 'deleteDisable' => $this->allowDelete ? 0 : 1, + 'depthSelection' => $this->getDataFromSession('depthSelection', 0), + 'tableSelection' => $this->getDataFromSession('tableSelection', 'pages'), + ); + 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' => $GLOBALS['LANG']->getLL('title'), + 'path' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:labels.path'), + 'table' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:labels.table'), + 'depth' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_web_perm.xml:Depth'), + 'depth_0' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:labels.depth_0'), + 'depth_1' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:labels.depth_1'), + 'depth_2' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:labels.depth_2'), + 'depth_3' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:labels.depth_3'), + 'depth_4' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:labels.depth_4'), + 'depth_infi' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:labels.depth_infi'), + ); + $extensionLabels = $this->getJavaScriptLabelsFromLocallang('js.', 'label_'); + return array_merge($coreLabels, $extensionLabels); + } + + /** + * Gets labels to be used in JavaScript fetched from the current locallang file. + * + * @param string $selectionPrefix: Prefix to select the correct labels (default: 'js.') + * @param string $stripFromSelectionName: Sub-prefix to be removed from label names in the result (default: '') + * @return array Lables to be used in JavaScript of the current locallang file + * @todo Check, whether this method can be moved in a generic way to $GLOBALS['LANG'] + */ + protected function getJavaScriptLabelsFromLocallang($selectionPrefix = 'js.', $stripFromSelectionName = '') { + $extraction = array(); + $labels = array_merge( + (array)$GLOBALS['LOCAL_LANG']['default'], + (array)$GLOBALS['LOCAL_LANG'][$GLOBALS['LANG']->lang] + ); + // Regular expression to strip the selection prefix and possibly something from the label name: + $labelPattern = '#^' . preg_quote($selectionPrefix, '#') . '(' . preg_quote($stripFromSelectionName, '#') . ')?#'; + // Iterate throuh all locallang lables: + foreach ($labels as $label => $value) { + if (strpos($label, $selectionPrefix) === 0) { + $key = preg_replace($labelPattern, '', $label); + $extraction[$key] = $value; + } + } + return $extraction; + } + + /** + * Gets the buttons that shall be rendered in the docHeader. + * + * @return array Available buttons for the docHeader + */ + protected function getDocHeaderButtons() { + $buttons = array( + 'csh' => t3lib_BEfunc::cshItem('_MOD_web_func', '', $GLOBALS['BACK_PATH']), + 'shortcut' => $this->getShortcutButton(), + 'save' => '' + ); + + // SAVE button + $buttons['save'] = ''; //'; + + return $buttons; + } + + /** + * Gets the button to set a new shortcut in the backend (if current user is allowed to). + * + * @return string HTML representiation of the shortcut button + */ + protected function getShortcutButton() { + $result = ''; + if ($GLOBALS['BE_USER']->mayMakeShortcut()) { + $result = $this->doc->makeShortcutIcon('', 'function', $this->MCONF['name']); + } + return $result; + } + + /** + * Gets the filled markers that are used in the HTML template. + * + * @return array The filled marker array + */ + protected function getTemplateMarkers() { + $markers = array( + 'FUNC_MENU' => $this->getFunctionMenu(), + 'CONTENT' => $this->content, + 'TITLE' => $GLOBALS['LANG']->getLL('title'), + ); + return $markers; + } + + /** + * Gets the function menu selector for this backend module. + * + * @return string The HTML representation of the function menu selector + */ + protected function getFunctionMenu() { + return t3lib_BEfunc::getFuncMenu( + 0, + 'SET[function]', + $this->MOD_SETTINGS['function'], + $this->MOD_MENU['function'] + ); + } + + /** + * Gets data from the session of the current backend user. + * + * @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 getDataFromSession($identifier, $default = NULL) { + $sessionData =& $GLOBALS['BE_USER']->uc['tx_recycler']; + if (isset($sessionData[$identifier]) && $sessionData[$identifier]) { + $data = $sessionData[$identifier]; + } else { + $data = $default; + } + return $data; + } +} + + + +if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/recycler/mod1/index.php']) { + include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/recycler/mod1/index.php']); +} + + + +// Make instance: +$SOBE = t3lib_div::makeInstance('tx_recycler_module1'); +$SOBE->initialize(); + +// Include files? +foreach($SOBE->include_once as $INC_FILE) { + include_once($INC_FILE); +} + +$SOBE->render(); +$SOBE->flush(); +?> \ No newline at end of file diff --git a/typo3/sysext/recycler/mod1/locallang.xml b/typo3/sysext/recycler/mod1/locallang.xml new file mode 100644 index 000000000000..9597782ce94e --- /dev/null +++ b/typo3/sysext/recycler/mod1/locallang.xml @@ -0,0 +1,42 @@ + + + + module + Language labels for module "web_txrecyclerM1" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/typo3/sysext/recycler/mod1/locallang_mod.xml b/typo3/sysext/recycler/mod1/locallang_mod.xml new file mode 100644 index 000000000000..d55e386c3301 --- /dev/null +++ b/typo3/sysext/recycler/mod1/locallang_mod.xml @@ -0,0 +1,12 @@ + + + + module + Language labels for module "tools_txrecyclerM1" - header, description + + + + + + + \ No newline at end of file diff --git a/typo3/sysext/recycler/mod1/mod_template.html b/typo3/sysext/recycler/mod1/mod_template.html new file mode 100644 index 000000000000..281c4c54ad53 --- /dev/null +++ b/typo3/sysext/recycler/mod1/mod_template.html @@ -0,0 +1,36 @@ + +
+ +
+
+
###BUTTONLIST_LEFT###
+
###BUTTONLIST_RIGHT###
+
+
+
###FUNC_MENU###
+
###PAGEPATH######PAGEINFO###
+
+
+ +
+
+

###TITLE###

+ ###CONTENT### +
+
+
+ + + + + +
###BUTTONS###
+ + + +###SAVE### + + + +###SHORTCUT### + \ No newline at end of file diff --git a/typo3/sysext/recycler/mod1/moduleicon.gif b/typo3/sysext/recycler/mod1/moduleicon.gif new file mode 100644 index 000000000000..9f0bf9fd76e2 Binary files /dev/null and b/typo3/sysext/recycler/mod1/moduleicon.gif differ diff --git a/typo3/sysext/recycler/res/css/customExtJs.css b/typo3/sysext/recycler/res/css/customExtJs.css new file mode 100644 index 000000000000..c48cefbbaae2 --- /dev/null +++ b/typo3/sysext/recycler/res/css/customExtJs.css @@ -0,0 +1,36 @@ +body .x-panel { margin-bottom:20px; } + +.icon-grid { background-image:url('../icons/recycler2.gif') !important; } + +#button-grid .x-panel-body { + border:1px solid #99bbe8; + border-top:0 none; +} + +.loadingClass { top:200px !important; } + +.ext-el-mask-msg { width:200px; } + +.ext-el-mask-msg div { + height:30px; + background-image: url('../extJS/resources/images/default/shared/large-loading.gif'); + background-position: 155px 3px; + background-repeat: no-repeat; +} + +.delete { background-image: url('../icons/delete.gif') !important; } +.undelete { background-image: url('../icons/arrow_rotate_anticlockwise.png') !important; } +.backup { background-image: url('../icons/database_save.png') !important; } +.filter_refresh { background-image: url('../icons/filter_refresh.png') !important; } +.filter_clear { background-image: url('../icons/filter_clear.png') !important; } + +.deletedPath { color: #ff0000; font-weight: bold; } + +.x-toolbar table { width: 100%; } + +#recordPaging table { width: auto; } +#recordPaging .x-tbar-page-number { text-align: center; } + +.x-window-body { padding: 5px; } +.x-window-body label { display: block; margin: 5px 0; } +.x-window-body .x-form-cb-label { margin-left: 5px; } \ No newline at end of file diff --git a/typo3/sysext/recycler/res/icons/accept.png b/typo3/sysext/recycler/res/icons/accept.png new file mode 100644 index 000000000000..89c8129a490b Binary files /dev/null and b/typo3/sysext/recycler/res/icons/accept.png differ diff --git a/typo3/sysext/recycler/res/icons/arrow_redo.png b/typo3/sysext/recycler/res/icons/arrow_redo.png new file mode 100644 index 000000000000..fdc394c7c59b Binary files /dev/null and b/typo3/sysext/recycler/res/icons/arrow_redo.png differ diff --git a/typo3/sysext/recycler/res/icons/arrow_rotate_anticlockwise.png b/typo3/sysext/recycler/res/icons/arrow_rotate_anticlockwise.png new file mode 100644 index 000000000000..46c75aa85967 Binary files /dev/null and b/typo3/sysext/recycler/res/icons/arrow_rotate_anticlockwise.png differ diff --git a/typo3/sysext/recycler/res/icons/bin.png b/typo3/sysext/recycler/res/icons/bin.png new file mode 100644 index 000000000000..ebad933c8b37 Binary files /dev/null and b/typo3/sysext/recycler/res/icons/bin.png differ diff --git a/typo3/sysext/recycler/res/icons/bin_closed.png b/typo3/sysext/recycler/res/icons/bin_closed.png new file mode 100644 index 000000000000..afe22ba99ee8 Binary files /dev/null and b/typo3/sysext/recycler/res/icons/bin_closed.png differ diff --git a/typo3/sysext/recycler/res/icons/bin_empty.png b/typo3/sysext/recycler/res/icons/bin_empty.png new file mode 100644 index 000000000000..375b8bf6a098 Binary files /dev/null and b/typo3/sysext/recycler/res/icons/bin_empty.png differ diff --git a/typo3/sysext/recycler/res/icons/database_save.png b/typo3/sysext/recycler/res/icons/database_save.png new file mode 100644 index 000000000000..44c06dddf19f Binary files /dev/null and b/typo3/sysext/recycler/res/icons/database_save.png differ diff --git a/typo3/sysext/recycler/res/icons/delete.gif b/typo3/sysext/recycler/res/icons/delete.gif new file mode 100644 index 000000000000..5e2a3b1434d9 Binary files /dev/null and b/typo3/sysext/recycler/res/icons/delete.gif differ diff --git a/typo3/sysext/recycler/res/icons/filter_clear.png b/typo3/sysext/recycler/res/icons/filter_clear.png new file mode 100644 index 000000000000..cce652e845cd Binary files /dev/null and b/typo3/sysext/recycler/res/icons/filter_clear.png differ diff --git a/typo3/sysext/recycler/res/icons/filter_refresh.png b/typo3/sysext/recycler/res/icons/filter_refresh.png new file mode 100644 index 000000000000..ff803be124ac Binary files /dev/null and b/typo3/sysext/recycler/res/icons/filter_refresh.png differ diff --git a/typo3/sysext/recycler/res/icons/recycler.gif b/typo3/sysext/recycler/res/icons/recycler.gif new file mode 100644 index 000000000000..2de775a91781 Binary files /dev/null and b/typo3/sysext/recycler/res/icons/recycler.gif differ diff --git a/typo3/sysext/recycler/res/icons/recycler2.gif b/typo3/sysext/recycler/res/icons/recycler2.gif new file mode 100644 index 000000000000..9f0bf9fd76e2 Binary files /dev/null and b/typo3/sysext/recycler/res/icons/recycler2.gif differ diff --git a/typo3/sysext/recycler/res/js/ext_expander.js b/typo3/sysext/recycler/res/js/ext_expander.js new file mode 100644 index 000000000000..44f02d579b75 --- /dev/null +++ b/typo3/sysext/recycler/res/js/ext_expander.js @@ -0,0 +1,159 @@ +/* + * Ext JS Library 2.0 + * Copyright(c) 2006-2007, Ext JS, LLC. + * licensing@extjs.com + * + * http://extjs.com/license + * + * MODIFIED: SGB [12.12.07] + * Added support for a new config option, remoteDataMethod, + * including getter and setter functions, and minor mods + * to the beforeExpand and expandRow functions + */ + +Ext.grid.RowExpander = function(config) { + Ext.apply(this, config); + Ext.grid.RowExpander.superclass.constructor.call(this); + + if (this.tpl) { + if (typeof this.tpl == 'string') { + this.tpl = new Ext.Template(this.tpl); + } + this.tpl.compile(); + } + + this.state = {}; + this.bodyContent = {}; + + this.addEvents({ + beforeexpand : true, + expand: true, + beforecollapse: true, + collapse: true + }); +}; + +Ext.extend(Ext.grid.RowExpander, Ext.util.Observable, { + header: "", + width: 20, + sortable: false, + fixed:true, + dataIndex: '', + id: 'expander', + lazyRender : true, + enableCaching: true, + + getRowClass : function(record, rowIndex, p, ds) { + p.cols = p.cols-1; + var content = this.bodyContent[record.id]; + if (!content && !this.lazyRender) { + content = this.getBodyContent(record, rowIndex); + } + if (content) { + p.body = content; + } + return this.state[record.id] ? 'x-grid3-row-expanded' : 'x-grid3-row-collapsed'; + }, + + init : function(grid) { + this.grid = grid; + + var view = grid.getView(); + view.getRowClass = this.getRowClass.createDelegate(this); + + view.enableRowBody = true; + + grid.on('render', function() { + view.mainBody.on('mousedown', this.onMouseDown, this); + }, this); + }, + + getBodyContent : function(record, index) { + if (!this.enableCaching) { + return this.tpl.apply(record.data); + } + var content = this.bodyContent[record.id]; + if (!content) { + content = this.tpl.apply(record.data); + this.bodyContent[record.id] = content; + } + return content; + }, + // Setter and Getter methods for the remoteDataMethod property + setRemoteDataMethod : function (fn) { + this.remoteDataMethod = fn; + }, + + getRemoteDataMethod : function (record, index) { + if (!this.remoteDataMethod) { + return; + } + return this.remoteDataMethod.call(this,record,index); + }, + + onMouseDown : function(e, t) { + if (t.className == 'x-grid3-row-expander') { + e.stopEvent(); + var row = e.getTarget('.x-grid3-row'); + this.toggleRow(row); + } + }, + + renderer : function(v, p, record) { + p.cellAttr = 'rowspan="2"'; + return '
 
'; + }, + + beforeExpand : function(record, body, rowIndex) { + if (this.fireEvent('beforexpand', this, record, body, rowIndex) !== false) { + // If remoteDataMethod is defined then we'll need a div, with a unique ID, + // to place the content + if (this.remoteDataMethod) { + this.tpl = new Ext.Template("
<\div>"); + } + if (this.tpl && this.lazyRender) { + body.innerHTML = this.getBodyContent(record, rowIndex); + } + + return true; + }else{ + return false; + } + }, + + toggleRow : function(row) { + if (typeof row == 'number') { + row = this.grid.view.getRow(row); + } + this[Ext.fly(row).hasClass('x-grid3-row-collapsed') ? 'expandRow' : 'collapseRow'](row); + }, + + expandRow : function(row) { + if (typeof row == 'number') { + row = this.grid.view.getRow(row); + } + var record = this.grid.store.getAt(row.rowIndex); + var body = Ext.DomQuery.selectNode('tr:nth(2) div.x-grid3-row-body', row); + if (this.beforeExpand(record, body, row.rowIndex)) { + this.state[record.id] = true; + Ext.fly(row).replaceClass('x-grid3-row-collapsed', 'x-grid3-row-expanded'); + if (this.fireEvent('expand', this, record, body, row.rowIndex) !== false) { + // If the expand event is successful then get the remoteDataMethod + this.getRemoteDataMethod(record,row.rowIndex); + } + } + }, + + collapseRow : function(row) { + if (typeof row == 'number') { + row = this.grid.view.getRow(row); + } + var record = this.grid.store.getAt(row.rowIndex); + var body = Ext.fly(row).child('tr:nth(1) div.x-grid3-row-body', true); + if (this.fireEvent('beforcollapse', this, record, body, row.rowIndex) !== false) { + this.state[record.id] = false; + Ext.fly(row).replaceClass('x-grid3-row-expanded', 'x-grid3-row-collapsed'); + this.fireEvent('collapse', this, record, body, row.rowIndex); + } + } +}); \ No newline at end of file diff --git a/typo3/sysext/recycler/res/js/search_field.js b/typo3/sysext/recycler/res/js/search_field.js new file mode 100644 index 000000000000..03784b2ab8c1 --- /dev/null +++ b/typo3/sysext/recycler/res/js/search_field.js @@ -0,0 +1,53 @@ +/* + * Ext JS Library 2.2.1 + * Copyright(c) 2006-2009, Ext JS, LLC. + * licensing@extjs.com + * + * http://extjs.com/license + */ + +Ext.app.SearchField = Ext.extend(Ext.form.TwinTriggerField, { + initComponent : function() { + Ext.app.SearchField.superclass.initComponent.call(this); + this.on('specialkey', function(f, e) { + if (e.getKey() == e.ENTER) { + this.onTrigger2Click(); + } + }, this); + }, + + validationEvent: false, + validateOnBlur: false, + trigger1Class: 'x-form-clear-trigger', + trigger2Class: 'x-form-search-trigger', + hideTrigger1: true, + width: 180, + hasSearch : false, + paramName : 'filterTxt', + + onTrigger1Click : function() { + if (this.hasSearch) { + this.el.dom.value = ''; + var o = {start: 0}; + this.store.baseParams = this.store.baseParams || {}; + this.store.baseParams[this.paramName] = ''; + this.store.reload({params:o}); + this.triggers[0].hide(); + this.hasSearch = false; + } + }, + + onTrigger2Click : function() { + var v = this.getRawValue(); + if (v.length < 1) { + this.onTrigger1Click(); + return; + } + var o = {start: 0}; + this.store.baseParams = this.store.baseParams || {}; + this.store.baseParams[this.paramName] = v; + this.store.reload({params:o}); + this.hasSearch = true; + this.triggers[0].show(); + } +}); \ No newline at end of file diff --git a/typo3/sysext/recycler/res/js/t3_recycler.js b/typo3/sysext/recycler/res/js/t3_recycler.js new file mode 100644 index 000000000000..8c5bc68ecd9e --- /dev/null +++ b/typo3/sysext/recycler/res/js/t3_recycler.js @@ -0,0 +1,538 @@ +/*************************************************************** +* Copyright notice +* +* (c) 2009 Julian Kleinhans +* All rights reserved +* +* This script is part of the TYPO3 project. The TYPO3 project is +* free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* The GNU General Public License can be found at +* http://www.gnu.org/copyleft/gpl.html. +* +* This script is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* This copyright notice MUST APPEAR in all copies of the script! +***************************************************************/ + +/** + * ExtJS for the 'recycler' extension. + * Contains the Recycler functions + * + * @author Julian Kleinhans + * @author Erik Frister + * @package TYPO3 + * @subpackage tx_recycler + * @version $Id: t3_recycler.js 17680 2009-03-10 18:02:21Z ohader $ + */ +Ext.onReady(function() { + //Quicktips initialisieren + Ext.QuickTips.init(); + + // @todo: description + // Ext.form.Field.prototype.msgTarget = 'side'; + + // disable loadindicator + Ext.UpdateManager.defaults.showLoadIndicator = false; + + // fire recycler grid + new Recycler.grid.init(); +}); + +Recycler.grid = { + /** + * Initializes the grid + * + * @return void + **/ + init: function() { + /**************************************************** + * row expander + ****************************************************/ + + var expander = new Ext.grid.RowExpander({ + tpl : new Ext.Template( + '
' + + '

' + Recycler.lang.table + ': {table}

' + + '

' + Recycler.lang.crdate + ': {crdate}

' + + '

' + Recycler.lang.tstamp + ': {tstamp}

' + + '

' + Recycler.lang.owner + ': {owner} (UID: {owner_uid})

' + + '

' + Recycler.lang.path + ': {path}

' + + '
' + ) + }); + + /**************************************************** + * pluggable renderer + ****************************************************/ + + var renderTopic = function (value, p, record) { + return String.format('{0}', value, record.data.table, record.data.uid, record.data.pid); + }; + + /**************************************************** + * row checkbox + ****************************************************/ + + var sm = new Ext.grid.CheckboxSelectionModel({ + singleSelect: false + }); + + /**************************************************** + * filter grid + ****************************************************/ + + var filterGrid = function(grid, cmp) { + var filterText = cmp.getValue(); + + addParameters('filterTxt', filterText); + + // load the datastore + grid.getStore().load({ + params: { + start: 0 + } + }); + }; + + /**************************************************** + * grid datastore + ****************************************************/ + var gridDs = 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: Recycler.statics.ajaxController + '&depth=' + Recycler.statics.depthSelection + '&startUid=' + Recycler.statics.startUid + '&cmd=getDeletedRecords&pagingSizeDefault=' + Recycler.statics.pagingSize + '&table=' + Recycler.statics.tableSelection + + }); + + /**************************************************** + * add param to grid store GET + ****************************************************/ + var addParameters = function(key, value) { + var grid = tabs.getComponent(0).getComponent(0); + + var url = grid.getStore().proxy.conn.url; + var urlParts = url.split('?'); + + var params = urlParts[1].split('&'); + var newParams = []; + var k = 0; // used to specify offset if we set a value + + // add our new key / value to the new params if value is not '' + if ('' !== value) { + newParams[0] = key + '=' + value; + k = 1; + } + + // find the key and remove it + var l = params.length; + + for (var i = 0; i < l; i ++) { + if (params[i].indexOf(key + '=') != -1) { + k -= 1; + continue; + } else { + newParams[i + k] = params[i]; + } + } + + // make new url from http + params + url = urlParts[0] + '?' + newParams.join('&'); + + + // set the new url for the store + grid.getStore().proxy.conn.url = url; + }; + + /**************************************************** + * permanent deleting function + ****************************************************/ + + var function_delete = function(ob) { + rowAction(ob, Recycler.lang.cmd_doDelete_confirmText, 'doDelete', Recycler.lang.title_delete, Recycler.lang.text_delete); + }; + + /**************************************************** + * Undeleting function + ****************************************************/ + + var function_undelete = function(ob) { + rowAction(ob, Recycler.lang.sure, 'doUndelete', Recycler.lang.title_undelete, Recycler.lang.text_undelete); + }; + + /**************************************************** + * Row action function ( deleted or undeleted ) + ****************************************************/ + + var rowAction = function(ob, confirmQuestion, cmd, confirmTitle, confirmText) { + // get the 'undeleted records' grid object + var grid = tabs.getComponent(0).getComponent(0); + recArray = grid.getSelectionModel().getSelections(); + + if (recArray.length > 0) { + + // check if a page is checked + var recursiveCheckbox = false; + var arePagesAffected = false; + var tables = []; + var hideRecursive = ('doDelete' == cmd); + + for (iterator=0; iterator < recArray.length; iterator++) { + if (tables.indexOf(recArray[iterator].data.table) < 0) { + tables.push(recArray[iterator].data.table); + } + if (cmd == 'doUndelete' && recArray[iterator].data.table == 'pages' ) { + recursiveCheckbox = true; + arePagesAffected = true; + } + } + + var frmConfirm = new Ext.Window({ + xtype: 'form', + width: 300, + height: 200, + modal: true, + title: confirmTitle, + items: [ + { + xtype: 'label', + text: confirmText + tables.join(', ') + },{ + xtype: 'label', + text: confirmQuestion + },{ + xtype: 'checkbox', + boxLabel: Recycler.lang.boxLabel_undelete_recursive, + name: 'recursiveCheckbox', + disabled: !recursiveCheckbox, + id: 'recursiveCheckbox', + hidden: hideRecursive // hide the checkbox when frm is used to permanently delete + } + ], + buttons: [ + { + text: Recycler.lang.yes, + handler: function(cmp, e) { + tcemainData = new Array(); + + for (iterator=0; iterator < recArray.length; iterator++) { + tcemainData[iterator] = [recArray[iterator].data.table, recArray[iterator].data.uid]; + } + + Ext.Ajax.request({ + url: Recycler.statics.ajaxController + '&cmd=' + cmd, + callback: function(options, success, response) { + if (response.responseText === "1") { + // reload the records and the table selector + grid.getStore().reload(); + Ext.getCmp('tableSelector').store.reload(); + if (arePagesAffected) { + Recycler.utility.updatePageTree(); + } + }else{ + alert('ERROR: '+response.responseText); + } + }, + params: {'data': Ext.encode(tcemainData), 'recursive':frmConfirm.getComponent('recursiveCheckbox').getValue() } + }); + + frmConfirm.destroy(); + } + },{ + text: Recycler.lang.no, + handler: function(cmp, e) { + frmConfirm.destroy(); + } + } + ] + }); + frmConfirm.show(); + + } else { + // no row selected + Ext.MessageBox.show({ + title: Recycler.lang.error_NoSelectedRows_title, + msg: Recycler.lang.error_NoSelectedRows_msg, + buttons: Ext.MessageBox.OK, + minWidth: 300, + minHeight: 200, + icon: Ext.MessageBox.INFO + }); + } + }; + + /**************************************************** + * tab container + ****************************************************/ + + var tabs = new Ext.TabPanel({ + renderTo: Recycler.statics.renderTo, + layoutOnTabChange: true, + activeTab: 0, + width: '99%', + height: 600, + frame: true, + border: false, + defaults: {autoScroll: true}, + plain: true, + buttons: [{ + + /**************************************************** + * Delete button + ****************************************************/ + + id: 'deleteButton', + text: Recycler.lang.deleteButton_text, + tooltip: Recycler.lang.deleteButton_tooltip, + iconCls: 'delete', + disabled: Recycler.statics.deleteDisable, + handler: function_delete + },{ + + /**************************************************** + * Undelete button + ****************************************************/ + + id: 'undeleteButton', + text: Recycler.lang.undeleteButton_text, + tooltip: Recycler.lang.undeleteButton_tooltip, + iconCls: 'undelete', + handler: function_undelete + } + ], + buttonAlign:'left', + items:[ + { + + /**************************************************** + * Deleted records Tab + ****************************************************/ + + id: 'delRecordId', + title: Recycler.lang.deletedTab, + items: [ + { + + /**************************************************** + * Grid + ****************************************************/ + + xtype: 'grid', + loadMask: true, + store: gridDs, + cm: new Ext.grid.ColumnModel([ + sm, + expander, + {header: "UID", width: 10, sortable: true, dataIndex: 'uid'}, + {header: "PID", width: 10, sortable: true, dataIndex: 'pid'}, + {id:'record',header: "Records", width: 60, sortable: true, dataIndex: 'record', renderer: renderTopic}, + {header: "Table", width: 20, sortable: true, dataIndex: 'tableTitle'} + ]), + + view: new Ext.grid.GridView({ + forceFit:true + }), + + bbar: [ + { + + /**************************************************** + * Paging toolbar + ****************************************************/ + id: 'recordPaging', + xtype: 'paging', + store: 'deletedRecordsStore', + pageSize: Recycler.statics.pagingSize, + displayInfo: true, + displayMsg: Recycler.lang.pagingMessage, + emptyMsg: Recycler.lang.pagingEmpty + } + ], + + tbar: [ + Recycler.lang.search, ' ', + new Ext.app.SearchField({ + store: gridDs, + width: 200 + }), + '->', { + + /**************************************************** + * Depth menu + ****************************************************/ + + xtype: 'combo', + lazyRender: true, + valueField: 'depth', + displayField: 'label', + id: 'depthSelector', + mode: 'local', + emptyText: Recycler.lang.depth, + selectOnFocus: true, + readOnly: true, + triggerAction: 'all', + editable: false, + forceSelection: true, + hidden: Recycler.lang.showDepthMenu, + store: new Ext.data.SimpleStore({ + autoLoad: true, + fields: ['depth','label'], + data : [ + ['0', Recycler.lang.depth_0], + ['1', Recycler.lang.depth_1], + ['2', Recycler.lang.depth_2], + ['3', Recycler.lang.depth_3], + ['4', Recycler.lang.depth_4], + ['999', Recycler.lang.depth_infi] + ] + }), + value: Recycler.statics.depthSelection, + listeners: { + 'select': { + fn: function(cmp, rec, index) { + var store = tabs.getComponent(0).getComponent(0).getStore(); + var depth = rec.get('depth'); + + addParameters('depth', depth); + store.load(); + + Ext.getCmp('tableSelector').store.load({ + params: { + depth: depth + } + }); + } + } + } + },'->',{ + + /**************************************************** + * Table menu + ****************************************************/ + + xtype: 'combo', + lazyRender: true, + valueField: 'valueField', + displayField: 'tableTitle', + id: 'tableSelector', + mode: 'local', + emptyText: Recycler.lang.tableMenu_emptyText, + selectOnFocus: true, + readOnly: true, + triggerAction: 'all', + editable: false, + forceSelection: true, + + store: new Ext.data.Store({ + autoLoad: true, + url: Recycler.statics.ajaxController + '&startUid=' + Recycler.statics.startUid + '&cmd=getTables' + '&depth=' + Recycler.statics.depthSelection, + reader: new Ext.data.ArrayReader({}, [ + {name: 'table', type: 'string'}, + {name: 'records', type: 'int'}, + {name: 'valueField', type: 'string'}, + {name: 'tableTitle', type: 'string'} + ]), + listeners: { + 'load': { + fn: function(store, records) { + Ext.getCmp('tableSelector').setValue(Recycler.statics.tableSelection); + }, + single: true + } + } + }), + valueNotFoundText: String.format(Recycler.lang.noValueFound, Recycler.statics.tableSelection), + tpl: '
{tableTitle} ({records})
{tableTitle} ({records})
', + listeners: { + 'select': { + fn: function(cmp, rec, index) { + var store = tabs.getComponent(0).getComponent(0).getStore(); + var table = rec.get('valueField'); + + // do not reload if the table selected has no deleted records - hide all records + if (rec.get('records') <= 0) { + store.filter('uid', '-1'); // never true + return false; + } + + addParameters('table', table); + + store.load({ + params: { + start: 0 + } + }); + } + } + } + } + ], + + sm: sm, + plugins: expander, + loadMask: true, + stripeRows: true, + width: '100%', + height: 530, + collapsible: false, + animCollapse: false, + frame: false, + border: false, + listeners: { + 'render': { + fn: function(cmp) { + cmp.getStore().load(); + }, + single: true + } + } + } + ] + }//,{ + + /**************************************************** + * Lost and found Tab + ****************************************************/ + /* + id: 'lostAndFoundId', + title: Recycler.staticsRecycler.statics.lostFoundTab, + html:'Later' + } */ + ] + }); + } +}; + + +Recycler.utility = { + updatePageTree: function() { + if (top && top.content && top.content.nav_frame && top.content.nav_frame.Tree) { + top.content.nav_frame.Tree.refresh(); + } + } +}; \ No newline at end of file