Added feature #10455: Improve recycling of deleted records
authorOliver Hader <oliver.hader@typo3.org>
Tue, 10 Mar 2009 22:19:14 +0000 (22:19 +0000)
committerOliver Hader <oliver.hader@typo3.org>
Tue, 10 Mar 2009 22:19:14 +0000 (22:19 +0000)
git-svn-id: https://svn.typo3.org/TYPO3v4/Core/trunk@5180 709f56b5-9817-0410-a4d7-c38de5d9e867

36 files changed:
ChangeLog
typo3/sysext/recycler/ChangeLog [new file with mode: 0644]
typo3/sysext/recycler/classes/controller/class.tx_recycler_controller_ajax.php [new file with mode: 0644]
typo3/sysext/recycler/classes/helper/class.tx_recycler_helper.php [new file with mode: 0644]
typo3/sysext/recycler/classes/model/class.tx_recycler_model_deletedRecords.php [new file with mode: 0644]
typo3/sysext/recycler/classes/model/class.tx_recycler_model_tables.php [new file with mode: 0644]
typo3/sysext/recycler/classes/view/class.tx_recycler_view_deletedRecords.php [new file with mode: 0644]
typo3/sysext/recycler/doc/manual.sxw [new file with mode: 0644]
typo3/sysext/recycler/ext_emconf.php [new file with mode: 0644]
typo3/sysext/recycler/ext_icon.gif [new file with mode: 0644]
typo3/sysext/recycler/ext_localconf.php [new file with mode: 0644]
typo3/sysext/recycler/ext_tables.php [new file with mode: 0644]
typo3/sysext/recycler/locallang_db.xml [new file with mode: 0644]
typo3/sysext/recycler/mod1/clear.gif [new file with mode: 0644]
typo3/sysext/recycler/mod1/conf.php [new file with mode: 0644]
typo3/sysext/recycler/mod1/index.php [new file with mode: 0644]
typo3/sysext/recycler/mod1/locallang.xml [new file with mode: 0644]
typo3/sysext/recycler/mod1/locallang_mod.xml [new file with mode: 0644]
typo3/sysext/recycler/mod1/mod_template.html [new file with mode: 0644]
typo3/sysext/recycler/mod1/moduleicon.gif [new file with mode: 0644]
typo3/sysext/recycler/res/css/customExtJs.css [new file with mode: 0644]
typo3/sysext/recycler/res/icons/accept.png [new file with mode: 0644]
typo3/sysext/recycler/res/icons/arrow_redo.png [new file with mode: 0644]
typo3/sysext/recycler/res/icons/arrow_rotate_anticlockwise.png [new file with mode: 0644]
typo3/sysext/recycler/res/icons/bin.png [new file with mode: 0644]
typo3/sysext/recycler/res/icons/bin_closed.png [new file with mode: 0644]
typo3/sysext/recycler/res/icons/bin_empty.png [new file with mode: 0644]
typo3/sysext/recycler/res/icons/database_save.png [new file with mode: 0644]
typo3/sysext/recycler/res/icons/delete.gif [new file with mode: 0644]
typo3/sysext/recycler/res/icons/filter_clear.png [new file with mode: 0644]
typo3/sysext/recycler/res/icons/filter_refresh.png [new file with mode: 0644]
typo3/sysext/recycler/res/icons/recycler.gif [new file with mode: 0644]
typo3/sysext/recycler/res/icons/recycler2.gif [new file with mode: 0644]
typo3/sysext/recycler/res/js/ext_expander.js [new file with mode: 0644]
typo3/sysext/recycler/res/js/search_field.js [new file with mode: 0644]
typo3/sysext/recycler/res/js/t3_recycler.js [new file with mode: 0644]

index bb9c13a..2ac7e00 100755 (executable)
--- 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  <francois@typo3.org>
 
diff --git a/typo3/sysext/recycler/ChangeLog b/typo3/sysext/recycler/ChangeLog
new file mode 100644 (file)
index 0000000..b001d22
--- /dev/null
@@ -0,0 +1,61 @@
+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
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 (file)
index 0000000..d3886c5
--- /dev/null
@@ -0,0 +1,190 @@
+<?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
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 (file)
index 0000000..85fa8a1
--- /dev/null
@@ -0,0 +1,214 @@
+<?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
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 (file)
index 0000000..f524dfc
--- /dev/null
@@ -0,0 +1,442 @@
+<?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
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 (file)
index 0000000..2cbf39e
--- /dev/null
@@ -0,0 +1,100 @@
+<?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
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 (file)
index 0000000..cf56a6e
--- /dev/null
@@ -0,0 +1,88 @@
+<?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
diff --git a/typo3/sysext/recycler/doc/manual.sxw b/typo3/sysext/recycler/doc/manual.sxw
new file mode 100644 (file)
index 0000000..df7f599
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 (file)
index 0000000..20b7a92
--- /dev/null
@@ -0,0 +1,47 @@
+<?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
diff --git a/typo3/sysext/recycler/ext_icon.gif b/typo3/sysext/recycler/ext_icon.gif
new file mode 100644 (file)
index 0000000..9f0bf9f
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 (file)
index 0000000..66de57d
--- /dev/null
@@ -0,0 +1,6 @@
+<?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
diff --git a/typo3/sysext/recycler/ext_tables.php b/typo3/sysext/recycler/ext_tables.php
new file mode 100644 (file)
index 0000000..db2ae70
--- /dev/null
@@ -0,0 +1,13 @@
+<?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
diff --git a/typo3/sysext/recycler/locallang_db.xml b/typo3/sysext/recycler/locallang_db.xml
new file mode 100644 (file)
index 0000000..7cdd4f0
--- /dev/null
@@ -0,0 +1,12 @@
+<?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
diff --git a/typo3/sysext/recycler/mod1/clear.gif b/typo3/sysext/recycler/mod1/clear.gif
new file mode 100644 (file)
index 0000000..9ed1269
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 (file)
index 0000000..28c1b1b
--- /dev/null
@@ -0,0 +1,12 @@
+<?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
diff --git a/typo3/sysext/recycler/mod1/index.php b/typo3/sysext/recycler/mod1/index.php
new file mode 100644 (file)
index 0000000..61b3275
--- /dev/null
@@ -0,0 +1,345 @@
+<?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
diff --git a/typo3/sysext/recycler/mod1/locallang.xml b/typo3/sysext/recycler/mod1/locallang.xml
new file mode 100644 (file)
index 0000000..9597782
--- /dev/null
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<T3locallang>
+       <meta type="array">
+               <type>module</type>
+               <description>Language labels for module &quot;web_txrecyclerM1&quot;</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...&lt;br/&gt;Records would be loaded!</label>
+                       <label index="js.label_doDelete_confirmText">Delete this record AND if page ALL subrecords ? &lt;br/&gt;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
diff --git a/typo3/sysext/recycler/mod1/locallang_mod.xml b/typo3/sysext/recycler/mod1/locallang_mod.xml
new file mode 100644 (file)
index 0000000..d55e386
--- /dev/null
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<T3locallang>
+       <meta type="array">
+               <type>module</type>
+               <description>Language labels for module &quot;tools_txrecyclerM1&quot; - 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
diff --git a/typo3/sysext/recycler/mod1/mod_template.html b/typo3/sysext/recycler/mod1/mod_template.html
new file mode 100644 (file)
index 0000000..281c4c5
--- /dev/null
@@ -0,0 +1,36 @@
+<!-- ###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
diff --git a/typo3/sysext/recycler/mod1/moduleicon.gif b/typo3/sysext/recycler/mod1/moduleicon.gif
new file mode 100644 (file)
index 0000000..9f0bf9f
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 (file)
index 0000000..c48cefb
--- /dev/null
@@ -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 (file)
index 0000000..89c8129
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 (file)
index 0000000..fdc394c
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 (file)
index 0000000..46c75aa
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 (file)
index 0000000..ebad933
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 (file)
index 0000000..afe22ba
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 (file)
index 0000000..375b8bf
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 (file)
index 0000000..44c06dd
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 (file)
index 0000000..5e2a3b1
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 (file)
index 0000000..cce652e
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 (file)
index 0000000..ff803be
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 (file)
index 0000000..2de775a
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 (file)
index 0000000..9f0bf9f
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 (file)
index 0000000..44f02d5
--- /dev/null
@@ -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 '<div class="x-grid3-row-expander">&#160;</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
diff --git a/typo3/sysext/recycler/res/js/search_field.js b/typo3/sysext/recycler/res/js/search_field.js
new file mode 100644 (file)
index 0000000..03784b2
--- /dev/null
@@ -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 (file)
index 0000000..8c5bc68
--- /dev/null
@@ -0,0 +1,538 @@
+/***************************************************************
+*  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 &gt; 0"><div ext:qtip="{table} ({records})" class="x-combo-list-item">{tableTitle} ({records}) </div></tpl><tpl if="records &lt; 1"><div ext:qtip="{table} ({records})" class="x-combo-list-item x-item-disabled">{tableTitle} ({records}) </div></tpl></tpl>',
+                                                                       listeners: {
+                                                                               'select': {
+                                                                                       fn: function(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