TYPO3 API  SVNRelease
class.tx_cms_treelistcacheupdate.php
Go to the documentation of this file.
00001 <?php
00002 /***************************************************************
00003 *  Copyright notice
00004 *
00005 *  (c) 2008-2011 Ingo Renner (ingo@typo3.org)
00006 *  All rights reserved
00007 *
00008 *  This script is part of the TYPO3 project. The TYPO3 project is
00009 *  free software; you can redistribute it and/or modify
00010 *  it under the terms of the GNU General Public License as published by
00011 *  the Free Software Foundation; either version 2 of the License, or
00012 *  (at your option) any later version.
00013 *
00014 *  The GNU General Public License can be found at
00015 *  http://www.gnu.org/copyleft/gpl.html.
00016 *  A copy is found in the textfile GPL.txt and important notices to the license
00017 *  from the author is found in LICENSE.txt distributed with these scripts.
00018 *
00019 *
00020 *  This script is distributed in the hope that it will be useful,
00021 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00022 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00023 *  GNU General Public License for more details.
00024 *
00025 *  This copyright notice MUST APPEAR in all copies of the script!
00026 ***************************************************************/
00027 
00028 
00029 /**
00030  * Class that hooks into TCEmain and listens for updates to pages to update the
00031  * treelist cache
00032  *
00033  * @author  Ingo Renner <ingo@typo3.org>
00034  * @package TYPO3
00035  * @subpackage tslib
00036  */
00037 class tx_cms_treelistCacheUpdate {
00038 
00039         // should not be manipulated from others except through the
00040         // configuration provided @see __construct()
00041     private $updateRequiringFields = array(
00042         'pid',
00043         'php_tree_stop',
00044         'extendToSubpages'
00045     );
00046 
00047     /**
00048      * constructor, adds update requiring fields to the default ones
00049      *
00050      */
00051     public function __construct() {
00052 
00053             // as enableFields can be set dynamically we add them here
00054         $pagesEnableFields = $GLOBALS['TCA']['pages']['ctrl']['enablecolumns'];
00055         foreach ($pagesEnableFields as $pagesEnableField) {
00056             $this->updateRequiringFields[] = $pagesEnableField;
00057         }
00058         $this->updateRequiringFields[] = $GLOBALS['TCA']['pages']['ctrl']['delete'];
00059 
00060             // extension can add fields to the pages table that require an
00061             // update of the treelist cache, too; so we also add those
00062             // example: $TYPO3_CONF_VARS['BE']['additionalTreelistUpdateFields'] .= ',my_field';
00063         if (!empty($GLOBALS['TYPO3_CONF_VARS']['BE']['additionalTreelistUpdateFields'])) {
00064             $additionalTreelistUpdateFields = t3lib_div::trimExplode(
00065                 ',',
00066                 $GLOBALS['TYPO3_CONF_VARS']['BE']['additionalTreelistUpdateFields'],
00067                 true
00068             );
00069 
00070             $this->updateRequiringFields += $additionalTreelistUpdateFields;
00071         }
00072 
00073     }
00074 
00075     /**
00076      * waits for TCEmain commands and looks for changed pages, if found further
00077      * changes take place to determine whether the cache needs to be updated
00078      *
00079      * @param   string  TCEmain operation status, either 'new' or 'update'
00080      * @param   string  the DB table the operation was carried out on
00081      * @param   mixed   the record's uid for update records, a string to look the record's uid up after it has been created
00082      * @param   array   array of changed fiels and their new values
00083      * @param   t3lib_TCEmain   TCEmain parent object
00084      */
00085     public function processDatamap_afterDatabaseOperations($status, $table, $recordId, array $updatedFields, t3lib_TCEmain $tceMain) {
00086 
00087         if ($table == 'pages' && $this->requiresUpdate($updatedFields)) {
00088             $affectedPagePid = 0;
00089             $affectedPageUid = 0;
00090 
00091             if ($status == 'new') {
00092                     // detect new pages
00093 
00094                     // resolve the uid
00095                 $affectedPageUid = $tceMain->substNEWwithIDs[$recordId];
00096                 $affectedPagePid = $updatedFields['pid'];
00097             } elseif ($status == 'update') {
00098                     // detect updated pages
00099 
00100                 $affectedPageUid = $recordId;
00101 
00102                 /*
00103                  when updating a page the pid is not directly available so we
00104                  need to retrieve it ourselves.
00105                 */
00106                 $fullPageRecord  = t3lib_BEfunc::getRecord($table, $recordId);
00107                 $affectedPagePid = $fullPageRecord['pid'];
00108             }
00109 
00110             $clearCacheActions = $this->determineClearCacheActions(
00111                 $status,
00112                 $updatedFields
00113             );
00114 
00115             $this->processClearCacheActions(
00116                 $affectedPageUid,
00117                 $affectedPagePid,
00118                 $updatedFields,
00119                 $clearCacheActions
00120             );
00121         }
00122     }
00123 
00124     /**
00125      * waits for TCEmain commands and looks for deleted pages, if found further
00126      * changes take place to determine whether the cache needs to be updated
00127      *
00128      * @param   string  the TCE command
00129      * @param   string  the record's table
00130      * @param   integer the record's uid
00131      * @param   array   the commands value, typically an array with more detailed command information
00132      * @param   t3lib_TCEmain   the TCEmain parent object
00133      */
00134     public function processCmdmap_postProcess($command, $table, $recordId, $commandValue, t3lib_TCEmain $tceMain) {
00135 
00136         if ($table == 'pages' && $command == 'delete') {
00137 
00138             $deletedRecord = t3lib_BEfunc::getRecord(
00139                 $table,
00140                 $recordId,
00141                 '*',
00142                 '',
00143                 false
00144             );
00145 
00146             $affectedPageUid = $deletedRecord['uid'];
00147             $affectedPagePid = $deletedRecord['pid'];
00148                 // faking the updated fields
00149             $updatedFields   = array('deleted' => 1);
00150 
00151             $clearCacheActions = $this->determineClearCacheActions(
00152                 'update',
00153                 $updatedFields
00154             );
00155 
00156             $this->processClearCacheActions(
00157                 $affectedPageUid,
00158                 $affectedPagePid,
00159                 $updatedFields,
00160                 $clearCacheActions
00161             );
00162         }
00163     }
00164 
00165     /**
00166      * waits for TCEmain commands and looks for moved pages, if found further
00167      * changes take place to determine whether the cache needs to be updated
00168      *
00169      * @param   string  table name of the moved record
00170      * @param   integer the record's uid
00171      * @param   integer the record's destination page id
00172      * @param   array   the record that moved
00173      * @param   array   array of changed fields
00174      * @param   t3lib_TCEmain   TCEmain parent object
00175      */
00176     public function moveRecord_firstElementPostProcess($table, $recordId, $destinationPid, array $movedRecord, array $updatedFields, t3lib_TCEmain $tceMain) {
00177 
00178         if ($table == 'pages' && $this->requiresUpdate($updatedFields)) {
00179 
00180             $affectedPageUid    = $recordId;
00181             $affectedPageOldPid = $movedRecord['pid'];
00182             $affectedPageNewPid = $updatedFields['pid'];
00183 
00184             $clearCacheActions = $this->determineClearCacheActions(
00185                 'update',
00186                 $updatedFields
00187             );
00188 
00189                 // clear treelist entries for old parent page
00190             $this->processClearCacheActions(
00191                 $affectedPageUid,
00192                 $affectedPageOldPid,
00193                 $updatedFields,
00194                 $clearCacheActions
00195             );
00196                 // clear treelist entries for new parent page
00197             $this->processClearCacheActions(
00198                 $affectedPageUid,
00199                 $affectedPageNewPid,
00200                 $updatedFields,
00201                 $clearCacheActions
00202             );
00203         }
00204     }
00205 
00206     /**
00207      * waits for TCEmain commands and looks for moved pages, if found further
00208      * changes take place to determine whether the cache needs to be updated
00209      *
00210      * @param   string  table name of the moved record
00211      * @param   integer the record's uid
00212      * @param   integer the record's destination page id
00213      * @param   integer (negative) page id th page has been moved after
00214      * @param   array   the record that moved
00215      * @param   array   array of changed fields
00216      * @param   t3lib_TCEmain   TCEmain parent object
00217      */
00218     public function moveRecord_afterAnotherElementPostProcess($table, $recordId, $destinationPid, $originalDestinationPid, array $movedRecord, array $updatedFields, t3lib_TCEmain $tceMain) {
00219 
00220         if ($table == 'pages' && $this->requiresUpdate($updatedFields)) {
00221 
00222             $affectedPageUid    = $recordId;
00223             $affectedPageOldPid = $movedRecord['pid'];
00224             $affectedPageNewPid = $updatedFields['pid'];
00225 
00226             $clearCacheActions = $this->determineClearCacheActions(
00227                 'update',
00228                 $updatedFields
00229             );
00230 
00231                 // clear treelist entries for old parent page
00232             $this->processClearCacheActions(
00233                 $affectedPageUid,
00234                 $affectedPageOldPid,
00235                 $updatedFields,
00236                 $clearCacheActions
00237             );
00238                 // clear treelist entries for new parent page
00239             $this->processClearCacheActions(
00240                 $affectedPageUid,
00241                 $affectedPageNewPid,
00242                 $updatedFields,
00243                 $clearCacheActions
00244             );
00245         }
00246     }
00247 
00248     /**
00249      * checks whether the change requires an update of the treelist cache
00250      *
00251      * @param   array   array of changed fields
00252      * @return  boolean true if the treelist cache needs to be updated, false if no update to the cache is required
00253      */
00254     protected function requiresUpdate(array $updatedFields) {
00255         $requiresUpdate = false;
00256 
00257         $updatedFieldNames = array_keys($updatedFields);
00258         foreach ($updatedFieldNames as $updatedFieldName) {
00259             if (in_array($updatedFieldName, $this->updateRequiringFields)) {
00260                 $requiresUpdate = true;
00261                 break;
00262             }
00263         }
00264 
00265         return  $requiresUpdate;
00266     }
00267 
00268     /**
00269      * calls the cache maintainance functions according to the determined actions
00270      *
00271      * @param   integer uid of the affected page
00272      * @param   integer parent uid of the affected page
00273      * @param   array   array of updated fields and their new values
00274      * @param   array   array of actions to carry out
00275      */
00276     protected function processClearCacheActions($affectedPage, $affectedParentPage, $updatedFields, array $actions) {
00277         $actionNames = array_keys($actions);
00278         foreach ($actionNames as $actionName) {
00279             switch ($actionName) {
00280                 case 'allParents':
00281                     $this->clearCacheForAllParents($affectedParentPage);
00282                     break;
00283                 case 'setExpiration':
00284                         // only used when setting an end time for a page
00285                     $expirationTime = $updatedFields['endtime'];
00286                     $this->setCacheExpiration($affectedPage, $expirationTime);
00287                     break;
00288                 case 'uidInTreelist':
00289                     $this->clearCacheWhereUidInTreelist($affectedPage);
00290                     break;
00291             }
00292         }
00293 
00294             // from time to time clean the cache from expired entries
00295             // (theoretically every 1000 calls)
00296         $randomNumber = rand(1, 1000);
00297         if ($randomNumber == 500) {
00298             $this->removeExpiredCacheEntries();
00299         }
00300     }
00301 
00302     /**
00303      * clears the treelist cache for all parents of a changed page.
00304      * gets called after creating a new page and after moving a page
00305      *
00306      * @param   integer parent page id of the changed page, the page to start clearing from
00307      */
00308     protected function clearCacheForAllParents($affectedParentPage) {
00309         $rootline = t3lib_BEfunc::BEgetRootLine($affectedParentPage);
00310 
00311         $rootlineIds = array();
00312         foreach ($rootline as $page) {
00313             if($page['uid'] != 0) {
00314                 $rootlineIds[] = $page['uid'];
00315             }
00316         }
00317 
00318         if (!empty($rootlineIds)) {
00319             $rootlineIdsImploded = implode(',', $rootlineIds);
00320 
00321             $GLOBALS['TYPO3_DB']->exec_DELETEquery(
00322                 'cache_treelist',
00323                 'pid IN(' . $rootlineIdsImploded . ')'
00324             );
00325         }
00326     }
00327 
00328     /**
00329      * clears the treelist cache for all pages where the affected page is found
00330      * in the treelist
00331      *
00332      * @param   integer Id of the changed page
00333      */
00334     protected function clearCacheWhereUidInTreelist($affectedPage) {
00335         $GLOBALS['TYPO3_DB']->exec_DELETEquery(
00336             'cache_treelist',
00337             $GLOBALS['TYPO3_DB']->listQuery(
00338                 'treelist',
00339                 $affectedPage,
00340                 'cache_treelist'
00341             )
00342         );
00343     }
00344 
00345     /**
00346      * sets an expiration time for all cache entries having the changed page in
00347      * the treelist.
00348      *
00349      * @param   integer uid of the changed page
00350      */
00351     protected function setCacheExpiration($affectedPage, $expirationTime) {
00352 
00353         $GLOBALS['TYPO3_DB']->exec_UPDATEquery(
00354             'cache_treelist',
00355             $GLOBALS['TYPO3_DB']->listQuery(
00356                 'treelist',
00357                 $affectedPage,
00358                 'cache_treelist'
00359             ),
00360             array(
00361                 'expires' => $expirationTime
00362             )
00363         );
00364     }
00365 
00366     /**
00367      * removes all expired treelist cache entries
00368      *
00369      */
00370     protected function removeExpiredCacheEntries() {
00371         $GLOBALS['TYPO3_DB']->exec_DELETEquery(
00372             'cache_treelist',
00373             'expires <= ' . $GLOBALS['EXEC_TIME']
00374         );
00375     }
00376 
00377     /**
00378      * determines what happened to the page record, this is necessary to clear
00379      * as less cache entries as needed later
00380      *
00381      * @param   string  TCEmain operation status, either 'new' or 'update'
00382      * @param   array   array of updated fields
00383      * @return  string  list of actions that happened to the page record
00384      */
00385     protected function determineClearCacheActions($status, $updatedFields) {
00386         $actions = array();
00387 
00388         if ($status == 'new') {
00389                 // new page
00390             $actions['allParents'] = true;
00391         } elseif ($status == 'update') {
00392             $updatedFieldNames = array_keys($updatedFields);
00393 
00394             foreach ($updatedFieldNames as $updatedFieldName) {
00395                 switch ($updatedFieldName) {
00396                     case 'pid':
00397                             // page moved
00398                     case $GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['disabled']:
00399                             // page hidden / unhidden
00400                     case $GLOBALS['TCA']['pages']['ctrl']['delete']:
00401                             // page deleted / undeleted
00402                     case $GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['starttime']:
00403                             /*
00404                              start time set/unset
00405                              Doesn't matter whether it was set or unset, in both
00406                              cases the cache needs to be cleared. When setting a
00407                              start time the page must be removed from the
00408                              treelist. When unsetting the start time it must
00409                              become listed in the tree list again.
00410                             */
00411                     case $GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['fe_group']:
00412                             // changes to FE user group
00413                     case 'extendToSubpages':
00414                             // extendToSubpages set (apply FE access restrictions to subpages)
00415                     case 'php_tree_stop':
00416                             // php_tree_stop
00417                         $actions['allParents'] = TRUE;
00418                         $actions['uidInTreelist'] = true;
00419                         break;
00420                     case $GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['endtime']:
00421                             /*
00422                              end time set/unset
00423                              When setting an end time the cache entry needs an
00424                              expiration time. When unsetting the end time the
00425                              page must become listed in the treelist again.
00426                             */
00427                         if($updatedFields['endtime'] > 0) {
00428                             $actions['setExpiration'] = true;
00429                         } else {
00430                             $actions['uidInTreelist'] = true;
00431                         }
00432                         break;
00433                     default:
00434                         if (in_array($updatedFieldName, $this->updateRequiringFields)) {
00435                             $actions['uidInTreelist'] = true;
00436                         }
00437                 }
00438             }
00439         }
00440 
00441         return $actions;
00442     }
00443 
00444 }
00445 
00446 
00447 if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['tslib/hooks/class.tx_cms_treelistcacheupdate.php'])) {
00448     include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['tslib/hooks/class.tx_cms_treelistcacheupdate.php']);
00449 }
00450 
00451 ?>