PageRenderTime 94ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/modx/core/xpdo/cache/xpdocachemanager.class.php

https://bitbucket.org/argnist/mohana
PHP | 1012 lines | 808 code | 22 blank | 182 comment | 129 complexity | fdb76c1683a996116e27ac206029d9f1 MD5 | raw file
Possible License(s): BSD-3-Clause, AGPL-1.0, LGPL-2.1, GPL-2.0, GPL-3.0, LGPL-2.0
  1. <?php
  2. /*
  3. * Copyright 2010-2011 by MODX, LLC.
  4. *
  5. * This file is part of xPDO.
  6. *
  7. * xPDO is free software; you can redistribute it and/or modify it under the
  8. * terms of the GNU General Public License as published by the Free Software
  9. * Foundation; either version 2 of the License, or (at your option) any later
  10. * version.
  11. *
  12. * xPDO is distributed in the hope that it will be useful, but WITHOUT ANY
  13. * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  14. * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License along with
  17. * xPDO; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
  18. * Suite 330, Boston, MA 02111-1307 USA
  19. */
  20. /**
  21. * Classes implementing a default cache implementation for xPDO.
  22. *
  23. * @package xpdo
  24. * @subpackage cache
  25. */
  26. /**
  27. * The default cache manager implementation for xPDO.
  28. *
  29. * @package xpdo
  30. * @subpackage cache
  31. */
  32. class xPDOCacheManager {
  33. const CACHE_PHP = 0;
  34. const CACHE_JSON = 1;
  35. const CACHE_SERIALIZE = 2;
  36. const CACHE_DIR = 'objects/';
  37. const LOG_DIR = 'logs/';
  38. protected $xpdo= null;
  39. protected $caches= array();
  40. protected $options= array();
  41. protected $_umask= null;
  42. public function __construct(& $xpdo, $options = array()) {
  43. $this->xpdo= & $xpdo;
  44. $this->options= $options;
  45. $this->_umask= umask();
  46. }
  47. /**
  48. * Get an instance of a provider which implements the xPDOCache interface.
  49. */
  50. public function & getCacheProvider($key = '', $options = array()) {
  51. $objCache = null;
  52. if (empty($key)) {
  53. $key = $this->getOption(xPDO::OPT_CACHE_KEY, $options, 'default');
  54. }
  55. $objCacheClass= 'xPDOFileCache';
  56. if (!isset($this->caches[$key]) || !is_object($this->caches[$key])) {
  57. if ($cacheClass = $this->getOption($key . '_' . xPDO::OPT_CACHE_HANDLER, $options, $this->getOption(xPDO::OPT_CACHE_HANDLER, $options))) {
  58. $cacheClass = $this->xpdo->loadClass($cacheClass, XPDO_CORE_PATH, false, true);
  59. if ($cacheClass) {
  60. $objCacheClass= $cacheClass;
  61. }
  62. }
  63. $options[xPDO::OPT_CACHE_KEY]= $key;
  64. $this->caches[$key] = new $objCacheClass($this->xpdo, $options);
  65. if (empty($this->caches[$key]) || !$this->caches[$key]->isInitialized()) {
  66. $this->caches[$key] = new xPDOFileCache($this->xpdo, $options);
  67. }
  68. $objCache = $this->caches[$key];
  69. $objCacheClass= get_class($objCache);
  70. } else {
  71. $objCache =& $this->caches[$key];
  72. $objCacheClass= get_class($objCache);
  73. }
  74. if ($this->xpdo->getDebug() === true) $this->xpdo->log(xPDO::LOG_LEVEL_DEBUG, "Returning {$objCacheClass}:{$key} cache provider from available providers: " . print_r(array_keys($this->caches), 1));
  75. return $objCache;
  76. }
  77. /**
  78. * Get an option from supplied options, the cacheManager options, or xpdo itself.
  79. *
  80. * @param string $key Unique identifier for the option.
  81. * @param array $options A set of explicit options to override those from xPDO or the
  82. * xPDOCacheManager implementation.
  83. * @param mixed $default An optional default value to return if no value is found.
  84. * @return mixed The value of the option.
  85. */
  86. public function getOption($key, $options = array(), $default = null) {
  87. $option = $default;
  88. if (is_array($key)) {
  89. if (!is_array($option)) {
  90. $default= $option;
  91. $option= array();
  92. }
  93. foreach ($key as $k) {
  94. $option[$k]= $this->getOption($k, $options, $default);
  95. }
  96. } elseif (is_string($key) && !empty($key)) {
  97. if (is_array($options) && !empty($options) && array_key_exists($key, $options)) {
  98. $option = $options[$key];
  99. } elseif (is_array($this->options) && !empty($this->options) && array_key_exists($key, $this->options)) {
  100. $option = $this->options[$key];
  101. } else {
  102. $option = $this->xpdo->getOption($key, null, $default);
  103. }
  104. }
  105. return $option;
  106. }
  107. /**
  108. * Get default folder permissions based on umask
  109. *
  110. * @return integer The default folder permissions.
  111. */
  112. public function getFolderPermissions() {
  113. $perms = 0777;
  114. $perms = $perms & (0777 - $this->_umask);
  115. return $perms;
  116. }
  117. /**
  118. * Get default file permissions based on umask
  119. *
  120. * @return integer The default file permissions.
  121. */
  122. public function getFilePermissions() {
  123. $perms = 0666;
  124. $perms = $perms & (0666 - $this->_umask);
  125. return $perms;
  126. }
  127. /**
  128. * Get the absolute path to a writable directory for storing files.
  129. *
  130. * @access public
  131. * @return string The absolute path of the xPDO cache directory.
  132. */
  133. public function getCachePath() {
  134. $cachePath= false;
  135. if (empty($this->xpdo->cachePath)) {
  136. if (!isset ($this->xpdo->config['cache_path'])) {
  137. while (true) {
  138. if (!empty ($_ENV['TMP'])) {
  139. if ($cachePath= strtr($_ENV['TMP'], '\\', '/'))
  140. break;
  141. }
  142. if (!empty ($_ENV['TMPDIR'])) {
  143. if ($cachePath= strtr($_ENV['TMPDIR'], '\\', '/'))
  144. break;
  145. }
  146. if (!empty ($_ENV['TEMP'])) {
  147. if ($cachePath= strtr($_ENV['TEMP'], '\\', '/'))
  148. break;
  149. }
  150. if ($temp_file= @ tempnam(md5(uniqid(rand(), TRUE)), '')) {
  151. $cachePath= strtr(dirname($temp_file), '\\', '/');
  152. @ unlink($temp_file);
  153. }
  154. break;
  155. }
  156. if ($cachePath) {
  157. if ($cachePath{strlen($cachePath) - 1} != '/') $cachePath .= '/';
  158. $cachePath .= '.xpdo-cache';
  159. }
  160. }
  161. else {
  162. $cachePath= strtr($this->xpdo->config['cache_path'], '\\', '/');
  163. }
  164. } else {
  165. $cachePath= $this->xpdo->cachePath;
  166. }
  167. if ($cachePath) {
  168. $perms = $this->getOption('new_folder_permissions', null, $this->getFolderPermissions());
  169. if (is_string($perms)) $perms = octdec($perms);
  170. if (@ $this->writeTree($cachePath, $perms)) {
  171. if ($cachePath{strlen($cachePath) - 1} != '/') $cachePath .= '/';
  172. if (!is_writeable($cachePath)) {
  173. @ chmod($cachePath, $perms);
  174. }
  175. } else {
  176. $cachePath= false;
  177. }
  178. }
  179. return $cachePath;
  180. }
  181. /**
  182. * Writes a file to the filesystem.
  183. *
  184. * @access public
  185. * @param string $filename The absolute path to the location the file will
  186. * be written in.
  187. * @param string $content The content of the newly written file.
  188. * @param string $mode The php file mode to write in. Defaults to 'wb'. Note that this method always
  189. * uses a (with b or t if specified) to open the file and that any mode except a means existing file
  190. * contents will be overwritten.
  191. * @param array $options An array of options for the function.
  192. * @return boolean Returns true if the file was successfully written.
  193. */
  194. public function writeFile($filename, $content, $mode= 'wb', $options= array()) {
  195. $written= false;
  196. if (!is_array($options)) {
  197. $options = is_scalar($options) && !is_bool($options) ? array('new_folder_permissions' => $options) : array();
  198. }
  199. $dirname= dirname($filename);
  200. if (!file_exists($dirname)) {
  201. $this->writeTree($dirname, $options);
  202. }
  203. $mode = str_replace('+', '', $mode);
  204. switch ($mode[0]) {
  205. case 'a':
  206. $append = true;
  207. break;
  208. default:
  209. $append = false;
  210. break;
  211. }
  212. $fmode = (strlen($mode) > 1 && in_array($mode[1], array('b', 't'))) ? "a{$mode[1]}" : 'a';
  213. $file= @fopen($filename, $fmode);
  214. if ($file) {
  215. if ($append === true) {
  216. $written= fwrite($file, $content);
  217. } else {
  218. $locked = false;
  219. $attempt = 1;
  220. $attempts = (integer) $this->getOption(xPDO::OPT_CACHE_ATTEMPTS, $options, 1);
  221. $attemptDelay = (integer) $this->getOption(xPDO::OPT_CACHE_ATTEMPT_DELAY, $options, 1000);
  222. while (!$locked && ($attempts === 0 || $attempt <= $attempts)) {
  223. $locked = flock($file, LOCK_EX | LOCK_NB);
  224. if (!$locked && $attemptDelay > 0 && ($attempts === 0 || $attempt < $attempts)) {
  225. usleep($attemptDelay);
  226. }
  227. $attempt++;
  228. }
  229. if ($locked) {
  230. fseek($file, 0);
  231. ftruncate($file, 0);
  232. $written= fwrite($file, $content);
  233. flock($file, LOCK_UN);
  234. }
  235. }
  236. @fclose($file);
  237. }
  238. return ($written !== false);
  239. }
  240. /**
  241. * Recursively writes a directory tree of files to the filesystem
  242. *
  243. * @access public
  244. * @param string $dirname The directory to write
  245. * @param array $options An array of options for the function. Can also be a value representing
  246. * a permissions mode to write new directories with, though this is deprecated.
  247. * @return boolean Returns true if the directory was successfully written.
  248. */
  249. public function writeTree($dirname, $options= array()) {
  250. $written= false;
  251. if (!empty ($dirname)) {
  252. if (!is_array($options)) $options = is_scalar($options) && !is_bool($options) ? array('new_folder_permissions' => $options) : array();
  253. $mode = $this->getOption('new_folder_permissions', $options, $this->getFolderPermissions());
  254. if (is_string($mode)) $mode = octdec($mode);
  255. $dirname= strtr(trim($dirname), '\\', '/');
  256. if ($dirname{strlen($dirname) - 1} == '/') $dirname = substr($dirname, 0, strlen($dirname) - 1);
  257. if (is_dir($dirname) || (is_writable(dirname($dirname)) && @mkdir($dirname, $mode))) {
  258. $written= true;
  259. } elseif (!$this->writeTree(dirname($dirname), $options)) {
  260. $written= false;
  261. } else {
  262. $written= @ mkdir($dirname, $mode);
  263. }
  264. if ($written && !is_writable($dirname)) {
  265. @ chmod($dirname, $mode);
  266. }
  267. }
  268. return $written;
  269. }
  270. /**
  271. * Copies a file from a source file to a target directory.
  272. *
  273. * @access public
  274. * @param string $source The absolute path of the source file.
  275. * @param string $target The absolute path of the target destination
  276. * directory.
  277. * @param array $options An array of options for this function.
  278. * @return boolean|array Returns true if the copy operation was successful, or a single element
  279. * array with filename as key and stat results of the successfully copied file as a result.
  280. */
  281. public function copyFile($source, $target, $options = array()) {
  282. $copied= false;
  283. if (!is_array($options)) $options = is_scalar($options) && !is_bool($options) ? array('new_file_permissions' => $options) : array();
  284. if (func_num_args() === 4) $options['new_folder_permissions'] = func_get_arg(3);
  285. if ($this->writeTree(dirname($target), $options)) {
  286. $existed= file_exists($target);
  287. if ($existed && $this->getOption('copy_newer_only', $options, false) && (($ttime = filemtime($target)) > ($stime = filemtime($source)))) {
  288. $this->xpdo->log(xPDO::LOG_LEVEL_INFO, "xPDOCacheManager->copyFile(): Skipping copy of newer file {$target} ({$ttime}) from {$source} ({$stime})");
  289. } else {
  290. $copied= copy($source, $target);
  291. }
  292. if ($copied) {
  293. if (!$this->getOption('copy_preserve_permissions', $options, false)) {
  294. $fileMode = $this->getOption('new_file_permissions', $options, $this->getFilePermissions());
  295. if (is_string($fileMode)) $fileMode = octdec($fileMode);
  296. @ chmod($target, $fileMode);
  297. }
  298. if ($this->getOption('copy_preserve_filemtime', $options, true)) @ touch($target, filemtime($source));
  299. if ($this->getOption('copy_return_file_stat', $options, false)) {
  300. $stat = stat($target);
  301. if (is_array($stat)) {
  302. $stat['overwritten']= $existed;
  303. $copied = array($target => $stat);
  304. }
  305. }
  306. }
  307. }
  308. if (!$copied) {
  309. $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, "xPDOCacheManager->copyFile(): Could not copy file {$source} to {$target}");
  310. }
  311. return $copied;
  312. }
  313. /**
  314. * Recursively copies a directory tree from a source directory to a target
  315. * directory.
  316. *
  317. * @access public
  318. * @param string $source The absolute path of the source directory.
  319. * @param string $target The absolute path of the target destination directory.
  320. * @param array $options An array of options for this function.
  321. * @return array|boolean Returns an array of all files and folders that were copied or false.
  322. */
  323. public function copyTree($source, $target, $options= array()) {
  324. $copied= false;
  325. $source= strtr($source, '\\', '/');
  326. $target= strtr($target, '\\', '/');
  327. if ($source{strlen($source) - 1} == '/') $source = substr($source, 0, strlen($source) - 1);
  328. if ($target{strlen($target) - 1} == '/') $target = substr($target, 0, strlen($target) - 1);
  329. if (is_dir($source . '/')) {
  330. if (!is_array($options)) $options = is_scalar($options) && !is_bool($options) ? array('new_folder_permissions' => $options) : array();
  331. if (func_num_args() === 4) $options['new_file_permissions'] = func_get_arg(3);
  332. if (!is_dir($target . '/')) {
  333. $this->writeTree($target . '/', $options);
  334. }
  335. if (is_dir($target)) {
  336. if (!is_writable($target)) {
  337. $dirMode = $this->getOption('new_folder_permissions', $options, $this->getFolderPermissions());
  338. if (is_string($dirMode)) $dirMode = octdec($dirMode);
  339. if (! @ chmod($target, $dirMode)) {
  340. $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, "{$target} is not writable and permissions could not be modified.");
  341. }
  342. }
  343. if ($handle= @ opendir($source)) {
  344. $excludeItems = $this->getOption('copy_exclude_items', $options, array('.', '..','.svn','.svn/','.svn\\'));
  345. $excludePatterns = $this->getOption('copy_exclude_patterns', $options);
  346. $copiedFiles = array();
  347. $error = false;
  348. while (false !== ($item= readdir($handle))) {
  349. $copied = false;
  350. if (is_array($excludeItems) && !empty($excludeItems) && in_array($item, $excludeItems)) continue;
  351. if (is_array($excludePatterns) && !empty($excludePatterns) && $this->matches($item, $excludePatterns)) continue;
  352. $from= $source . '/' . $item;
  353. $to= $target . '/' . $item;
  354. if (is_dir($from)) {
  355. if (!($copied= $this->copyTree($from, $to, $options))) {
  356. $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Could not copy directory {$from} to {$to}");
  357. $error = true;
  358. } else {
  359. $copiedFiles = array_merge($copiedFiles, $copied);
  360. }
  361. } elseif (is_file($from)) {
  362. if (!$copied= $this->copyFile($from, $to, $options)) {
  363. $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Could not copy file {$from} to {$to}; could not create directory.");
  364. $error = true;
  365. } else {
  366. $copiedFiles[] = $to;
  367. }
  368. } else {
  369. $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Could not copy {$from} to {$to}");
  370. }
  371. }
  372. @ closedir($handle);
  373. if (!$error) $copiedFiles[] = $target;
  374. $copied = $copiedFiles;
  375. } else {
  376. $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Could not read source directory {$source}");
  377. }
  378. } else {
  379. $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Could not create target directory {$target}");
  380. }
  381. } else {
  382. $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Source directory {$source} does not exist.");
  383. }
  384. return $copied;
  385. }
  386. /**
  387. * Recursively deletes a directory tree of files.
  388. *
  389. * @access public
  390. * @param string $dirname An absolute path to the source directory to delete.
  391. * @param array $options An array of options for this function.
  392. * @return boolean Returns true if the deletion was successful.
  393. */
  394. public function deleteTree($dirname, $options= array('deleteTop' => false, 'skipDirs' => false, 'extensions' => array('.cache.php'))) {
  395. $result= false;
  396. if (is_dir($dirname)) { /* Operate on dirs only */
  397. if (substr($dirname, -1) != '/') {
  398. $dirname .= '/';
  399. }
  400. $result= array ();
  401. if (!is_array($options)) {
  402. $numArgs = func_num_args();
  403. $options = array(
  404. 'deleteTop' => is_scalar($options) ? (boolean) $options : false
  405. ,'skipDirs' => $numArgs > 2 ? func_get_arg(2) : false
  406. ,'extensions' => $numArgs > 3 ? func_get_arg(3) : array('.cache.php')
  407. );
  408. }
  409. $hasMore= true;
  410. if ($handle= opendir($dirname)) {
  411. $limit= 4;
  412. $extensions= $this->getOption('extensions', $options, array('.cache.php'));
  413. $excludeItems = $this->getOption('delete_exclude_items', $options, array('.', '..','.svn','.svn/','.svn\\'));
  414. $excludePatterns = $this->getOption('delete_exclude_patterns', $options);
  415. while ($hasMore && $limit--) {
  416. if (!$handle) {
  417. $handle= opendir($dirname);
  418. }
  419. $hasMore= false;
  420. while (false !== ($file= @ readdir($handle))) {
  421. if (is_array($excludeItems) && !empty($excludeItems) && in_array($file, $excludeItems)) continue;
  422. if (is_array($excludePatterns) && !empty($excludePatterns) && $this->matches($file, $excludePatterns)) continue;
  423. if ($file != '.' && $file != '..') { /* Ignore . and .. */
  424. $path= $dirname . $file;
  425. if (is_dir($path)) {
  426. $suboptions = $this->getOption('deleteTop', $options, false) ? $options : array_merge($options, array('deleteTop' => false));
  427. if ($subresult= $this->deleteTree($path, $suboptions)) {
  428. $result= array_merge($result, $subresult);
  429. }
  430. }
  431. elseif (is_file($path)) {
  432. if (is_array($extensions) && !empty($extensions) && !$this->endsWith($file, $extensions)) continue;
  433. if (unlink($path)) {
  434. array_push($result, $path);
  435. } else {
  436. $hasMore= true;
  437. }
  438. }
  439. }
  440. }
  441. closedir($handle);
  442. }
  443. $options['deleteTop']= $this->getOption('skipDirs', $options, false) ? false : $this->getOption('deleteTop', $options, false);
  444. if ($this->getOption('deleteTop', $options, false)) {
  445. if (@ rmdir($dirname)) {
  446. array_push($result, $dirname);
  447. }
  448. }
  449. }
  450. } else {
  451. $result= false; /* return false if attempting to operate on a file */
  452. }
  453. return $result;
  454. }
  455. /**
  456. * Sees if a string ends with a specific pattern or set of patterns.
  457. *
  458. * @access public
  459. * @param string $string The string to check.
  460. * @param string|array $pattern The pattern or an array of patterns to check against.
  461. * @return boolean True if the string ends with the pattern or any of the patterns provided.
  462. */
  463. public function endsWith($string, $pattern) {
  464. $matched= false;
  465. if (is_string($string) && ($stringLen= strlen($string))) {
  466. if (is_array($pattern)) {
  467. foreach ($pattern as $subPattern) {
  468. if (is_string($subPattern) && $this->endsWith($string, $subPattern)) {
  469. $matched= true;
  470. break;
  471. }
  472. }
  473. } elseif (is_string($pattern)) {
  474. if (($patternLen= strlen($pattern)) && $stringLen >= $patternLen) {
  475. $matched= (substr($string, -$patternLen) === $pattern);
  476. }
  477. }
  478. }
  479. return $matched;
  480. }
  481. /**
  482. * Sees if a string matches a specific pattern or set of patterns.
  483. *
  484. * @access public
  485. * @param string $string The string to check.
  486. * @param string|array $pattern The pattern or an array of patterns to check against.
  487. * @return boolean True if the string matched the pattern or any of the patterns provided.
  488. */
  489. public function matches($string, $pattern) {
  490. $matched= false;
  491. if (is_string($string) && ($stringLen= strlen($string))) {
  492. if (is_array($pattern)) {
  493. foreach ($pattern as $subPattern) {
  494. if (is_string($subPattern) && $this->matches($string, $subPattern)) {
  495. $matched= true;
  496. break;
  497. }
  498. }
  499. } elseif (is_string($pattern)) {
  500. $matched= preg_match($pattern, $string);
  501. }
  502. }
  503. return $matched;
  504. }
  505. /**
  506. * Generate a PHP executable representation of an xPDOObject.
  507. *
  508. * @todo Complete $generateRelated functionality.
  509. * @todo Add stdObject support.
  510. *
  511. * @access public
  512. * @param xPDOObject $obj An xPDOObject to generate the cache file for
  513. * @param string $objName The name of the xPDOObject
  514. * @param boolean $generateObjVars If true, will also generate maps for all
  515. * object variables. Defaults to false.
  516. * @param boolean $generateRelated If true, will also generate maps for all
  517. * related objects. Defaults to false.
  518. * @param string $objRef The reference to the xPDO instance, in string
  519. * format.
  520. * @param boolean $format The format to cache in. Defaults to
  521. * xPDOCacheManager::CACHE_PHP, which is set to cache in executable PHP format.
  522. * @return string The source map file, in string format.
  523. */
  524. public function generateObject($obj, $objName, $generateObjVars= false, $generateRelated= false, $objRef= 'this->xpdo', $format= xPDOCacheManager::CACHE_PHP) {
  525. $source= false;
  526. if (is_object($obj) && $obj instanceof xPDOObject) {
  527. $className= $obj->_class;
  528. $source= "\${$objName}= \${$objRef}->newObject('{$className}');\n";
  529. $source .= "\${$objName}->fromArray(" . var_export($obj->toArray('', true), true) . ", '', true, true);\n";
  530. if ($generateObjVars && $objectVars= get_object_vars($obj)) {
  531. while (list($vk, $vv)= each($objectVars)) {
  532. if ($vk === 'modx') {
  533. $source .= "\${$objName}->{$vk}= & \${$objRef};\n";
  534. }
  535. elseif ($vk === 'xpdo') {
  536. $source .= "\${$objName}->{$vk}= & \${$objRef};\n";
  537. }
  538. elseif (!is_resource($vv)) {
  539. $source .= "\${$objName}->{$vk}= " . var_export($vv, true) . ";\n";
  540. }
  541. }
  542. }
  543. if ($generateRelated && !empty ($obj->_relatedObjects)) {
  544. foreach ($obj->_relatedObjects as $className => $fk) {
  545. foreach ($fk as $key => $relObj) {} /* TODO: complete $generateRelated functionality */
  546. }
  547. }
  548. }
  549. return $source;
  550. }
  551. /**
  552. * Add a key-value pair to a cache provider if it does not already exist.
  553. *
  554. * @param string $key A unique key identifying the item being stored.
  555. * @param mixed & $var A reference to the PHP variable representing the item.
  556. * @param integer $lifetime Seconds the item will be valid in cache.
  557. * @param array $options Additional options for the cache add operation.
  558. */
  559. public function add($key, & $var, $lifetime= 0, $options= array()) {
  560. $return= false;
  561. if ($cache = $this->getCacheProvider($this->getOption(xPDO::OPT_CACHE_KEY, $options))) {
  562. $value= null;
  563. if (is_object($var) && $var instanceof xPDOObject) {
  564. $value= $var->toArray('', true);
  565. } else {
  566. $value= $var;
  567. }
  568. $return= $cache->add($key, $value, $lifetime, $options);
  569. }
  570. return $return;
  571. }
  572. /**
  573. * Replace a key-value pair in in a cache provider.
  574. *
  575. * @access public
  576. * @param string $key A unique key identifying the item being replaced.
  577. * @param mixed & $var A reference to the PHP variable representing the item.
  578. * @param integer $lifetime Seconds the item will be valid in objcache.
  579. * @param array $options Additional options for the cache replace operation.
  580. * @return boolean True if the replace was successful.
  581. */
  582. public function replace($key, & $var, $lifetime= 0, $options= array()) {
  583. $return= false;
  584. if ($cache = $this->getCacheProvider($this->getOption(xPDO::OPT_CACHE_KEY, $options), $options)) {
  585. $value= null;
  586. if (is_object($var) && $var instanceof xPDOObject) {
  587. $value= $var->toArray('', true);
  588. } else {
  589. $value= $var;
  590. }
  591. $return= $cache->replace($key, $value, $lifetime, $options);
  592. }
  593. return $return;
  594. }
  595. /**
  596. * Set a key-value pair in a cache provider.
  597. *
  598. * @access public
  599. * @param string $key A unique key identifying the item being set.
  600. * @param mixed & $var A reference to the PHP variable representing the item.
  601. * @param integer $lifetime Seconds the item will be valid in objcache.
  602. * @param array $options Additional options for the cache set operation.
  603. * @return boolean True if the set was successful
  604. */
  605. public function set($key, & $var, $lifetime= 0, $options= array()) {
  606. $return= false;
  607. if ($cache = $this->getCacheProvider($this->getOption(xPDO::OPT_CACHE_KEY, $options), $options)) {
  608. $value= null;
  609. if (is_object($var) && $var instanceof xPDOObject) {
  610. $value= $var->toArray('', true);
  611. } else {
  612. $value= $var;
  613. }
  614. $return= $cache->set($key, $value, $lifetime, $options);
  615. } else {
  616. $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, 'No cache implementation found.');
  617. }
  618. return $return;
  619. }
  620. /**
  621. * Delete a key-value pair from a cache provider.
  622. *
  623. * @access public
  624. * @param string $key A unique key identifying the item being deleted.
  625. * @param array $options Additional options for the cache deletion.
  626. * @return boolean True if the deletion was successful.
  627. */
  628. public function delete($key, $options = array()) {
  629. $return= false;
  630. if ($cache = $this->getCacheProvider($this->getOption(xPDO::OPT_CACHE_KEY, $options), $options)) {
  631. $return= $cache->delete($key, $options);
  632. }
  633. return $return;
  634. }
  635. /**
  636. * Get a value from a cache provider by key.
  637. *
  638. * @access public
  639. * @param string $key A unique key identifying the item being retrieved.
  640. * @param array $options Additional options for the cache retrieval.
  641. * @return mixed The value of the object cache key
  642. */
  643. public function get($key, $options = array()) {
  644. $return= false;
  645. if ($cache = $this->getCacheProvider($this->getOption(xPDO::OPT_CACHE_KEY, $options), $options)) {
  646. $return= $cache->get($key, $options);
  647. }
  648. return $return;
  649. }
  650. /**
  651. * Flush the contents of a cache provider.
  652. *
  653. * @access public
  654. * @param array $options Additional options for the cache flush.
  655. * @return boolean True if the flush was successful.
  656. */
  657. public function clean($options = array()) {
  658. $return= false;
  659. if ($cache = $this->getCacheProvider($this->getOption(xPDO::OPT_CACHE_KEY, $options), $options)) {
  660. $return= $cache->flush($options);
  661. }
  662. return $return;
  663. }
  664. /**
  665. * Refresh specific or all cache providers.
  666. *
  667. * The default behavior is to call clean() with the provided options
  668. *
  669. * @param array $providers An associative array with keys representing the cache provider key
  670. * and the value an array of options.
  671. * @param array &$results An associative array for collecting results for each provider.
  672. * @return array An array of results for each provider that is refreshed.
  673. */
  674. public function refresh(array $providers = array(), array &$results = array()) {
  675. if (empty($providers)) {
  676. foreach ($this->caches as $cacheKey => $cache) {
  677. $providers[$cacheKey] = array();
  678. }
  679. }
  680. foreach ($providers as $key => $options) {
  681. if (array_key_exists($key, $this->caches) && !array_key_exists($key, $results)) {
  682. $results[$key] = $this->clean(array_merge($options, array(xPDO::OPT_CACHE_KEY => $key)));
  683. }
  684. }
  685. return (array_search(false, $results, true) === false);
  686. }
  687. /**
  688. * Escapes all single quotes in a string
  689. *
  690. * @access public
  691. * @param string $s The string to escape single quotes in.
  692. * @return string The string with single quotes escaped.
  693. */
  694. public function escapeSingleQuotes($s) {
  695. $q1= array (
  696. "\\",
  697. "'"
  698. );
  699. $q2= array (
  700. "\\\\",
  701. "\\'"
  702. );
  703. return str_replace($q1, $q2, $s);
  704. }
  705. }
  706. /**
  707. * An abstract class that defines the methods a cache provider must implement.
  708. *
  709. * @package xpdo
  710. * @subpackage cache
  711. */
  712. abstract class xPDOCache {
  713. public $xpdo= null;
  714. protected $options= array();
  715. protected $key= '';
  716. protected $initialized= false;
  717. public function __construct(& $xpdo, $options = array()) {
  718. $this->xpdo= & $xpdo;
  719. $this->options= $options;
  720. $this->key = $this->getOption(xPDO::OPT_CACHE_KEY, $options, 'default');
  721. }
  722. /**
  723. * Indicates if this xPDOCache instance has been properly initialized.
  724. *
  725. * @return boolean true if the implementation was initialized successfully.
  726. */
  727. public function isInitialized() {
  728. return (boolean) $this->initialized;
  729. }
  730. /**
  731. * Get an option from supplied options, the cache options, or the xpdo config.
  732. *
  733. * @param string $key Unique identifier for the option.
  734. * @param array $options A set of explicit options to override those from xPDO or the xPDOCache
  735. * implementation.
  736. * @param mixed $default An optional default value to return if no value is found.
  737. * @return mixed The value of the option.
  738. */
  739. public function getOption($key, $options = array(), $default = null) {
  740. $option = $default;
  741. if (is_array($key)) {
  742. if (!is_array($option)) {
  743. $default= $option;
  744. $option= array();
  745. }
  746. foreach ($key as $k) {
  747. $option[$k]= $this->getOption($k, $options, $default);
  748. }
  749. } elseif (is_string($key) && !empty($key)) {
  750. if (is_array($options) && !empty($options) && array_key_exists($key, $options)) {
  751. $option = $options[$key];
  752. } elseif (is_array($this->options) && !empty($this->options) && array_key_exists($key, $this->options)) {
  753. $option = $this->options[$key];
  754. } else {
  755. $option = $this->xpdo->cacheManager->getOption($key, null, $default);
  756. }
  757. }
  758. return $option;
  759. }
  760. /**
  761. * Get the actual cache key the implementation will use.
  762. *
  763. * @param string $key The identifier the application uses.
  764. * @param array $options Additional options for the operation.
  765. * @return string The identifier with any implementation specific prefixes or other
  766. * transformations applied.
  767. */
  768. public function getCacheKey($key, $options = array()) {
  769. $prefix = $this->getOption('cache_prefix', $options);
  770. if (!empty($prefix)) $key = $prefix . $key;
  771. return $this->key . '/' . $key;
  772. }
  773. /**
  774. * Adds a value to the cache.
  775. *
  776. * @access public
  777. * @param string $key A unique key identifying the item being set.
  778. * @param string $var A reference to the PHP variable representing the item.
  779. * @param integer $expire The amount of seconds for the variable to expire in.
  780. * @param array $options Additional options for the operation.
  781. * @return boolean True if successful
  782. */
  783. abstract public function add($key, $var, $expire= 0, $options= array());
  784. /**
  785. * Sets a value in the cache.
  786. *
  787. * @access public
  788. * @param string $key A unique key identifying the item being set.
  789. * @param string $var A reference to the PHP variable representing the item.
  790. * @param integer $expire The amount of seconds for the variable to expire in.
  791. * @param array $options Additional options for the operation.
  792. * @return boolean True if successful
  793. */
  794. abstract public function set($key, $var, $expire= 0, $options= array());
  795. /**
  796. * Replaces a value in the cache.
  797. *
  798. * @access public
  799. * @param string $key A unique key identifying the item being set.
  800. * @param string $var A reference to the PHP variable representing the item.
  801. * @param integer $expire The amount of seconds for the variable to expire in.
  802. * @param array $options Additional options for the operation.
  803. * @return boolean True if successful
  804. */
  805. abstract public function replace($key, $var, $expire= 0, $options= array());
  806. /**
  807. * Deletes a value from the cache.
  808. *
  809. * @access public
  810. * @param string $key A unique key identifying the item being deleted.
  811. * @param array $options Additional options for the operation.
  812. * @return boolean True if successful
  813. */
  814. abstract public function delete($key, $options= array());
  815. /**
  816. * Gets a value from the cache.
  817. *
  818. * @access public
  819. * @param string $key A unique key identifying the item to fetch.
  820. * @param array $options Additional options for the operation.
  821. * @return mixed The value retrieved from the cache.
  822. */
  823. public function get($key, $options= array()) {}
  824. /**
  825. * Flush all values from the cache.
  826. *
  827. * @access public
  828. * @param array $options Additional options for the operation.
  829. * @return boolean True if successful.
  830. */
  831. abstract public function flush($options= array());
  832. }
  833. /**
  834. * A simple file-based caching implementation using executable PHP.
  835. *
  836. * This can be used to relieve database loads, though the overall performance is
  837. * about the same as without the file-based cache. For maximum performance and
  838. * scalability, use a server with memcached and the PHP memcache extension
  839. * configured.
  840. *
  841. * @package xpdo
  842. * @subpackage cache
  843. */
  844. class xPDOFileCache extends xPDOCache {
  845. public function __construct(& $xpdo, $options = array()) {
  846. parent :: __construct($xpdo, $options);
  847. $this->initialized = true;
  848. }
  849. public function getCacheKey($key, $options = array()) {
  850. $cachePath = $this->getOption('cache_path', $options);
  851. $cacheExt = $this->getOption('cache_ext', $options, '.cache.php');
  852. $key = parent :: getCacheKey($key, $options);
  853. return $cachePath . $key . $cacheExt;
  854. }
  855. public function add($key, $var, $expire= 0, $options= array()) {
  856. $added= false;
  857. if (!file_exists($this->getCacheKey($key, $options))) {
  858. if ($expire === true)
  859. $expire= 0;
  860. $added= $this->set($key, $var, $expire, $options);
  861. }
  862. return $added;
  863. }
  864. public function set($key, $var, $expire= 0, $options= array()) {
  865. $set= false;
  866. if ($var !== null) {
  867. if ($expire === true)
  868. $expire= 0;
  869. $expirationTS= $expire ? time() + $expire : 0;
  870. $expireContent= '';
  871. if ($expirationTS) {
  872. $expireContent= 'if(time() > ' . $expirationTS . '){return null;}';
  873. }
  874. $fileName= $this->getCacheKey($key, $options);
  875. $format = (integer) $this->getOption(xPDO::OPT_CACHE_FORMAT, $options, xPDOCacheManager::CACHE_PHP);
  876. switch ($format) {
  877. case xPDOCacheManager::CACHE_SERIALIZE:
  878. $content= serialize(array('expires' => $expirationTS, 'content' => $var));
  879. break;
  880. case xPDOCacheManager::CACHE_JSON:
  881. $content= $this->xpdo->toJSON(array('expires' => $expirationTS, 'content' => $var));
  882. break;
  883. case xPDOCacheManager::CACHE_PHP:
  884. default:
  885. $content= '<?php ' . $expireContent . ' return ' . var_export($var, true) . ';';
  886. break;
  887. }
  888. $set= $this->xpdo->cacheManager->writeFile($fileName, $content);
  889. }
  890. return $set;
  891. }
  892. public function replace($key, $var, $expire= 0, $options= array()) {
  893. $replaced= false;
  894. if (file_exists($this->getCacheKey($key, $options))) {
  895. if ($expire === true)
  896. $expire= 0;
  897. $replaced= $this->set($key, $var, $expire, $options);
  898. }
  899. return $replaced;
  900. }
  901. public function delete($key, $options= array()) {
  902. $deleted= false;
  903. $cacheKey= $this->getCacheKey($key, array_merge($options, array('cache_ext' => '')));
  904. if (file_exists($cacheKey) && is_dir($cacheKey)) {
  905. $results = $this->xpdo->cacheManager->deleteTree($cacheKey, array_merge(array('deleteTop' => false, 'skipDirs' => false, 'extensions' => array('.cache.php')), $options));
  906. if ($results !== false) {
  907. $deleted = true;
  908. }
  909. } else {
  910. $cacheKey= $this->getCacheKey($key, $options);
  911. if (file_exists($cacheKey)) {
  912. $deleted= @ unlink($cacheKey);
  913. }
  914. }
  915. return $deleted;
  916. }
  917. public function get($key, $options= array()) {
  918. $value= null;
  919. $cacheKey= $this->getCacheKey($key, $options);
  920. if (file_exists($cacheKey)) {
  921. if ($file = @fopen($cacheKey, 'rb')) {
  922. $format = (integer) $this->getOption(xPDO::OPT_CACHE_FORMAT, $options, xPDOCacheManager::CACHE_PHP);
  923. if (flock($file, LOCK_SH)) {
  924. switch ($format) {
  925. case xPDOCacheManager::CACHE_PHP:
  926. $value= @include $cacheKey;
  927. break;
  928. case xPDOCacheManager::CACHE_JSON:
  929. $payload = stream_get_contents($file);
  930. if ($payload !== false) {
  931. $payload = $this->xpdo->fromJSON($payload);
  932. if (is_array($payload) && isset($payload['expires']) && (empty($payload['expires']) || time() < $payload['expires'])) {
  933. if (array_key_exists('content', $payload)) {
  934. $value= $payload['content'];
  935. }
  936. }
  937. }
  938. break;
  939. case xPDOCacheManager::CACHE_SERIALIZE:
  940. $payload = stream_get_contents($file);
  941. if ($payload !== false) {
  942. $payload = unserialize($payload);
  943. if (is_array($payload) && isset($payload['expires']) && (empty($payload['expires']) || time() < $payload['expires'])) {
  944. if (array_key_exists('content', $payload)) {
  945. $value= $payload['content'];
  946. }
  947. }
  948. }
  949. break;
  950. }
  951. flock($file, LOCK_UN);
  952. if ($value === null && $this->getOption('removeIfEmpty', $options, true)) {
  953. fclose($file);
  954. @ unlink($cacheKey);
  955. return $value;
  956. }
  957. }
  958. @fclose($file);
  959. }
  960. }
  961. return $value;
  962. }
  963. public function flush($options= array()) {
  964. $cacheKey= $this->getCacheKey('', array_merge($options, array('cache_ext' => '')));
  965. $results = $this->xpdo->cacheManager->deleteTree($cacheKey, array_merge(array('deleteTop' => false, 'skipDirs' => false, 'extensions' => array('.cache.php')), $options));
  966. return ($results !== false);
  967. }
  968. }