Sindbad~EG File Manager
<?php
/*
* Gallery - a web based photo album viewer and editor
* Copyright (C) 2000-2007 Bharat Mediratta
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
*/
/**
* Utility class for caching data
*
* Very useful in the case where retrieving or building data sets is expensive, and the data
* doesn't change during the lifetime of the request. This class serves as a hash table where
* any data class can store and retrieve cached data.
*
* @package GalleryCore
* @subpackage Classes
* @author Bharat Mediratta <bharat@menalto.com>
* @version $Revision: 16011 $
* @static
*/
class GalleryDataCache {
/**
* Get the static cache
*
* @return array the cache
* @staticvar cache the singleton cache
* @access private
*/
function &_getCache() {
static $cache;
if (!isset($cache)) {
$cache['maxKeys'] = 300;
$cache['positions'] = array();
$cache['latestPosition'] = 0;
$cache['keys'] = array();
$cache['protected'] = array();
$cache['memoryCacheEnabled'] = 1;
$cache['fileCacheEnabled'] = 1;
}
return $cache;
}
/**
* Is in-memory caching enabled?
* @return boolean true if it's enabled
*/
function isMemoryCachingEnabled() {
$cache =& GalleryDataCache::_getCache();
return $cache['memoryCacheEnabled'];
}
/**
* Turn in-memory caching on or off
* @param boolean $bool
*/
function setMemoryCachingEnabled($bool) {
$cache =& GalleryDataCache::_getCache();
$cache['memoryCacheEnabled'] = $bool;
}
/**
* Store data in the cache
* You must provide a unique key. Existing keys are overwritten.
*
* @param string $key
* @param mixed $data
* @param boolean $protected should this key survive a reset call?
*/
function put($key, $data, $protected=false) {
$cache =& GalleryDataCache::_getCache();
if (!$cache['memoryCacheEnabled']) {
return;
}
$cache['keys'][$key] = $data;
if ($protected) {
$cache['protected'][$key] = 1;
} else {
$cache['positions'][$key] = $cache['latestPosition']++;
}
if ($cache['latestPosition'] % 150 == 0) {
GalleryDataCache::_performMaintenance();
}
}
/**
* Remove data from the cache
* @param string $key
*/
function remove($key) {
$cache =& GalleryDataCache::_getCache();
if (!$cache['memoryCacheEnabled']) {
return;
}
unset($cache['positions'][$key]);
unset($cache['keys'][$key]);
unset($cache['protected'][$key]);
}
/**
* Remove data from the cache
* @param string $pattern regexp
*/
function removeByPattern($pattern) {
$cache =& GalleryDataCache::_getCache();
if (!$cache['memoryCacheEnabled']) {
return;
}
foreach (preg_grep("/$pattern/", array_keys($cache['keys'])) as $key) {
unset($cache['positions'][$key]);
unset($cache['keys'][$key]);
unset($cache['protected'][$key]);
}
}
/**
* Store a reference to the data in the cache
*
* You must provide a unique key. Existing keys are overwritten.
*
* @param string $key
* @param mixed $data
* @param boolean $protected (optional) should this key survive a reset call?
*/
function putByReference($key, &$data, $protected=false) {
$cache =& GalleryDataCache::_getCache();
if (!$cache['memoryCacheEnabled']) {
return;
}
$cache['keys'][$key] =& $data;
if ($protected) {
$cache['protected'][$key] = 1;
} else {
$cache['positions'][$key] = $cache['latestPosition']++;
}
if ($cache['latestPosition'] % 150 == 0) {
GalleryDataCache::_performMaintenance();
}
}
/**
* Perform some pruning of our cache to prevent it from growing too large when we're doing
* exceptionally long operations like adding many items in one request.
*
* @access private
*/
function _performMaintenance() {
$cache =& GalleryDataCache::_getCache();
$numToExpire = sizeof($cache['positions']) - $cache['maxKeys'];
if ($numToExpire > 0) {
asort($cache['positions']);
foreach ($cache['positions'] as $key => $position) {
unset($cache['keys'][$key]);
unset($cache['positions'][$key]);
if ($numToExpire-- == 0) {
break;
}
}
}
}
/**
* Retrieve data from the cache
* @param string $key
* @return mixed the cached data
*/
function get($key) {
$cache =& GalleryDataCache::_getCache();
if (!$cache['memoryCacheEnabled']) {
return null;
}
if (!isset($cache['protected'][$key])) {
$cache['positions'][$key] = $cache['latestPosition']++;
}
return $cache['keys'][$key];
}
/**
* Does the cache contain the key specified?
* @param string $key
* @return boolean true if the cache contains the key given
*/
function containsKey($key) {
$cache =& GalleryDataCache::_getCache();
if (!$cache['memoryCacheEnabled']) {
return false;
}
return isset($cache['keys'][$key]);
}
/**
* Return all the keys in the cache
* @return array string keys
*/
function getAllKeys() {
$cache =& GalleryDataCache::_getCache();
if (!$cache['memoryCacheEnabled']) {
return array();
}
return array_keys($cache['keys']);
}
/**
* Empty the cache of all but protected entries
* @param boolean $purgeProtected (optional) purge protected also?
*/
function reset($purgeProtected=false) {
$cache =& GalleryDataCache::_getCache();
if (!$cache['memoryCacheEnabled']) {
return;
}
if ($purgeProtected) {
$cache['positions'] = array();
$cache['keys'] = array();
} else {
foreach (array_keys($cache['keys']) as $key) {
if (!isset($cache['protected'][$key])) {
unset($cache['positions'][$key]);
unset($cache['keys'][$key]);
}
}
}
}
/**
* Is caching to disk enabled?
* @return boolean
*/
function &isFileCachingEnabled() {
$cache =& GalleryDataCache::_getCache();
return $cache['fileCacheEnabled'];
}
/**
* Turn caching to disk on or off
* @param boolean $bool
*/
function setFileCachingEnabled($bool) {
$cache =& GalleryDataCache::_getCache();
$cache['fileCacheEnabled'] = $bool;
}
/**
* Get the file from disk. PathInfo is of the form that can be passed to getCachePath
* @see GalleryDataCache::getCachePath
* @param array $pathInfo the path info
* @return mixed object data
*/
function &getFromDisk($pathInfo) {
$null = null;
$cache =& GalleryDataCache::_getCache();
if (!$cache['fileCacheEnabled']) {
return $null;
}
global $gallery;
$platform =& $gallery->getPlatform();
$cacheFile = GalleryDataCache::getCachePath($pathInfo);
if ($platform->file_exists($cacheFile) &&
$buf = $platform->file_get_contents($cacheFile)) {
/* Parse the cache file */
$marker = strcspn($buf, '|');
foreach (explode(',', substr($buf, 0, $marker)) as $classFile) {
if ($classFile) {
GalleryCoreApi::requireOnce($classFile);
}
}
$data = unserialize(substr($buf, $marker+1));
return $data;
}
return $null;
}
/**
* Remove the cache file from disk. PathInfo is of the form that can be passed to getCachePath
* @see GalleryDataCache::getCachePath
* @param array $pathInfo the path info
*/
function removeFromDisk($pathInfo) {
$cache =& GalleryDataCache::_getCache();
if (!$cache['fileCacheEnabled']) {
return null;
}
global $gallery;
$platform =& $gallery->getPlatform();
if ($pathInfo['type'] == 'entity' ||
$pathInfo['type'] == 'derivative-meta' ||
$pathInfo['type'] == 'module-data' ||
isset($pathInfo['id'])) {
$cacheFile = GalleryDataCache::getCachePath($pathInfo);
if ($platform->file_exists($cacheFile)) {
if ($platform->is_dir($cacheFile)) {
$platform->recursiveRmDir($cacheFile);
} else {
$platform->unlink($cacheFile);
}
}
} else {
if ($pathInfo['type'] == 'module' || $pathInfo['type'] == 'theme') {
list ($ret, $pluginStatus) = GalleryCoreApi::fetchPluginStatus($pathInfo['type']);
if ($ret) {
return $ret;
}
foreach (array_keys($pluginStatus) as $pluginId) {
GalleryDataCache::removeFromDisk(array('type' => $pathInfo['type'],
'id' => $pluginId,
'itemId' => $pathInfo['itemId']));
}
}
}
}
/**
* Put the specified data into a cache file from disk. PathInfo is of the form that can
* be passed to getCachePath.
*
* @param array $pathInfo the path info
* @param mixed $data the object data
* @param array $requiredClasses classes that must be loaded in order to retrieve this data
* @see GalleryDataCache::getCachePath
*/
function putToDisk($pathInfo, &$data, $requiredClasses=array()) {
$cache =& GalleryDataCache::_getCache();
if (!$cache['fileCacheEnabled']) {
return;
}
global $gallery;
$cacheFile = GalleryDataCache::getCachePath($pathInfo);
$platform =& $gallery->getPlatform();
GalleryUtilities::guaranteeDirExists(dirname($cacheFile));
/* This will either succeed, or leave no trace of its attempt. */
$platform->atomicWrite(
$cacheFile, implode(',', $requiredClasses) . "|" . serialize($data));
}
/**
* For a given id, return a tuple with the breakdown of the id. The caching mechanism uses
* this to determine where in the cache tree to place the file. The breakdown happens
* according to the digits of the id. The first element returned is the hundreds digit,
* the second element is the tens digit.
*
* 0..9 => 0, 0
* 10..19 => 0, 1
* 20..29 => 0, 2
* ...
* 100..109 => 1, 0
* 110..119 => 1, 1
*
* @param int $id
* @return array the tuple
*/
function getCacheTuple($id) {
$id = "$id";
if ($id > 100) {
return array($id[0], $id[1]);
} else if ($id > 10) {
return array('0', $id[0]);
} else {
return array('0', '0');
}
}
/**
* Given a path info descriptor, return the path to the appropriate cache file.
*
* Path info contains the following variables:
* type: entity, derivative, derivative-meta, module, theme
* itemId: the item id
* id: (module, theme only) a refinement of the type
*
* @return string the path
*/
function getCachePath($pathInfo) {
global $gallery;
$base = $gallery->getConfig('data.gallery.cache');
$cacheFile = null;
switch ($pathInfo['type']) {
case 'entity':
list ($first, $second) = GalleryDataCache::getCacheTuple($pathInfo['itemId']);
$cacheFile = sprintf('%sentity/%s/%s/%d.inc',
$base, $first, $second, $pathInfo['itemId']);
break;
case 'derivative':
list ($first, $second) = GalleryDataCache::getCacheTuple($pathInfo['itemId']);
$cacheFile = sprintf('%sderivative/%s/%s/%d.dat',
$base, $first, $second, $pathInfo['itemId']);
break;
case 'derivative-relative':
list ($first, $second) = GalleryDataCache::getCacheTuple($pathInfo['itemId']);
$cacheFile = sprintf('derivative/%s/%s/%d.dat',
$first, $second, $pathInfo['itemId']);
break;
case 'derivative-meta':
list ($first, $second) = GalleryDataCache::getCacheTuple($pathInfo['itemId']);
$cacheFile = sprintf('%sderivative/%s/%s/%d-meta.inc',
$base, $first, $second, $pathInfo['itemId']);
break;
case 'fast-download':
list ($first, $second) = GalleryDataCache::getCacheTuple($pathInfo['itemId']);
$cacheFile = sprintf('%sentity/%s/%s/%d-fast.inc',
$base, $first, $second, $pathInfo['itemId']);
break;
case 'module':
case 'theme':
if (isset($pathInfo['id'])) {
if (strstr($pathInfo['id'], '..') !== false) {
$pathInfo['id'] = '0';
}
if (isset($pathInfo['itemId'])) {
if (strstr($pathInfo['itemId'], '..') !== false) {
$pathInfo['itemId'] = '0';
}
list ($first, $second) = GalleryDataCache::getCacheTuple($pathInfo['itemId']);
$cacheFile = sprintf('%s%s/%s/%s/%s/%s.inc',
$base, $pathInfo['type'], $pathInfo['id'],
$first, $second, $pathInfo['itemId']);
} else {
$cacheFile = sprintf('%s%s/%s', $base, $pathInfo['type'], $pathInfo['id']);
}
}
break;
case 'module-data':
if (strstr($pathInfo['module'], '..') !== false) {
$pathInfo['module'] = '0';
}
if (isset($pathInfo['itemId'])) {
list ($first, $second) = GalleryDataCache::getCacheTuple($pathInfo['itemId']);
/* $itemId is overloaded here; sanitize it so that it only accepts [0-9A-Za-z_]+ */
$itemId = preg_replace('/[^0-9A-Za-z_]/', '_', $pathInfo['itemId']);
$cacheFile = sprintf('%s%s/%s/%s/%s/%s.dat',
$base, 'module', $pathInfo['module'],
$first, $second, $itemId);
} else {
$cacheFile = sprintf('%s%s/%s/', $base, 'module', $pathInfo['module']);
}
break;
}
return $cacheFile;
}
/**
* Store the given permission => ids mapping in the session cache
* @param mixed $ids item ids (can be an array of ids or a single id)
* @param string $permission
*/
function cachePermissions($ids, $permission) {
global $gallery;
$session =& $gallery->getSession();
if (!isset($session)) {
/* No session means we've got no cache */
return;
}
if (!is_array($ids)) {
$ids = array($ids);
}
$permissions = $session->get('permissionCache');
/*
* We want to put all the permissions that we cache in this request into one entry in
* the session. However we're going to get several separate requests. So start by
* pruning down the total number of cached permission sets, then create a new set
* and add all cached values to that.
*/
static $initialized;
if (!isset($initialized)) {
if (!isset($permissions)) {
$permissions = array();
}
array_unshift($permissions, array());
/* Trim down the cache */
$max = 6;
if (sizeof($permissions) > $max) {
array_splice($permissions, -1, sizeof($permissions) - $max);
}
$initialized = 1;
}
$cacheKey = 'GalleryDataCache::cachePermissions::newEntries';
if (GalleryDataCache::containsKey($cacheKey)) {
$newEntries = GalleryDataCache::get($cacheKey);
} else {
$newEntries = 0;
}
/* Make sure the session permission cache is not too large */
$maxEntries = 40;
/* Add our new data to the head of the list */
while (($id = array_shift($ids)) && $maxEntries > $newEntries++) {
$permissions[0][$permission][$id] = 1;
}
GalleryDataCache::put($cacheKey, $newEntries);
$session->put('permissionCache', $permissions);
}
/**
* Look up the given permission in the cache. Return true if the permission
* exists in the cache, false if it doesn't. A return of false doesn't mean
* that the user doesn't have the permission -- just that it's not in the
* cache.
*
* @param int $id the item id
* @param string $permission
* @return boolean
*/
function hasPermission($id, $permission) {
global $gallery;
$session =& $gallery->getSession();
if (!isset($session)) {
return;
}
$permissions = $session->get('permissionCache');
/*
* Since we add all new data to the head of the list, the odds are good
* that we should find our answer in the first iteration of this loop.
*/
for ($i = 0; $i < sizeof($permissions); $i++) {
if (isset($permissions[$i][$permission][$id])) {
return true;
}
}
return false;
}
/**
* Clear permission cache for active user.
*/
function clearPermissionCache() {
global $gallery;
$session =& $gallery->getSession();
if (isset($session)) {
$session->remove('permissionCache');
}
}
/**
* Get page data from the cache.
*
* @param string $type the page type
* @param mixed $keyData data to use to generate the unique key for this cache entry
* @return array object GalleryStatus a status code
* string the page data
*/
function getPageData($type, $keyData) {
global $gallery;
list ($ret, $acceleration) =
GalleryCoreApi::getPluginParameter('module', 'core', 'acceleration');
if ($ret) {
return array($ret, null);
}
$acceleration = unserialize($acceleration);
list ($ret, $isAnonymous) = GalleryCoreApi::isAnonymousUser();
if ($ret) {
return array($ret, null);
}
$expiration = $acceleration[$isAnonymous ? 'guest' : 'user']['expiration'];
$phpVm = $gallery->getPhpVm();
$cutoff = $phpVm->time() - $expiration;
list ($ret, $aclIds) =
GalleryCoreApi::fetchAccessListIds('core.view', $gallery->getActiveUserId());
if ($ret) {
return array($ret, null);
}
list ($ret, $extraKey) = GalleryDataCache::_getExtraPageCacheKey();
if ($ret) {
return array($ret, null);
}
$key = md5(serialize($keyData) . '|' . $extraKey . '|' . implode(",", $aclIds));
$userId = $gallery->getActiveUserId();
/*
* Note: there is no complete index for this WHERE clause because the MySQL query optimizer
* would still use the PK for this query. This should not lead to a performance decrease as
* PK is a unique index thus returning only 0/1 rows that need to be compared with isEmpty
* and timestamp.
*/
list ($ret, $results) = GalleryCoreApi::getMapEntry('GalleryCacheMap',
array('value'),
array('key' => $key, 'type' => $type, 'userId' => $userId,
'timestamp' => new GallerySqlFragment('>?', $cutoff), 'isEmpty' => 0));
if ($ret) {
return array($ret, null);
}
if ($results->resultCount() > 0) {
$result = $results->nextResult();
$value = $result[0];
if (function_exists('gzinflate')) {
$storage =& $gallery->getStorage();
$value = $storage->decodeBlob($value);
$value = gzinflate($value);
}
} else {
$value = null;
}
return array(null, $value);
}
/**
* Store page data from the cache.
*
* @param string $type the page type
* @param int $itemId the itemId of the item this page is rendering
* @param mixed $keyData data to use to generate the unique key for this cache entry
* @param string $value the page data
* @return object GalleryStatus a status code
*/
function putPageData($type, $itemId, $keyData, $value) {
global $gallery;
$userId = $gallery->getActiveUserId();
list ($ret, $aclIds) = GalleryCoreApi::fetchAccessListIds('core.view', $userId);
if ($ret) {
return $ret;
}
list ($ret, $extraKey) = GalleryDataCache::_getExtraPageCacheKey();
if ($ret) {
return $ret;
}
$key = md5(serialize($keyData) . '|' . $extraKey . '|' . implode(",", $aclIds));
if (function_exists('gzdeflate')) {
$value = gzdeflate($value);
$storage =& $gallery->getStorage();
$value = $storage->encodeBlob($value);
}
$phpVm = $gallery->getPhpVm();
$now = $phpVm->time();
$ret = GalleryCoreApi::updateMapEntry(
'GalleryCacheMap',
array('key' => $key, 'userId' => $userId, 'itemId' => $itemId, 'type' => $type),
array('value' => $value, 'timestamp' => $now, 'isEmpty' => 0));
$storage =& $gallery->getStorage();
list ($ret, $affectedRows) = $storage->getAffectedRows();
if ($ret) {
return $ret;
}
if (!$affectedRows) {
$ret = GalleryCoreApi::addMapEntry(
'GalleryCacheMap',
array('key' => $key, 'value' => $value, 'userId' => $userId,
'itemId' => $itemId, 'type' => $type, 'timestamp' => $now, 'isEmpty' => 0));
if ($ret) {
return $ret;
}
}
/* Clear expired data only 5% of the time. */
$dieRoll = $phpVm->rand(1,100);
if($dieRoll <= 5){
$ret = GalleryDataCache::_cleanPageDataCache();
if ($ret) {
return $ret;
}
}
return null;
}
/**
* Remove all cached page data for the given item ids
*
* @param mixed $itemIds one item id, or an array of item ids
* @return object GalleryStatus a status code
*/
function removePageData($itemIds) {
$ret = GalleryCoreApi::removeMapEntry('GalleryCacheMap', array('itemId' => $itemIds));
if ($ret) {
return $ret;
}
return null;
}
/**
* Should we use the cache for this page or not? Right now we cache a page if it's a GET
* request, the browser hasn't specified that it wants uncached data, and the caching policy
* for the given user class is appropriate.
*
* @param string $action
* @param string $type the page type
* @return object GalleryStatus a status code
*/
function shouldCache($action, $type) {
global $gallery;
if (GalleryUtilities::getServerVar('REQUEST_METHOD') != 'GET') {
return array(null, false);
}
$phpVm = $gallery->getPhpVm();
if ($action == 'read') {
$noCache = (GalleryUtilities::getServerVar('HTTP_PRAGMA') == 'no-cache' ||
GalleryUtilities::getServerVar('HTTP_CACHE_CONTROL') == 'no-cache');
if ($noCache) {
return array(null, false);
}
}
/*
* Don't cache guest preview pages since we want a realtime preview
* and they are infrequently visited
*/
$session =& $gallery->getSession();
if ($session->get('theme.guestPreviewMode')) {
return array(null, false);
}
list ($ret, $acceleration) =
GalleryCoreApi::getPluginParameter('module', 'core', 'acceleration');
if ($ret) {
return array($ret, null);
}
$acceleration = unserialize($acceleration);
list ($ret, $isAnonymous) = GalleryCoreApi::isAnonymousUser();
if ($ret) {
return array($ret, null);
}
return array(null, $acceleration[$isAnonymous ? 'guest' : 'user']['type'] == $type);
}
/**
* Returns session related page cache key, eg. to make the page cache language sensitive
* @todo The extra key for exif module is a quick fix, not a permanent fix; it should be
* cached on the block level not on page level.
*
* @return array object GalleryStatus a status code
* string extra cache key
*/
function _getExtraPageCacheKey() {
global $gallery;
$session =& $gallery->getSession();
/* Add session specific flags & values to the cache key */
list ($ret, $languageCode) = $gallery->getActiveLanguageCode();
if ($ret) {
return array($ret, null);
}
$isEmbedded = (int)$gallery->isEmbedded();
$extraKey = (int)($session->isUsingCookies()) . '-' . $languageCode . '-' . $isEmbedded;
$mode = $session->get('exif.module.LoadExifInfo.mode');
if (!empty($mode) && $mode != 'summary') {
$extraKey .= '-exif' . $mode;
}
return array(null, $extraKey);
}
/**
* Delete value of obsolete cache data.
*
* @return object GalleryStatus a status code
*/
function _cleanPageDataCache() {
global $gallery;
list ($ret, $anonymousUserId) =
GalleryCoreApi::getPluginParameter('module', 'core', 'id.anonymousUser');
if ($ret) {
return $ret;
}
list ($ret, $acceleration) =
GalleryCoreApi::getPluginParameter('module', 'core', 'acceleration');
if ($ret) {
return $ret;
}
$acceleration = unserialize($acceleration);
list ($ret, $isAnonymous) = GalleryCoreApi::isAnonymousUser();
if ($ret) {
return $ret;
}
$expiration = $acceleration[$isAnonymous ? 'guest' : 'user']['expiration'];
$phpVm = $gallery->getPhpVm();
$cutoff = $phpVm->time() - $expiration;
/* Use UPDATE instead of DELETE since UPDATE is faster and we reuse the rows. */
$storage =& $gallery->getStorage();
if ($isAnonymous) {
$userIdForSql = (int)$anonymousUserId;
} else {
$userIdForSql = new GallerySqlFragment('<> ?', (int)$anonymousUserId);
}
list ($ret, $results) = GalleryCoreApi::updateMapEntry('GalleryCacheMap',
array('userId' => $userIdForSql,
'timestamp' => new GallerySqlFragment('< ?', (int)$cutoff), 'isEmpty' => 0),
array('value' => null, 'isEmpty' => 1));
if ($ret) {
return $ret;
}
return null;
}
}
?>
Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists