* 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 <francois@typo3.org>
--- /dev/null
+2009-03-10 Oliver Hader <oliver@typo3.org>
+
+ * 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 <oliver@typo3.org>
+
+ * 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 <typo3@kj187.de>
+
+ * Cleanup: Fixed formatting concerning CGL
+ * Add doc/manual.sxw
+
+2009-02-14 Oliver Hader <oliver@typo3.org>
+
+ * 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 <typo3@kj187.de>
+
+ * Fixed Bug: Wrong total count in pagenavigation
+ * Fixed Bug: Filter doesnt work correct
+ * Fixed Bug: All table view
+
+2009-02-04 Julian Kleinhans <typo3@kj187.de>
+
+ * Coding guidelines clean up after first review
+
+2009-01-26 Julian Kleinhans <typo3@kj187.de>
+
+ * Fixed bug: Change extension to sysext extension
+
+2009-01-23 Oliver Hader <oliver@typo3.org>
+
+ * 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 <oliver@typo3.org>
+
+ * 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 <typo3@kj187.de>
+
+ * Initial release
\ No newline at end of file
--- /dev/null
+<?php
+/***************************************************************
+* Copyright notice
+*
+* (c) 2009 Julian Kleinhans <typo3@kj187.de>
+* 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 <typo3@kj187.de>
+ * @author Erik Frister <erik_frister@otq-solutions.com>
+ * @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
--- /dev/null
+<?php
+/***************************************************************
+* Copyright notice
+*
+* (c) 2009 Julian Kleinhans <typo3@kj187.de>
+* 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 <typo3@kj187.de>
+ * @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 = '<span class="deletedPath">' . $output . '</span>';
+ }
+
+ 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
--- /dev/null
+<?php
+/***************************************************************
+* Copyright notice
+*
+* (c) 2009 Julian Kleinhans <typo3@kj187.de>
+* 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 <typo3@kj187.de>
+ * @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
--- /dev/null
+<?php
+/***************************************************************
+* Copyright notice
+*
+* (c) 2009 Julian Kleinhans <typo3@kj187.de>
+* 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 <typo3@kj187.de>
+ * @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
--- /dev/null
+<?php
+/***************************************************************
+ * Copyright notice
+ *
+ * (c) 2009 Julian Kleinhans <typo3@kj187.de>
+ * 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 <erik_frister@otq-solutions.com>
+ * @author Julian Kleinhans <typo3@kj187.de>
+ * @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
--- /dev/null
+<?php
+
+########################################################################
+# Extension Manager/Repository config file for ext: "recycler"
+#
+# Auto generated 10-03-2009 22:56
+#
+# Manual updates:
+# Only the data in the array - anything else is removed by next write.
+# "version" and "dependencies" must not be touched!
+########################################################################
+
+$EM_CONF[$_EXTKEY] = array(
+ 'title' => '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
--- /dev/null
+<?php
+if (!defined ('TYPO3_MODE')) die ('Access denied.');
+
+$TYPO3_CONF_VARS['BE']['AJAX']['tx_recycler::controller'] = t3lib_extMgm::extPath($_EXTKEY) . 'classes/controller/class.tx_recycler_controller_ajax.php:tx_recycler_controller_ajax->init';
+
+?>
\ No newline at end of file
--- /dev/null
+<?php
+if (!defined ('TYPO3_MODE')) die ('Access denied.');
+
+if (TYPO3_MODE == 'BE') {
+
+ // add module
+
+ t3lib_extMgm::addModulePath('web_txrecyclerM1',t3lib_extMgm::extPath ($_EXTKEY).'mod1/');
+ t3lib_extMgm::addModule('web','txrecyclerM1','',t3lib_extMgm::extPath($_EXTKEY).'mod1/');
+
+}
+
+?>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<T3locallang>
+ <meta type="array">
+ <type>database</type>
+ <description>Language labels for database tables/fields belonging to extension 'recycler'</description>
+ </meta>
+ <data type="array">
+ <languageKey index="default" type="array">
+ <label index="moduleFunction.tx_recycler_modfunc1">Recycler</label>
+ </languageKey>
+ </data>
+</T3locallang>
\ No newline at end of file
--- /dev/null
+<?php
+
+ // DO NOT REMOVE OR CHANGE THESE 3 LINES:
+
+$MCONF['name']='web_txrecyclerM1';
+$MCONF['script']='_DISPATCH';
+$MCONF['access']='user,group';
+
+
+$MLANG['default']['tabs_images']['tab'] = 'moduleicon.gif';
+$MLANG['default']['ll_ref']='LLL:EXT:recycler/mod1/locallang_mod.xml';
+?>
\ No newline at end of file
--- /dev/null
+<?php
+/***************************************************************
+* Copyright notice
+*
+* (c) 2009 Julian Kleinhans <typo3@kj187.de>
+* 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 <typo3@kj187.de>
+ * @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.= '<div id="recyclerContent"></div>';
+ } 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" . '<link rel="stylesheet" type="text/css" href="' . $fileName . '" />' . "\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" . '<script language="javascript" type="text/javascript" src="' . $fileName . '"></script>' . "\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'] = ''; //<input type="image" class="c-inputButton" name="submit" value="Update"' . t3lib_iconWorks::skinImg($GLOBALS['BACK_PATH'], 'gfx/savedok.gif', '') . ' title="' . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.php:rm.saveDoc', 1) . '" />';
+
+ 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
--- /dev/null
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<T3locallang>
+ <meta type="array">
+ <type>module</type>
+ <description>Language labels for module "web_txrecyclerM1"</description>
+ </meta>
+ <data type="array">
+ <languageKey index="default" type="array">
+ <label index="title">Recycler</label>
+ <label index="label_alltables">All tables</label>
+ <label index="js.label_lostandfound">Lost and found</label>
+ <label index="js.label_deletedTab">Deleted data</label>
+ <label index="js.label_loadMessage">Please wait...<br/>Records would be loaded!</label>
+ <label index="js.label_doDelete_confirmText">Delete this record AND if page ALL subrecords ? <br/>ATTENTION: Data will be finally deleted from the database</label>
+ <label index="js.label_deleteButton_text">Delete</label>
+ <label index="js.label_deleteButton_tooltip">Delete selected items</label>
+ <label index="js.label_undeleteButton_text">Undelete</label>
+ <label index="js.label_undeleteButton_tooltip">Undelete selected items</label>
+ <label index="js.label_error_NoSelectedRows_title">No row selected</label>
+ <label index="js.label_error_NoSelectedRows_msg">You must select a row!</label>
+ <label index="js.label_yes">Yes</label>
+ <label index="js.label_no">No</label>
+ <label index="js.label_sure">Are you Sure?</label>
+ <label index="js.label_crdate">Create</label>
+ <label index="js.label_owner">Owner</label>
+ <label index="js.label_tstamp">Last edit</label>
+ <label index="js.label_clear">Clear</label>
+ <label index="js.label_filter">Filter</label>
+ <label index="js.label_title_undelete">Undelete?</label>
+ <label index="js.label_text_undelete">Undelete records from tables: </label>
+ <label index="js.label_title_delete">Delete?</label>
+ <label index="js.label_text_delete">Delete records from tables: </label>
+ <label index="js.label_boxLabel_undelete_recursive">Undelete recursively</label>
+ <label index="js.label_tableMenu_emptyText">Choose a table...</label>
+ <label index="js.label_filter_emptyText">Keyword</label>
+ <label index="js.label_search">Search:</label>
+ <label index="js.label_pagingMessage">Displaying records {0} - {1} of {2}</label>
+ <label index="js.label_pagingEmpty">No records to display</label>
+ <label index="js.label_noValueFound">No records for {0}</label>
+ </languageKey>
+ </data>
+</T3locallang>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<T3locallang>
+ <meta type="array">
+ <type>module</type>
+ <description>Language labels for module "tools_txrecyclerM1" - header, description</description>
+ </meta>
+ <data type="array">
+ <languageKey index="default" type="array">
+ <label index="mlang_tabs_tab">Recycler</label>
+ </languageKey>
+ </data>
+</T3locallang>
\ No newline at end of file
--- /dev/null
+<!-- ###FULLDOC### begin -->
+<div class="typo3-fullDoc">
+ <!-- Page header with buttons, path details and csh -->
+ <div id="typo3-docheader">
+ <div id="typo3-docheader-row1">
+ <div class="buttonsleft">###BUTTONLIST_LEFT###</div>
+ <div class="buttonsright">###BUTTONLIST_RIGHT###</div>
+ </div>
+ <div id="typo3-docheader-row2">
+ <div class="docheader-row2-left"><div class="docheader-funcmenu">###FUNC_MENU###</div></div>
+ <div class="docheader-row2-right">###PAGEPATH######PAGEINFO###</div>
+ </div>
+ </div>
+ <!-- Content of module, for instance listing, info or editing -->
+ <div id="typo3-docbody">
+ <div id="typo3-inner-docbody">
+ <h1>###TITLE###</h1>
+ ###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
--- /dev/null
+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
--- /dev/null
+/*
+ * 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 '<div class="x-grid3-row-expander"> </div>';
+ },
+
+ 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 id=\"remData" + rowIndex + "\" class=\"rem-data-expand\"><\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
--- /dev/null
+/*
+ * 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
--- /dev/null
+/***************************************************************
+* Copyright notice
+*
+* (c) 2009 Julian Kleinhans <typo3@kj187.de>
+* 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 <typo3@kj187.de>
+ * @author Erik Frister <erik_frister@otq-solutions.com>
+ * @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(
+ '<br/>' +
+ '<p style="margin-left:45px;"><b>' + Recycler.lang.table + ':</b> {table}</p>' +
+ '<p style="margin-left:45px;"><b>' + Recycler.lang.crdate + ':</b> {crdate}</p>' +
+ '<p style="margin-left:45px;"><b>' + Recycler.lang.tstamp + ':</b> {tstamp}</p>' +
+ '<p style="margin-left:45px;"><b>' + Recycler.lang.owner + ':</b> {owner} (UID: {owner_uid})</p>' +
+ '<p style="margin-left:45px;"><b>' + Recycler.lang.path + ':</b> {path}</p>' +
+ '<br/>'
+ )
+ });
+
+ /****************************************************
+ * 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: '<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(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