<?php
|
|
|
|
// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project
|
|
//
|
|
// All Rights Reserved. See copyright.txt for details and a complete list of authors.
|
|
// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
|
|
// $Id$
|
|
|
|
/**
|
|
* \brief A basic library to handle a cache of some Tiki Objects,
|
|
* usage is simple and feel free to improve it
|
|
*/
|
|
|
|
class Cachelib
|
|
{
|
|
private $implementation;
|
|
|
|
public function __construct()
|
|
{
|
|
global $prefs;
|
|
|
|
if (isset($prefs['memcache_enabled']) && $prefs['memcache_enabled'] == 'y') {
|
|
$this->implementation = new CacheLibMemcache();
|
|
} elseif (isset($prefs['redis_enabled']) && $prefs['redis_enabled'] == 'y') {
|
|
require_once('lib/cache/redislib.php');
|
|
$this->implementation = new CacheLibRedis();
|
|
} else {
|
|
$this->implementation = new CacheLibFileSystem();
|
|
}
|
|
}
|
|
|
|
public function replaceImplementation($implementation)
|
|
{
|
|
$old = $this->implementation;
|
|
$this->implementation = $implementation;
|
|
|
|
return $old;
|
|
}
|
|
|
|
public function cacheItem($key, $data, $type = '')
|
|
{
|
|
return $this->implementation->cacheItem($key, $data, $type);
|
|
}
|
|
|
|
public function isCached($key, $type = '')
|
|
{
|
|
return $this->implementation->isCached($key, $type);
|
|
}
|
|
|
|
public function getCached($key, $type = '', $lastModif = false)
|
|
{
|
|
return $this->implementation->getCached($key, $type, $lastModif);
|
|
}
|
|
|
|
public function getSerialized($key, $type = '', $lastModif = false)
|
|
{
|
|
$data = $this->getCached($key, $type, $lastModif);
|
|
|
|
if ($data) {
|
|
return unserialize($data);
|
|
}
|
|
}
|
|
|
|
public function invalidate($key, $type = '')
|
|
{
|
|
return $this->implementation->invalidate($key, $type);
|
|
}
|
|
|
|
/**
|
|
* Empty one or more caches
|
|
*
|
|
* Checks for existance of libs because it's called from the installer
|
|
*
|
|
* @param mixed $dir_names all|templates_c|temp_cache|temp_public|modules_cache|prefs (default all)
|
|
* @param string $log_section Type of log message. Default 'system'
|
|
*/
|
|
public function empty_cache($dir_names = ['all'], $log_section = 'system')
|
|
{
|
|
global $tikidomain, $prefs;
|
|
$logslib = TikiLib::lib('logs');
|
|
|
|
$inInstaller = defined('TIKI_IN_INSTALLER');
|
|
|
|
if (! is_array($dir_names)) {
|
|
$dir_names = [$dir_names];
|
|
}
|
|
if (in_array('all', $dir_names)) {
|
|
$this->erase_dir_content("temp/templates_c/$tikidomain");
|
|
$this->erase_dir_content("temp/public/$tikidomain");
|
|
$this->erase_dir_content("temp/cache/$tikidomain");
|
|
|
|
$banner = glob("temp/banner*.*");
|
|
array_map('unlink', $banner);
|
|
|
|
$banner = glob("temp/TMPIMG*");
|
|
array_map('unlink', $banner);
|
|
|
|
$this->flush_opcode_cache();
|
|
$this->flush_memcache();
|
|
$this->flush_redis();
|
|
$this->invalidate('global_preferences');
|
|
if (! $inInstaller) {
|
|
$logslib->add_log($log_section, 'erased all cache content');
|
|
}
|
|
}
|
|
if (in_array('templates_c', $dir_names)) {
|
|
$this->erase_dir_content("temp/templates_c/$tikidomain");
|
|
$this->flush_opcode_cache();
|
|
if (! $inInstaller) {
|
|
$logslib->add_log($log_section, 'erased templates_c content');
|
|
}
|
|
}
|
|
if (in_array('temp_cache', $dir_names)) {
|
|
$this->erase_dir_content("temp/cache/$tikidomain");
|
|
// Next case is needed to clean also cached data created through mod PluginR
|
|
if ((isset($prefs['wikiplugin_rr']) && $prefs['wikiplugin_rr'] == 'y') or (isset($prefs['wikiplugin_r']) && $prefs['wikiplugin_r'] == 'y')) {
|
|
$this->erase_dir_content("temp/cache/$tikidomain/R_*/");
|
|
}
|
|
$this->flush_redis();
|
|
if (! $inInstaller) {
|
|
$logslib->add_log($log_section, 'erased temp/cache content');
|
|
}
|
|
}
|
|
if (in_array('temp_public', $dir_names)) {
|
|
$this->erase_dir_content("temp/public/$tikidomain");
|
|
if (! $inInstaller) {
|
|
$logslib->add_log($log_section, 'erased temp/public content');
|
|
}
|
|
}
|
|
if (in_array('prefs', $dir_names)) {
|
|
$this->invalidate('global_preferences');
|
|
}
|
|
}
|
|
|
|
public function empty_type_cache($type)
|
|
{
|
|
return $this->implementation->empty_type_cache($type);
|
|
}
|
|
|
|
public function count_cache_files($path, $begin = null)
|
|
{
|
|
global $tikidomain;
|
|
|
|
if (! $path or ! is_dir($path)) {
|
|
return (['total' => 0,'cant' => 0]);
|
|
}
|
|
|
|
$total = 0;
|
|
$cant = 0;
|
|
$back = [];
|
|
$all = opendir($path);
|
|
|
|
// If using multiple Tikis but flushing cache on default install...
|
|
if (empty($tikidomain) && is_file('db/virtuals.inc')) {
|
|
$virtuals = array_map('trim', file('db/virtuals.inc'));
|
|
} else {
|
|
$virtuals = false;
|
|
}
|
|
|
|
while ($file = readdir($all)) {
|
|
if (
|
|
substr($file, 0, 1) == "." or
|
|
$file == 'CVS' or
|
|
$file == '.svn' or
|
|
$file == "index.php" or
|
|
$file == "README" or
|
|
$file == "web.config" or
|
|
($virtuals && in_array($file, $virtuals))
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
if (is_dir($path . '/' . $file) and $file <> ".." and $file <> "." and $file <> "CVS" and $file <> ".svn") {
|
|
$du = $this->count_cache_files($path . '/' . $file);
|
|
$total += $du['total'];
|
|
$cant += $du['cant'];
|
|
unset($file);
|
|
} elseif (! is_dir($path . '/' . $file)) {
|
|
if (isset($begin) && substr($file, 0, strlen($begin)) != $begin) {
|
|
continue; // the file name doesn't begin with the good beginning
|
|
}
|
|
$stats = @stat($path . '/' . $file); // avoid the warning if safe mode on
|
|
$total += $stats['size'];
|
|
$cant++;
|
|
unset($file);
|
|
}
|
|
}
|
|
closedir($all);
|
|
unset($all);
|
|
$back['total'] = $total;
|
|
$back['cant'] = $cant;
|
|
return $back;
|
|
}
|
|
|
|
public function flush_opcode_cache()
|
|
{
|
|
if (function_exists('apc_clear_cache')) {
|
|
apc_clear_cache();
|
|
}
|
|
|
|
if (function_exists('xcache_clear_cache') && ! ini_get('xcache.admin.enable_auth')) {
|
|
foreach (range(0, xcache_count(XC_TYPE_PHP) - 1) as $index) {
|
|
xcache_clear_cache(XC_TYPE_PHP, $index);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Flush memcache if endabled
|
|
*
|
|
* @return void
|
|
*/
|
|
public function flush_memcache()
|
|
{
|
|
global $prefs;
|
|
|
|
if (isset($prefs['memcache_enabled']) && $prefs['memcache_enabled'] == 'y') {
|
|
$memcachelib = TikiLib::lib("memcache");
|
|
if ($memcachelib->isEnabled()) {
|
|
$memcachelib->flush();
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
public function flush_redis()
|
|
{
|
|
global $prefs;
|
|
|
|
if (isset($prefs['redis_enabled']) && $prefs['redis_enabled'] == 'y') {
|
|
$this->implementation->flush();
|
|
}
|
|
return;
|
|
}
|
|
|
|
public function erase_dir_content($path)
|
|
{
|
|
global $tikidomain, $prefs;
|
|
|
|
$path = rtrim($path, '/');
|
|
if (! $path or ! is_dir($path)) {
|
|
return 0;
|
|
}
|
|
if ($dir = opendir($path)) {
|
|
// If using multiple Tikis but flushing cache on default install...
|
|
if (empty($tikidomain) && is_file('db/virtuals.inc')) {
|
|
$virtuals = array_map('trim', file('db/virtuals.inc'));
|
|
} else {
|
|
$virtuals = false;
|
|
}
|
|
|
|
// Next case is needed to clean also cached data created through mod PluginR
|
|
if (
|
|
(isset($prefs['wikiplugin_rr']) && $prefs['wikiplugin_rr'] == 'y') ||
|
|
(isset($prefs['wikiplugin_r']) && $prefs['wikiplugin_r'] == 'y')
|
|
) {
|
|
$extracheck = 'RData';
|
|
} else {
|
|
$extracheck = '';
|
|
}
|
|
|
|
// Folders created by unoconv/libreoffice that should be removed
|
|
$unoconvFolders = ['.cache', '.config'];
|
|
|
|
while (false !== ($file = readdir($dir))) {
|
|
if (
|
|
// .RData case needed to clean also cached data created through mod PluginR
|
|
( substr($file, 0, 1) == "." && substr($file, -5) != $extracheck ) or
|
|
$file == 'CVS' or
|
|
$file == '.svn' or
|
|
$file == "index.php" or
|
|
$file == "README" or
|
|
$file == "web.config" or
|
|
($virtuals && in_array($file, $virtuals)) and
|
|
! in_array($file, $unoconvFolders)
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
if (is_dir($path . "/" . $file)) {
|
|
$this->erase_dir_content($path . "/" . $file);
|
|
@rmdir($path . "/" . $file); // dir won't be empty if there are multitiki dirs inside
|
|
} else {
|
|
try {
|
|
unlink($path . "/" . $file);
|
|
} catch(Error $e) {
|
|
Feedback::error(tr('Cache file %0 is not writable (%1)', $path . "/" . $file, $e->getMessage()));
|
|
}
|
|
}
|
|
}
|
|
closedir($dir);
|
|
}
|
|
}
|
|
|
|
public function cache_templates($path, $newlang)
|
|
{
|
|
global $prefs;
|
|
$smarty = TikiLib::lib('smarty');
|
|
$smarty->refreshLanguage();
|
|
|
|
$oldlang = $prefs['language'];
|
|
$prefs['language'] = $newlang;
|
|
if (! $path or ! is_dir($path)) {
|
|
return 0;
|
|
}
|
|
if ($dir = opendir($path)) {
|
|
while (false !== ($file = readdir($dir))) {
|
|
$a = explode(".", $file);
|
|
$ext = strtolower(end($a));
|
|
if (substr($file, 0, 1) == "." or $file == 'CVS') {
|
|
continue;
|
|
}
|
|
if (is_dir($path . "/" . $file)) {
|
|
$prefs['language'] = $oldlang;
|
|
$this->cache_templates($path . "/" . $file, $newlang);
|
|
$prefs['language'] = $newlang;
|
|
} else {
|
|
if ($ext == "tpl") {
|
|
$template_file = substr($path . "/" . $file, 10);
|
|
try {
|
|
$_tpl = $smarty->createTemplate($template_file, null, null, null, false);
|
|
$_tpl->compileTemplateSource();
|
|
} catch (Exception $e) {
|
|
$errors_found = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
closedir($dir);
|
|
}
|
|
$prefs['language'] = $oldlang;
|
|
}
|
|
|
|
/**
|
|
* Generates caches for templates, modules and other features.
|
|
*
|
|
* @param mixed $dir_names all|templates_c|modules_cache|misc (default all)
|
|
*
|
|
* @throws Exception
|
|
*/
|
|
public function generateCache($dir_names = ['all'])
|
|
{
|
|
if (! is_array($dir_names)) {
|
|
$dir_names = [$dir_names];
|
|
}
|
|
|
|
if (in_array('all', $dir_names)) {
|
|
$this->generateTemplateCache();
|
|
$this->generateModuleCache();
|
|
$this->generateMiscCache();
|
|
}
|
|
if (in_array('templates', $dir_names)) {
|
|
$this->generateTemplateCache();
|
|
}
|
|
if (in_array('modules', $dir_names)) {
|
|
$this->generateModuleCache();
|
|
}
|
|
if (in_array('misc', $dir_names)) {
|
|
$this->generateMiscCache();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Compile all Smarty templates
|
|
* @param string $logSection Section to log the request
|
|
*/
|
|
protected function generateTemplateCache($logSection = 'system')
|
|
{
|
|
global $prefs;
|
|
|
|
$logslib = TikiLib::lib('logs');
|
|
|
|
$inInstaller = defined('TIKI_IN_INSTALLER');
|
|
|
|
$lang = $prefs['language'];
|
|
$ctempl = 'templates';
|
|
$this->cache_templates($ctempl, $lang);
|
|
|
|
if (! $inInstaller) {
|
|
$logslib->add_log($logSection, 'generated templates cache, language = ' . $lang);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Compile all module cache
|
|
* @param string $logSection Section to log the request
|
|
*/
|
|
protected function generateModuleCache($logSection = 'system')
|
|
{
|
|
$logslib = TikiLib::lib('logs');
|
|
$modlib = TikiLib::lib('mod');
|
|
|
|
$inInstaller = defined('TIKI_IN_INSTALLER');
|
|
|
|
$assigned_modules = $modlib->get_assigned_modules();
|
|
foreach ($assigned_modules as $zone => $modules) {
|
|
foreach ($modules as $pos => $module) {
|
|
/** Pre-execute module to cache its content */
|
|
$result = $modlib->execute_module($module);
|
|
if (! $inInstaller) {
|
|
$logslib->add_log($logSection, 'generated module-cache for ' . $module['name']);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Compile Misc caches like language, categories, users.
|
|
*
|
|
* @param string $logSection
|
|
*
|
|
* @throws Exception
|
|
*/
|
|
protected function generateMiscCache($logSection = 'system')
|
|
{
|
|
$logslib = TikiLib::lib('logs');
|
|
|
|
$inInstaller = defined('TIKI_IN_INSTALLER');
|
|
|
|
TikiLib::lib('language')->list_languages();
|
|
if (! $inInstaller) {
|
|
$logslib->add_log($logSection, 'cached language list');
|
|
}
|
|
|
|
TikiLib::lib('categ')->getCategories();
|
|
if (! $inInstaller) {
|
|
$logslib->add_log($logSection, 'cached category list');
|
|
}
|
|
|
|
TikiLib::lib('user')->list_all_users();
|
|
TikiLib::lib('user')->list_all_groups();
|
|
TikiLib::lib('user')->list_all_groupIds();
|
|
if (! $inInstaller) {
|
|
$logslib->add_log($logSection, 'cached user/group list');
|
|
}
|
|
}
|
|
|
|
public function get_cache_purge_rules($type = 'all')
|
|
{
|
|
if ($this->isCached($type, 'cachepurgerules')) {
|
|
return $this->getSerialized($type, 'cachepurgerules');
|
|
}
|
|
if ($type != 'all') {
|
|
$rules = TikiLib::lib('tiki')->fetchAll("select * from tiki_object_relations where relation = 'tiki.cache.purge' and source_type = ?", array($type));
|
|
} else {
|
|
$rules = TikiLib::lib('tiki')->fetchAll("select * from tiki_object_relations where relation = 'tiki.cache.purge'");
|
|
}
|
|
$this->cacheItem($type, serialize($rules), 'cachepurgerules');
|
|
return $rules;
|
|
}
|
|
|
|
public function get_purge_rules_for_cache($cacheType, $cacheKey)
|
|
{
|
|
return TikiLib::lib('tiki')->fetchAll("select source_type as type, source_itemId as object from tiki_object_relations where relation = 'tiki.cache.purge' and target_type = ? and target_itemId = ?", array($cacheType, $cacheKey));
|
|
}
|
|
|
|
public function clear_purge_rules_for_cache($cacheType, $cacheKey)
|
|
{
|
|
return TikiLib::lib('tiki')->query("delete from tiki_object_relations where relation = 'tiki.cache.purge' and target_type = ? and target_itemId = ?", array($cacheType, $cacheKey));
|
|
}
|
|
|
|
public function set_cache_purge_rule($type, $object, $cacheType, $cacheKey)
|
|
{
|
|
$relationId = TikiLib::lib('relation')->add_relation('tiki.cache.purge', $type, $object, $cacheType, $cacheKey, true);
|
|
if ($relationId) {
|
|
// Rule is added (if it already existed, nothing would have happened)
|
|
$this->invalidate($type, 'cachepurgerules');
|
|
}
|
|
return $relationId;
|
|
}
|
|
|
|
public function invalidate_by_cache_purge_rules($args)
|
|
{
|
|
// First get all candidates which match type (source_itemId does not matter for now - see below)
|
|
$cache_purge_rules = $this->get_cache_purge_rules($args['type']);
|
|
|
|
foreach ($cache_purge_rules as $c) {
|
|
if ($c['source_itemId'] == $args['object'] || $c['source_itemId'] == 0) {
|
|
TikiLib::lib('cache')->invalidate($c['target_itemId'], $c['target_type']);
|
|
} elseif ($args['type'] != 'wiki page' && $colonpos = strpos($c['source_itemId'], ':')) {
|
|
// Examples: trackeritem:20, trackerId:3, galleryId:5, forum_id:7, parent_id:8 etc...
|
|
$prefix = substr($c['source_itemId'], 0, $colonpos);
|
|
$itemId = substr($c['source_itemId'], $colonpos + 1);
|
|
if (isset($args[$prefix]) && $itemId == $args[$prefix]) {
|
|
TikiLib::lib('cache')->invalidate($c['target_itemId'], $c['target_type']);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class CacheLibFileSystem
|
|
{
|
|
public $folder;
|
|
|
|
public function __construct()
|
|
{
|
|
global $tikidomain;
|
|
$this->folder = realpath("temp/cache");
|
|
if ($tikidomain) {
|
|
$this->folder .= "/$tikidomain";
|
|
}
|
|
if (! is_dir($this->folder)) {
|
|
mkdir($this->folder);
|
|
chmod($this->folder, 0777);
|
|
}
|
|
}
|
|
|
|
public function cacheItem($key, $data, $type = '')
|
|
{
|
|
$key = $type . md5($key);
|
|
@file_put_contents($this->folder . "/$key", $data);
|
|
return true;
|
|
}
|
|
|
|
public function isCached($key, $type = '')
|
|
{
|
|
$key = $type . md5($key);
|
|
return is_file($this->folder . "/$key");
|
|
}
|
|
|
|
public function getCached($key, $type = '', $lastModif = false)
|
|
{
|
|
$key = $type . md5($key);
|
|
$file = $this->folder . "/$key";
|
|
if (is_readable($file)) {
|
|
// If a last date is given for cache validity, make sure the file is younger
|
|
if ($lastModif !== false && filemtime($file) < $lastModif) {
|
|
unlink($file);
|
|
return false;
|
|
}
|
|
|
|
return @file_get_contents($file);
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public function invalidate($key, $type = '')
|
|
{
|
|
$key = $type . md5($key);
|
|
if (is_file($this->folder . "/$key")) {
|
|
unlink($this->folder . "/$key");
|
|
}
|
|
}
|
|
|
|
public function empty_type_cache($type)
|
|
{
|
|
$path = $this->folder;
|
|
$all = opendir($path);
|
|
while ($file = readdir($all)) {
|
|
if (strpos($file, $type) === 0) {
|
|
unlink("$path/$file");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class CacheLibMemcache
|
|
{
|
|
private function getKey($key, $type)
|
|
{
|
|
return $type . md5($key);
|
|
}
|
|
|
|
public function cacheItem($key, $data, $type = '')
|
|
{
|
|
TikiLib::lib("memcache")->set($this->getKey($key, $type), $data);
|
|
return true;
|
|
}
|
|
|
|
public function isCached($key, $type = '')
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public function getCached($key, $type = '', $lastModif = false)
|
|
{
|
|
return TikiLib::lib("memcache")->get($this->getKey($key, $type));
|
|
}
|
|
|
|
public function invalidate($key, $type = '')
|
|
{
|
|
return TikiLib::lib("memcache")->delete($this->getKey($key, $type));
|
|
}
|
|
|
|
public function empty_type_cache($type)
|
|
{
|
|
return TikiLib::lib("memcache")->flush();
|
|
}
|
|
}
|
|
|
|
class CacheLibNoCache
|
|
{
|
|
public function cacheItem($key, $data, $type = '')
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public function isCached($key, $type = '')
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public function getCached($key, $type = '', $lastModif = false)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public function invalidate($key, $type = '')
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public function empty_type_cache($type)
|
|
{
|
|
return false;
|
|
}
|
|
}
|