PageRenderTime 44ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 1ms

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

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