Added Feature #8515: Speed up tslib_cObj->getTreeList by caching its results
authorIngo Renner <ingo.renner@typo3.org>
Thu, 7 Aug 2008 09:56:03 +0000 (09:56 +0000)
committerIngo Renner <ingo.renner@typo3.org>
Thu, 7 Aug 2008 09:56:03 +0000 (09:56 +0000)
git-svn-id: https://svn.typo3.org/TYPO3v4/Core/trunk@3947 709f56b5-9817-0410-a4d7-c38de5d9e867

ChangeLog
t3lib/class.t3lib_tcemain.php
typo3/sysext/cms/ext_localconf.php
typo3/sysext/cms/ext_tables.sql
typo3/sysext/cms/tslib/class.tslib_content.php
typo3/sysext/cms/tslib/hooks/class.tx_cms_treelistcacheupdate.php [new file with mode: 0644]

index b1248c1..e5f4c67 100755 (executable)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
+2008-08-07  Ingo Renner  <ingo@typo3.org>
+
+       * Added Feature #8515: Speed up tslib_cObj->getTreeList by caching its results
+
 2008-08-06  Ingo Renner  <ingo@typo3.org>
 
        * Fixed bug: PHP 5.3 throws a warning in class.t3lib_befunc.php on line 3348: trim() expects parameter 1 to be string, array given
index e6bf2a3..ce55418 100755 (executable)
@@ -7022,7 +7022,8 @@ State was change by %s (username: %s)
                                if ($this->admin || $this->BE_USER->getTSConfigVal('options.clearCache.all'))   {
                                        if (t3lib_extMgm::isLoaded('cms'))      {
                                                $this->internal_clearPageCache();
-                                               $GLOBALS['TYPO3_DB']->exec_DELETEquery('cache_pagesection','');
+                                               $GLOBALS['TYPO3_DB']->exec_DELETEquery('cache_pagesection', '');
+                                               $GLOBALS['TYPO3_DB']->exec_DELETEquery('cache_treelist', '');
                                        }
                                        $GLOBALS['TYPO3_DB']->exec_DELETEquery('cache_hash','');
 
index 9479c29..331a55d 100755 (executable)
@@ -21,4 +21,10 @@ $TYPO3_CONF_VARS['SC_OPTIONS']['ext/install']['compat_version']['cms'] = array(
 );
 
 
+       // registering hooks for the treelist cache
+$TYPO3_CONF_VARS['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass'][] = 'EXT:cms/tslib/hooks/class.tx_cms_treelistcacheupdate.php:&tx_cms_treelistCacheUpdate';
+$TYPO3_CONF_VARS['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processCmdmapClass'][]  = 'EXT:cms/tslib/hooks/class.tx_cms_treelistcacheupdate.php:&tx_cms_treelistCacheUpdate';
+$TYPO3_CONF_VARS['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['moveRecordClass'][]     = 'EXT:cms/tslib/hooks/class.tx_cms_treelistcacheupdate.php:&tx_cms_treelistCacheUpdate';
+
+
 ?>
\ No newline at end of file
index 83dcae8..25447a2 100755 (executable)
@@ -72,6 +72,19 @@ CREATE TABLE cache_imagesizes (
 ) ENGINE=InnoDB;
 
 
+#
+# Table structure for table 'cache_treelist'
+#
+CREATE TABLE cache_treelist (
+  md5hash char(32) DEFAULT '' NOT NULL,
+  pid int(11) DEFAULT '0' NOT NULL,
+  treelist text NOT NULL,
+  tstamp int(11) DEFAULT '0' NOT NULL,
+  expires int(11) unsigned  DEFAULT '0' NOT NULL,
+  PRIMARY KEY (md5hash)
+) ENGINE=InnoDB;
+
+
 #
 # Table structure for table 'fe_groups'
 #
index 68ae91f..80a08bf 100755 (executable)
@@ -6472,13 +6472,19 @@ class tslib_cObj {
        }
 
        /**
-        * Generates a list of Page-uid's from $id. List does not include $id itself (unless the id specified is negative in which case it does!)
+        * Generates a list of Page-uid's from $id. List does not include $id itself
+        * (unless the id specified is negative in which case it does!)
         * The only pages WHICH PREVENTS DECENDING in a branch are
         *    - deleted pages,
-        *    - pages in a recycler or of the Backend User Section type
-        *    - pages that has the extendToSubpages set, WHERE start/endtime, hidden and fe_users would hide the records.
-        * Apart from that, pages with enable-fields excluding them, will also be removed. HOWEVER $dontCheckEnableFields set will allow enableFields-excluded pages to be included anyway - including extendToSubpages sections!
-        * Mount Pages are also descended but notice that these ID numbers are not useful for links unless the correct MPvar is set.
+        *    - pages in a recycler (doktype = 255) or of the Backend User Section (doktpe = 6) type
+        *    - pages that has the extendToSubpages set, WHERE start/endtime, hidden
+        *              and fe_users would hide the records.
+        * Apart from that, pages with enable-fields excluding them, will also be
+        * removed. HOWEVER $dontCheckEnableFields set will allow
+        * enableFields-excluded pages to be included anyway - including
+        * extendToSubpages sections!
+        * Mount Pages are also descended but notice that these ID numbers are not
+        * useful for links unless the correct MPvar is set.
         *
         * @param       integer         The id of the start page from which point in the page tree to decend. IF NEGATIVE the id itself is included in the end of the list (only if $begin is 0) AND the output does NOT contain a last comma. Recommended since it will resolve the input ID for mount pages correctly and also check if the start ID actually exists!
         * @param       integer         The number of levels to decend. If you want to decend infinitely, just set this to 100 or so. Should be at least "1" since zero will just make the function return (no decend...)
@@ -6491,84 +6497,143 @@ class tslib_cObj {
         * @return      string          Returns the list with a comma in the end (if any pages selected and not if $id is negative and $id is added itself) - which means the input page id can comfortably be appended to the output string if you need it to.
         * @see tslib_fe::checkEnableFields(), tslib_fe::checkPagerecordForIncludeSection()
         */
-       function getTreeList($id,$depth,$begin=0,$dontCheckEnableFields=FALSE,$addSelectFields='',$moreWhereClauses='', $prevId_array=array(), $recursionLevel=0)       {
+       public function getTreeList($id, $depth, $begin = 0, $dontCheckEnableFields = false, $addSelectFields = '', $moreWhereClauses = '', array $prevId_array = array(), $recursionLevel = 0) {
 
                        // Init vars:
-               $allFields = 'uid,hidden,starttime,endtime,fe_group,extendToSubpages,doktype,php_tree_stop,mount_pid,mount_pid_ol,t3ver_state'.$addSelectFields;
-               $depth = intval($depth);
-               $begin = intval($begin);
-               $id = intval($id);
-               $theList = '';
-               $addId = 0;
+               $allFields   = 'uid,hidden,starttime,endtime,fe_group,extendToSubpages,doktype,php_tree_stop,mount_pid,mount_pid_ol,t3ver_state'.$addSelectFields;
+               $depth       = intval($depth);
+               $begin       = intval($begin);
+               $id          = intval($id);
+               $theList     = '';
+               $addId       = 0;
+               $requestHash = '';
 
-               if ($id)        {
+               if ($id) {
 
                                // First level, check id (second level, this is done BEFORE the recursive call)
-                       if (!$recursionLevel)   {
+                       if (!$recursionLevel) {
+
+                                       // check tree list cache
+
+                                       // first, create the hash for this request - not sure yet whether we need all these parameters though
+                               $parameters = array(
+                                       $id,
+                                       $depth,
+                                       $begin,
+                                       $dontCheckEnableFields,
+                                       $addSelectFields,
+                                       $moreWhereClauses,
+                                       $prevId_array
+                               );
+                               $requestHash = md5(serialize($parameters));
+
+                               $cacheEntry = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
+                                       'treelist',
+                                       'cache_treelist',
+                                       'md5hash = \'' . $requestHash . '\' AND ( expires > ' . time() . ' OR expires = 0 )'
+                               );
+
+                               if (!empty($cacheEntry[0]['treelist'])) {
+                                               // cache hit
+                                       t3lib_div::devLog('Cache Treelist: HIT', 'tslib_cObj');
+                                       return $cacheEntry[0]['treelist'];
+                               } else {
+                                               // cache miss
+                                       t3lib_div::devLog('Cache Treelist: MISS', 'tslib_cObj');
+                               }
+
                                        // If Id less than zero it means we should add the real id to list:
-                               if ($id < 0)    {
+                               if ($id < 0) {
                                        $addId = $id = abs($id);
                                }
                                        // Check start page:
-                               if ($GLOBALS['TSFE']->sys_page->getRawRecord('pages',$id,'uid'))        {
+                               if ($GLOBALS['TSFE']->sys_page->getRawRecord('pages', $id, 'uid')) {
 
                                                // Find mount point if any:
                                        $mount_info = $GLOBALS['TSFE']->sys_page->getMountPointInfo($id);
-                                       if (is_array($mount_info))      {
+                                       if (is_array($mount_info)) {
                                                $id = $mount_info['mount_pid'];
                                                        // In Overlay mode, use the mounted page uid as added ID!:
-                                               if ($addId && $mount_info['overlay'])   {
+                                               if ($addId && $mount_info['overlay']) {
                                                        $addId = $id;
                                                }
                                        }
-                               } else return '';       // Return blank if the start page was NOT found at all!
+                               } else {
+                                       return '';      // Return blank if the start page was NOT found at all!
+                               }
                        }
 
                                // Add this ID to the array of IDs
-                       if ($begin<=0)  {
+                       if ($begin <= 0) {
                                $prevId_array[] = $id;
                        }
 
                                // Select sublevel:
-                       if ($depth>0)   {
-                               $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery($allFields, 'pages', 'pid='.intval($id).' AND deleted=0 '.$moreWhereClauses, '' ,'sorting');
-                               while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res))      {
-                                       $GLOBALS['TSFE']->sys_page->versionOL('pages',$row);
-
-                                       if ($row['doktype']==255 || $row['doktype']==6 || $row['t3ver_state']>0)        { unset($row); }        // Doing this after the overlay to make sure changes in the overlay are respected.
-
-                                       if (is_array($row))     {
-                                                       // Find mount point if any:
-                                               $next_id = $row['uid'];
-                                               $mount_info = $GLOBALS['TSFE']->sys_page->getMountPointInfo($next_id, $row);
-                                                       // Overlay mode:
-                                               if (is_array($mount_info) && $mount_info['overlay'])    {
-                                                       $next_id = $mount_info['mount_pid'];
-                                                       $res2 = $GLOBALS['TYPO3_DB']->exec_SELECTquery($allFields, 'pages', 'uid='.intval($next_id).' AND deleted=0 '.$moreWhereClauses, '' ,'sorting');
-                                                       $row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res2);
-                                                       $GLOBALS['TYPO3_DB']->sql_free_result($res2);
-                                                       $GLOBALS['TSFE']->sys_page->versionOL('pages',$row);
+                       if ($depth > 0) {
+                               $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
+                                       $allFields,
+                                       'pages',
+                                       'pid = '.intval($id).' AND deleted = 0 '.$moreWhereClauses,
+                                       '',
+                                       'sorting'
+                               );
+
+                               while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
+                                       $GLOBALS['TSFE']->sys_page->versionOL('pages', $row);
 
-                                                       if ($row['doktype']==255 || $row['doktype']==6 || $row['t3ver_state']>0)        { unset($row); }        // Doing this after the overlay to make sure changes in the overlay are respected.
+                                       if ($row['doktype'] == 255 || $row['doktype'] == 6 || $row['t3ver_state'] > 0) {
+                                                       // Doing this after the overlay to make sure changes
+                                                       // in the overlay are respected.
+                                                       // However, we do not process pages below of and
+                                                       // including of type recycler and BE user section
+                                               continue;
+                                       }
+
+                                               // Find mount point if any:
+                                       $next_id    = $row['uid'];
+                                       $mount_info = $GLOBALS['TSFE']->sys_page->getMountPointInfo($next_id, $row);
+
+                                               // Overlay mode:
+                                       if (is_array($mount_info) && $mount_info['overlay']) {
+                                               $next_id = $mount_info['mount_pid'];
+
+                                               $res2 = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
+                                                       $allFields,
+                                                       'pages',
+                                                       'uid = '.intval($next_id).' AND deleted = 0 '.$moreWhereClauses,
+                                                       '' ,
+                                                       'sorting'
+                                               );
+                                               $row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res2);
+                                               $GLOBALS['TYPO3_DB']->sql_free_result($res2);
+
+                                               $GLOBALS['TSFE']->sys_page->versionOL('pages', $row);
+
+                                               if ($row['doktype'] == 255 || $row['doktype'] == 6 || $row['t3ver_state'] > 0) {
+                                                               // Doing this after the overlay to make sure
+                                                               // changes in the overlay are respected.
+
+                                                               // see above
+                                                       continue;
                                                }
-                                                       // Add record:
-                                               if (is_array($row) && ($dontCheckEnableFields || $GLOBALS['TSFE']->checkPagerecordForIncludeSection($row)))     {
-                                                               // Add ID to list:
-                                                       if ($begin<=0)  {
-                                                               if ($dontCheckEnableFields || $GLOBALS['TSFE']->checkEnableFields($row))        {
-                                                                       $theList.= $next_id.',';
-                                                               }
+                                       }
+                                               // Add record:
+                                       if ($dontCheckEnableFields || $GLOBALS['TSFE']->checkPagerecordForIncludeSection($row)) {
+                                                       // Add ID to list:
+                                               if ($begin <= 0) {
+                                                       if ($dontCheckEnableFields || $GLOBALS['TSFE']->checkEnableFields($row)) {
+                                                               $theList.= $next_id.',';
                                                        }
-                                                               // Next level:
-                                                       if ($depth>1 && !$row['php_tree_stop']) {
-                                                                       // Normal mode:
-                                                               if (is_array($mount_info) && !$mount_info['overlay'])   {
-                                                                       $next_id = $mount_info['mount_pid'];
-                                                               }
-                                                                       // Call recursively, if the id is not in prevID_array:
-                                                               if (!in_array($next_id,$prevId_array))  {
-                                                                       $theList.= tslib_cObj::getTreeList($next_id, $depth-1, $begin-1, $dontCheckEnableFields, $addSelectFields, $moreWhereClauses, $prevId_array, $recursionLevel+1);
-                                                               }
+                                               }
+                                                       // Next level:
+                                               if ($depth > 1 && !$row['php_tree_stop']) {
+                                                               // Normal mode:
+                                                       if (is_array($mount_info) && !$mount_info['overlay']) {
+                                                               $next_id = $mount_info['mount_pid'];
+                                                       }
+                                                               // Call recursively, if the id is not in prevID_array:
+                                                       if (!in_array($next_id, $prevId_array)) {
+                                                               $theList.= tslib_cObj::getTreeList($next_id, $depth-1, $begin-1, $dontCheckEnableFields, $addSelectFields, $moreWhereClauses, $prevId_array, $recursionLevel+1);
                                                        }
                                                }
                                        }
@@ -6577,14 +6642,24 @@ class tslib_cObj {
                        }
                }
                        // If first run, check if the ID should be returned:
-               if (!$recursionLevel)   {
-                       if ($addId)     {
-                               if ($begin>0)   {
+               if (!$recursionLevel) {
+                       if ($addId) {
+                               if ($begin > 0) {
                                        $theList.= 0;
                                } else {
                                        $theList.= $addId;
                                }
                        }
+
+                       $GLOBALS['TYPO3_DB']->exec_INSERTquery(
+                               'cache_treelist',
+                               array(
+                                       'md5hash'  => $requestHash,
+                                       'pid'      => $id,
+                                       'treelist' => $theList,
+                                       'tstamp'   => time()
+                               )
+                       );
                }
                        // Return list:
                return $theList;
diff --git a/typo3/sysext/cms/tslib/hooks/class.tx_cms_treelistcacheupdate.php b/typo3/sysext/cms/tslib/hooks/class.tx_cms_treelistcacheupdate.php
new file mode 100644 (file)
index 0000000..dec425a
--- /dev/null
@@ -0,0 +1,452 @@
+<?php
+/***************************************************************
+*  Copyright notice
+*
+*  (c) 2008 Ingo Renner (ingo@typo3.org)
+*  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.
+*  A copy is found in the textfile GPL.txt and important notices to the license
+*  from the author is found in LICENSE.txt distributed with these scripts.
+*
+*
+*  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!
+***************************************************************/
+
+
+/**
+ * Class that hooks into TCEmain and listens for updates to pages to update the
+ * treelist cache
+ *
+ * @author     Ingo Renner <ingo@typo3.org>
+ * @package TYPO3
+ * @subpackage tslib
+ */
+class tx_cms_treelistCacheUpdate {
+
+               // should not be manipulated from others except through the
+               // configuration provided @see __construct()
+       private $updateRequiringFields = array(
+               'pid',
+               'php_tree_stop',
+               'extendToSubpages'
+       );
+
+       /**
+        * constructor, adds update requiring fields to the default ones
+        *
+        */
+       public function __construct() {
+
+                       // as enableFields can be set dynamically we add them here
+               $pagesEnableFields = $GLOBALS['TCA']['pages']['ctrl']['enablecolumns'];
+               foreach ($pagesEnableFields as $pagesEnableField) {
+                       $this->updateRequiringFields[] = $pagesEnableField;
+               }
+               $this->updateRequiringFields[] = $GLOBALS['TCA']['pages']['ctrl']['delete'];
+
+                       // extension can add fields to the pages table that require an
+                       // update of the treelist cache, too; so we also add those
+                       // example: $TYPO3_CONF_VARS['BE']['additionalTreelistUpdateFields'] .= ',my_field';
+               if (!empty($GLOBALS['TYPO3_CONF_VARS']['BE']['additionalTreelistUpdateFields'])) {
+                       $additionalTreelistUpdateFields = t3lib_div::trimExplode(
+                               ',',
+                               $GLOBALS['TYPO3_CONF_VARS']['BE']['additionalTreelistUpdateFields'],
+                               true
+                       );
+
+                       $this->updateRequiringFields += $additionalTreelistUpdateFields;
+               }
+
+       }
+
+       /**
+        * waits for TCEmain commands and looks for changed pages, if found further
+        * changes take place to determine whether the cache needs to be updated
+        *
+        * @param       string  TCEmain operation status, either 'new' or 'update'
+        * @param       string  the DB table the operation was carried out on
+        * @param       mixed   the record's uid for update records, a string to look the record's uid up after it has been created
+        * @param       array   array of changed fiels and their new values
+        * @param       t3lib_TCEmain   TCEmain parent object
+        */
+       public function processDatamap_afterDatabaseOperations($status, $table, $recordId, array $updatedFields, t3lib_TCEmain $tceMain) {
+
+               if ($table == 'pages' && $this->requiresUpdate($updatedFields)) {
+                       $affectedPagePid = 0;
+                       $affectedPageUid = 0;
+
+                       if ($status == 'new') {
+                                       // detect new pages
+
+                                       // resolve the uid
+                               $affectedPageUid = $tceMain->substNEWwithIDs[$recordId];
+                               $affectedPagePid = $updatedFields['pid'];
+                       } elseif ($status == 'update') {
+                                       // detect updated pages
+
+                               $affectedPageUid = $recordId;
+
+                               /*
+                                when updating a page the pid is not directly available so we
+                                need to retrieve it ourselves.
+                               */
+                               $fullPageRecord  = t3lib_BEfunc::getRecord($table, $recordId);
+                               $affectedPagePid = $fullPageRecord['pid'];
+                       }
+
+                       $clearCacheActions = $this->determineClearCacheActions(
+                               $status,
+                               $updatedFields
+                       );
+
+                       $this->processClearCacheActions(
+                               $affectedPageUid,
+                               $affectedPagePid,
+                               $updatedFields,
+                               $clearCacheActions
+                       );
+               }
+       }
+
+       /**
+        * waits for TCEmain commands and looks for deleted pages, if found further
+        * changes take place to determine whether the cache needs to be updated
+        *
+        * @param       string  the TCE command
+        * @param       string  the record's table
+        * @param       integer the record's uid
+        * @param       array   the commands value, typically an array with more detailed command information
+        * @param       t3lib_TCEmain   the TCEmain parent object
+        */
+       public function processCmdmap_postProcess($command, $table, $recordId, $commandValue, t3lib_TCEmain $tceMain) {
+
+               if ($table == 'pages' && $command == 'delete') {
+
+                       $deletedRecord = t3lib_BEfunc::getRecord(
+                               $table,
+                               $recordId,
+                               '*',
+                               '',
+                               false
+                       );
+
+                       $affectedPageUid = $deletedRecord['uid'];
+                       $affectedPagePid = $deletedRecord['pid'];
+                               // faking the updated fields
+                       $updatedFields   = array('deleted' => 1);
+
+                       $clearCacheActions = $this->determineClearCacheActions(
+                               'update',
+                               $updatedFields
+                       );
+
+                       $this->processClearCacheActions(
+                               $affectedPageUid,
+                               $affectedPagePid,
+                               $updatedFields,
+                               $clearCacheActions
+                       );
+               }
+       }
+
+       /**
+        * waits for TCEmain commands and looks for moved pages, if found further
+        * changes take place to determine whether the cache needs to be updated
+        *
+        * @param       string  table name of the moved record
+        * @param       integer the record's uid
+        * @param       integer the record's destination page id
+        * @param       array   the record that moved
+        * @param       array   array of changed fields
+        * @param       t3lib_TCEmain   TCEmain parent object
+        */
+       public function moveRecord_firstElementPostProcess($table, $recordId, $destinationPid, array $movedRecord, array $updatedFields, t3lib_TCEmain $tceMain) {
+
+               if ($table == 'pages' && $this->requiresUpdate($updatedFields)) {
+
+                       $affectedPageUid    = $recordId;
+                       $affectedPageOldPid = $movedRecord['pid'];
+                       $affectedPageNewPid = $updatedFields['pid'];
+
+                       $clearCacheActions = $this->determineClearCacheActions(
+                               'update',
+                               $updatedFields
+                       );
+
+                               // clear treelist entries for old parent page
+                       $this->processClearCacheActions(
+                               $affectedPageUid,
+                               $affectedPageOldPid,
+                               $updatedFields,
+                               $clearCacheActions
+                       );
+                               // clear treelist entries for new parent page
+                       $this->processClearCacheActions(
+                               $affectedPageUid,
+                               $affectedPageNewPid,
+                               $updatedFields,
+                               $clearCacheActions
+                       );
+               }
+       }
+
+       /**
+        * waits for TCEmain commands and looks for moved pages, if found further
+        * changes take place to determine whether the cache needs to be updated
+        *
+        * @param       string  table name of the moved record
+        * @param       integer the record's uid
+        * @param       integer the record's destination page id
+        * @param       integer (negative) page id th page has been moved after
+        * @param       array   the record that moved
+        * @param       array   array of changed fields
+        * @param       t3lib_TCEmain   TCEmain parent object
+        */
+       public function moveRecord_afterAnotherElementPostProcess($table, $recordId, $destinationPid, $originalDestinationPid, array $movedRecord, array $updatedFields, t3lib_TCEmain $tceMain) {
+
+               if ($table == 'pages' && $this->requiresUpdate($updatedFields)) {
+
+                       $affectedPageUid    = $recordId;
+                       $affectedPageOldPid = $movedRecord['pid'];
+                       $affectedPageNewPid = $updatedFields['pid'];
+
+                       $clearCacheActions = $this->determineClearCacheActions(
+                               'update',
+                               $updatedFields
+                       );
+
+                               // clear treelist entries for old parent page
+                       $this->processClearCacheActions(
+                               $affectedPageUid,
+                               $affectedPageOldPid,
+                               $updatedFields,
+                               $clearCacheActions
+                       );
+                               // clear treelist entries for new parent page
+                       $this->processClearCacheActions(
+                               $affectedPageUid,
+                               $affectedPageNewPid,
+                               $updatedFields,
+                               $clearCacheActions
+                       );
+               }
+       }
+
+       /**
+        * checks whether the change requires an update of the treelist cache
+        *
+        * @param       array   array of changed fields
+        * @return      boolean true if the treelist cache needs to be updated, false if no update to the cache is required
+        */
+       protected function requiresUpdate(array $updatedFields) {
+               $requiresUpdate = false;
+
+               $updatedFieldNames = array_keys($updatedFields);
+               foreach ($updatedFieldNames as $updatedFieldName) {
+                       if (in_array($updatedFieldName, $this->updateRequiringFields)) {
+                               $requiresUpdate = true;
+                               break;
+                       }
+               }
+
+               return  $requiresUpdate;
+       }
+
+       /**
+        * calls the cache maintainance functions according to the determined actions
+        *
+        * @param       integer uid of the affected page
+        * @param       integer parent uid of the affected page
+        * @param       array   array of updated fields and their new values
+        * @param       array   array of actions to carry out
+        */
+       protected function processClearCacheActions($affectedPage, $affectedParentPage, $updatedFields, array $actions) {
+               $actionNames = array_keys($actions);
+               foreach ($actionNames as $actionName) {
+                       switch ($actionName) {
+                               case 'allParents':
+                                       $this->clearCacheForAllParents($affectedParentPage);
+                                       break;
+                               case 'setExpiration':
+                                               // only used when setting an end time for a page
+                                       $expirationTime = $updatedFields['endtime'];
+                                       $this->setCacheExpiration($affectedPage, $expirationTime);
+                                       break;
+                               case 'uidInTreelist':
+                                       $this->clearCacheWhereUidInTreelist($affectedPage);
+                                       break;
+                       }
+               }
+
+                       // from time to time clean the cache from expired entries
+                       // (theoretically every 1000 calls)
+               $randomNumber = rand(1, 1000);
+               if ($randomNumber == 500) {
+                       $this->removeExpiredCacheEntries();
+               }
+       }
+
+       /**
+        * clears the treelist cache for all parents of a changed page.
+        * gets called after creating a new page and after moving a page
+        *
+        * @param       integer parent page id of the changed page, the page to start clearing from
+        */
+       protected function clearCacheForAllParents($affectedParentPage) {
+
+               $rootline = t3lib_BEfunc::BEgetRootLine($affectedParentPage);
+
+               $rootlineIds = array();
+               foreach ($rootline as $page) {
+                       if($page['uid'] != 0) {
+                               $rootlineIds[] = $page['uid'];
+                       }
+               }
+
+               $rootlineIdsImploded = implode(',', $rootlineIds);
+
+               $GLOBALS['TYPO3_DB']->exec_DELETEquery(
+                       'cache_treelist',
+                       'pid IN(' . $rootlineIdsImploded . ')'
+               );
+
+       }
+
+       /**
+        * clears the treelist cache for all pages where the affected page is found
+        * in the treelist
+        *
+        * @param       integer Id of the changed page
+        */
+       protected function clearCacheWhereUidInTreelist($affectedPage) {
+               $GLOBALS['TYPO3_DB']->exec_DELETEquery(
+                       'cache_treelist',
+                       $GLOBALS['TYPO3_DB']->listQuery(
+                               'treelist',
+                               $affectedPage,
+                               'cache_treelist'
+                       )
+               );
+       }
+
+       /**
+        * sets an expiration time for all cache entries having the changed page in
+        * the treelist.
+        *
+        * @param       integer uid of the changed page
+        */
+       protected function setCacheExpiration($affectedPage, $expirationTime) {
+
+               $GLOBALS['TYPO3_DB']->exec_UPDATEquery(
+                       'cache_treelist',
+                       $GLOBALS['TYPO3_DB']->listQuery(
+                               'treelist',
+                               $affectedPage,
+                               'cache_treelist'
+                       ),
+                       array(
+                               'expires' => $expirationTime
+                       )
+               );
+       }
+
+       /**
+        * removes all expired treelist cache entries
+        *
+        */
+       protected function removeExpiredCacheEntries() {
+               $GLOBALS['TYPO3_DB']->exec_DELETEquery(
+                       'cache_treelist',
+                       'expires <= ' . time()
+               );
+       }
+
+       /**
+        * determines what happened to the page record, this is necessary to clear
+        * as less cache entries as needed later
+        *
+        * @param       string  TCEmain operation status, either 'new' or 'update'
+        * @param       array   array of updated fields
+        * @return      string  list of actions that happened to the page record
+        */
+       protected function determineClearCacheActions($status, $updatedFields) {
+               $actions = array();
+
+               if ($status == 'new') {
+                               // new page
+                       $actions['allParents'] = true;
+               } elseif ($status == 'update') {
+                       $updatedFieldNames = array_keys($updatedFields);
+
+                       foreach ($updatedFieldNames as $updatedFieldName) {
+                               switch ($updatedFieldName) {
+                                       case 'pid':
+                                                       // page moved
+                                               $actions['allParents']    = true;
+                                               $actions['uidInTreelist'] = true;
+                                               break;
+                                       case $GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['disabled']:
+                                       case $GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['fe_group']:
+                                       case $GLOBALS['TCA']['pages']['ctrl']['delete']:
+                                       case 'extendToSubpages':
+                                       case 'php_tree_stop':
+                                                       // page hidden / unhidden / deleted / extendToSubpages set
+                                                       // php_tree_stop and/or FE groups set
+                                               $actions['uidInTreelist'] = true;
+                                               break;
+                                       case $GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['starttime']:
+                                                       /*
+                                                        start time set/unset
+                                                        Doesn't matter whether it was set or unset, in both
+                                                        cases the cache needs to be cleared. When setting a
+                                                        start time the page must be removed from the
+                                                        treelist. When unsetting the start time it must
+                                                        become listed in the tree list again.
+                                                       */
+                                               $actions['uidInTreelist'] = true;
+                                               break;
+                                       case $GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['endtime']:
+                                                       /*
+                                                        end time set/unset
+                                                        When setting an end time the cache entry needs an
+                                                        expiration time. When unsetting the end time the
+                                                        page must become listed in the treelist again.
+                                                       */
+                                               if($updatedFields['endtime'] > 0) {
+                                                       $actions['setExpiration'] = true;
+                                               } else {
+                                                       $actions['uidInTreelist'] = true;
+                                               }
+                                               break;
+                                       default:
+                                               if (in_array($updatedFieldName, $this->updateRequiringFields)) {
+                                                       $actions['uidInTreelist'] = true;
+                                               }
+                               }
+                       }
+               }
+
+               return $actions;
+       }
+
+}
+
+
+if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['tslib/hooks/class.tx_cms_treelistcacheupdate.php'])       {
+       include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['tslib/hooks/class.tx_cms_treelistcacheupdate.php']);
+}
+
+?>
\ No newline at end of file