MidCOM svn: r13214 - in trunk/midcom/midcom.core/midcom: . config
services
flack
midcom-commits at lists.midgard-project.org
Wed Oct 31 17:49:52 CET 2007
Author: flack
Date: Wed Oct 31 17:49:52 2007
New Revision: 13214
URL: http://trac.midgard-project.org/software/changeset/13214
Log:
forward ported js/css merger and
uncommented the css part (still disabled by default)
Added:
trunk/midcom/midcom.core/midcom/services/js_css_merger.php
Modified:
trunk/midcom/midcom.core/midcom/application.php
trunk/midcom/midcom.core/midcom/config/midcom_config.php
Modified: trunk/midcom/midcom.core/midcom/application.php
==============================================================================
--- trunk/midcom/midcom.core/midcom/application.php (original)
+++ trunk/midcom/midcom.core/midcom/application.php Wed Oct 31 17:49:52 2007
@@ -235,6 +235,11 @@
public $auth = null;
/**
+ * JS/CSS merger service
+ */
+ var $jscss = false;
+
+ /**
* Database class loader service.
*
* @var midcom_services_dbclassloader
@@ -895,7 +900,7 @@
$this->cache->content->no_cache();
$this->auth->logout($redirect_to);
// This will exit;
-
+
case "login":
// rest of URL used as redirect
$remaining_url = false;
@@ -933,6 +938,18 @@
$this->_showdebuglog($value);
break;
+ case 'servejscsscache':
+ //$name = $tmp[MIDCOM_HELPER_URLPARSER_VALUE];
+ $name = $this->_parser->argv[0];
+ if ( !$this->jscss
+ || !is_callable(array($this->jscss, 'serve')))
+ {
+ $_MIDCOM->generate_error(MIDCOM_ERRCRIT, 'Cache is not initialized');
+ // this will exit
+ }
+ $this->jscss->serve($name);
+ // this will exit()
+
default:
debug_add("Unknown MidCOM URL Property ignored: {$key} => {$value}", MIDCOM_LOG_WARN);
$_MIDCOM->generate_error(MIDCOM_ERRNOTFOUND, "This MidCOM URL method is unknown.");
@@ -2447,6 +2464,15 @@
*/
function add_jsfile($url, $prepend = false)
{
+ // use merger cache if possible
+ if ( $this->jscss
+ && is_callable(array($this->jscss, 'add_jsfile')))
+ {
+ if ($this->jscss->add_jsfile($url, $prepend))
+ {
+ return;
+ }
+ }
// Adds an URL for a <script type="text/javascript" src="tinymce.js"></script>
// like call. $url is inserted into src. Duplicates are omitted.
if (! in_array($url, $this->_jsfiles))
@@ -2614,6 +2640,15 @@
{
return false;
}
+ // use merger cache if possible
+ if ( $this->jscss
+ && is_callable(array($this->jscss, 'add_cssfile')))
+ {
+ if ($this->jscss->add_cssfile($attributes))
+ {
+ return;
+ }
+ }
// Register each URL only once
if (in_array($attributes['href'], $this->_linkhrefs))
@@ -2753,9 +2788,19 @@
}
echo $this->_link_head;
+ if ( $this->jscss
+ && is_callable(array($this->jscss, 'print_cssheaders')))
+ {
+ $this->jscss->print_cssheaders();
+ }
echo $this->_object_head;
echo $this->_style_head;
echo $this->_meta_head;
+ // if ( $this->jscss
+ // && is_callable(array($this->jscss, 'print_jsheaders')))
+ // {
+ // $this->jscss->print_jsheaders();
+ // }
foreach ($this->_jshead as $js_call)
{
echo $js_call;
Modified: trunk/midcom/midcom.core/midcom/config/midcom_config.php
==============================================================================
--- trunk/midcom/midcom.core/midcom/config/midcom_config.php (original)
+++ trunk/midcom/midcom.core/midcom/config/midcom_config.php Wed Oct 31 17:49:52 2007
@@ -70,7 +70,7 @@
* is nonzero) or non-sitegrouped mode if we are in SG0.
* - <b>int auth_login_form_httpcode</b>: HTTP return code used in MidCOM login screens,
* either 403 (403 Forbidden) or 200 (200 OK), defaulting to 403.
- * - <b>bool auth_openid_enable:</b> Whether to enable OpenID authentication handled with
+ * - <b>bool auth_openid_enable:</b> Whether to enable OpenID authentication handled with
* the net.nemein.openid library
*
* <b>Authentication Backend configuration: "simple"</b>
@@ -92,7 +92,7 @@
* file) is not changed.
* - <b>string cache_base_directory:</b> The directory where to place cache files for MidCOM.
* This defaults to /tmp/ (note the trailing slash) as this is writable everywhere.
- *
+ *
* - <b>Array cache_module_acl:</b> If this is non-null and an array, MidCOM will create a memcached
* caching instance to buffer ACL reads from the DB. You set this parameter to the configuration
* array to use (use an empty array for the defaults). See the memcached backend for details.
@@ -363,7 +363,7 @@
//Memory Caching Daemon
$GLOBALS['midcom_config_default']['cache_module_memcache_backend'] = null;
$GLOBALS['midcom_config_default']['cache_module_memcache_backend_config'] = Array();
-$GLOBALS['midcom_config_default']['cache_module_memcache_data_groups'] = Array('ACL', 'PARENT');
+$GLOBALS['midcom_config_default']['cache_module_memcache_data_groups'] = Array('ACL', 'PARENT', 'jscss_merged');
// Generated class cache directory
$GLOBALS['midcom_config_default']['cache_module_phpscripts_directory'] = 'phpscripts/';
Added: trunk/midcom/midcom.core/midcom/services/js_css_merger.php
==============================================================================
--- (empty file)
+++ trunk/midcom/midcom.core/midcom/services/js_css_merger.php Wed Oct 31 17:49:52 2007
@@ -0,0 +1,819 @@
+<?php
+/**
+ * @package midcom.services
+ * @author The Midgard Project, http://www.midgard-project.org
+ * @version $Id:tmp.php 3765 2006-07-31 08:51:39 +0000 (Mon, 31 Jul 2006) tarjei $
+ * @copyright The Midgard Project, http://www.midgard-project.org
+ * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License
+ */
+
+class midcom_services_js_css_merger extends midcom_baseclasses_core_object
+{
+ /**
+ * local document root of the website
+ *
+ * we try to read this from environment if possible
+ */
+ var $documentroot = false;
+
+ /**
+ * maximum time unaccessed time for cache_id before it's garbage collected
+ */
+ var $max_unaccessed = 7200;
+
+ /**
+ * array of callbacks to send merged CSS to before storing
+ *
+ * must take 3 arguments (the css content, uri path and local path) and return new value for content (or false in critical failure)
+ */
+ var $css_plugins = array
+ (
+ array ('midcom_services_js_css_merger', 'rewrite_url_references'),
+ array ('midcom_services_js_css_merger', 'remove_cstyle_comments'),
+ array ('midcom_services_js_css_merger', 'minimize_whitespace'),
+ );
+
+ /**
+ * array of callbacks to send merged JS to before storing
+ *
+ * must take 3 arguments (the JS content, uri path and local path) and return new value for content (or false in critical failure)
+ */
+ var $js_plugins = array
+ (
+ array ('midcom_services_js_css_merger', 'remove_cstyle_comments'),
+ );
+
+ /**
+ * Javascript files to merge, values are url paths
+ */
+ var $_jsfiles = array();
+
+ /**
+ * CSS files to merge, multi-dimensional first dimension is the 'media' property
+ */
+ var $_cssfiles = array();
+
+ /**
+ * Cache can_merge results here
+ */
+ var $_can_merge_cache = array();
+
+ /**
+ * Cache of local paths, keyed by given path
+ */
+ var $_resolved_paths = array();
+
+ var $_jsheaders_printed = false;
+ var $_cssheaders_printed = false;
+
+ /**
+ * Constructor, sets default value and test memcached
+ */
+ function midcom_services_js_css_merger()
+ {
+ parent::midcom_baseclasses_core_object();
+ $this->documentroot = @getenv('DOCUMENT_ROOT');
+ // We check this key later
+ $_MIDCOM->cache->memcache->put('jscss_merged', 'is_up', true);
+ }
+
+ function print_jsheaders()
+ {
+ debug_push_class(__CLASS__, __FUNCTION__);
+ debug_add('called');
+ if ($this->_jsheaders_printed)
+ {
+ $GLOBALS['midcom_debugger']->print_function_stack('subsequent call to print_jsheaders from');
+ debug_add('JS headers already printed', MIDCOM_LOG_ERROR);
+ debug_pop();
+ return false;
+ }
+ $this->_jsheaders_printed = true;
+ $paths =& $this->_jsfiles;
+ if (empty($paths))
+ {
+ debug_add('$this->_jsfiles is empty, returning early');
+ $GLOBALS['midcom_debugger']->print_function_stack('called from');
+ debug_pop();
+ return true;
+ }
+ $cache_id = $this->calculate_cache_id_and_merge($paths, 'js_merge');
+ if (empty($cache_id))
+ {
+ debug_add("Could not get cache id from calculate_cache_id_and_merge(\$paths, 'css_merge')", MIDCOM_LOG_ERROR);
+ debug_pop();
+ return false;
+ }
+
+ $url = "{$_MIDGARD['self']}midcom-servejscsscache-js/{$cache_id}.js";
+ echo '<script type="text/javascript" src="' . $url . '"></script>' . "\n";
+ $this->_jsheaders_printed = true;
+ debug_pop();
+ return true;
+ }
+
+ function remove_cstyle_comments(&$merged, $path, $local_path)
+ {
+ return preg_replace('%/\*.*?\*/%', '', $merged);
+ }
+
+ function minimize_whitespace(&$merged, $path, $local_path)
+ {
+ return preg_replace('/\s+/', ' ', $merged);
+ }
+
+ function rewrite_url_references(&$merged, $path, $local_path)
+ {
+ debug_push_class(__CLASS__, __FUNCTION__);
+ $path_dir = dirname($path);
+ $searches = array();
+ $replaces = array();
+ $regexp_url = "/url\s*\(([\"'´])?(((https?|ftp):\/\/)?(.*?))\\1?\)/i";
+ preg_match_all($regexp_url, $merged, $matches_url);
+ debug_print_r ('$matches_url: ', $matches_url);
+ $tmparr = array();
+ $tmparr['whole'] = $matches_url[0];
+ $tmparr['uri'] = $matches_url[2];
+ $tmparr['proto'] = $matches_url[3];
+ $tmparr['location'] = $matches_url[5];
+ foreach ($tmparr['whole'] as $k => $search)
+ {
+ if (!empty($tmparr['proto'][$k]))
+ {
+ // fully qualified uri, don't bother rewriting
+ continue;
+ }
+ if (substr($tmparr['location'][$k], 0, 1) == '/')
+ {
+ // uri relative from root, no need to rewrite
+ continue;
+ }
+ $replace = "url('{$path_dir}/{$tmparr['location'][$k]}')";
+ $searches[] = $search;
+ $replaces[] = $replace;
+ }
+ debug_print_r('$searches: ', $searches);
+ debug_print_r('$replaces: ', $replaces);
+ $merged = str_replace($searches, $replaces, $merged);
+ debug_pop();
+ return $merged;
+ }
+
+ function calculate_cache_id_and_merge(&$paths, $method)
+ {
+ debug_push_class(__CLASS__, __FUNCTION__);
+ debug_add("called, method={$method}");
+ if (!is_callable(array($this, $method)))
+ {
+ debug_add("\$this->$method() is not callable", MIDCOM_LOG_ERROR);
+ debug_pop();
+ return false;
+ }
+ $cache_id_and_mtimes = $this->_calculate_cache_id($paths);
+ if (!is_array($cache_id_and_mtimes))
+ {
+ debug_add('_calculate_cache_id did not return array', MIDCOM_LOG_ERROR);
+ debug_pop();
+ return false;
+ }
+ list ($cache_id, $mtimes) = $cache_id_and_mtimes;
+ /* TODO: Check for client no-cache headers to refresh
+ if ()
+ {
+ $this->remove($cache_id);
+ }
+ */
+ $cache_metadata = $_MIDCOM->cache->memcache->get('jscss_merged', 'cache_metadata');
+ if (!is_array($cache_metadata))
+ {
+ $cache_metadata = array();
+ }
+ if (!isset($cache_metadata[$cache_id]))
+ {
+ $merged = $this->$method($paths);
+ if ($merged === false)
+ {
+ debug_add("\$this->{$method}(\$paths) returned false, so do we", MIDCOM_LOG_ERROR);
+ debug_pop();
+ return false;
+ }
+ $this->store($cache_id, $merged);
+ }
+ debug_add("all done, returning '{$cache_id}'");
+ debug_pop();
+ return $cache_id;
+ }
+
+ /**
+ * Merges given uri paths
+ *
+ * @param array $paths array of uri paths, must been given via add_jsfile/add_cssfile previously
+ * @return string merged data or false on critical failure
+ */
+ function merge(&$paths, &$plugins)
+ {
+ debug_push_class(__CLASS__, __FUNCTION__);
+ $merged = '';
+ foreach ($paths as $path)
+ {
+ if ( !isset($this->_resolved_paths[$path])
+ || !is_readable($this->_resolved_paths[$path]))
+ {
+ // A path has not been resolved! FATAL !!
+ debug_add("Could path '{$path}' is not resolved", MIDCOM_LOG_ERROR);
+ debug_pop();
+ unset($merged);
+ return false;
+ }
+ $local_path =& $this->_resolved_paths[$path];
+ debug_add("Merging file '{$local_path}'");
+ $content = file_get_contents($local_path);
+ if (!$this->_call_plugins($plugins, $content, $path, $local_path))
+ {
+ return false;
+ }
+ $merged .= $content . "\n";
+ unset($content);
+ }
+ debug_add('calling generate_cache_id()');
+ $this->generate_cache_id($paths);
+ debug_pop();
+ return $merged;
+ }
+
+ /**
+ * Merges given uri paths and calls JS post-processing plugins
+ *
+ * @param array $paths array of uri paths, must been given via add_jsfile previously
+ * @return string merged data or false on critical failure
+ */
+ function js_merge(&$paths)
+ {
+ return $this->merge($paths, $this->js_plugins);
+ }
+
+ /**
+ * Merges given uri paths and calls JS post-processing plugins
+ *
+ * @param array $paths array of uri paths, must been given via add_cssfile previously
+ * @return string merged data or false on critical failure
+ */
+ function css_merge(&$paths)
+ {
+ return $this->merge($paths, $this->css_plugins);
+ }
+
+ function _call_plugins(&$plugins, &$merged, $path, $local_path)
+ {
+ if (!is_array($plugins))
+ {
+ return true;
+ }
+ foreach ($plugins as $callback)
+ {
+ if (!is_callable($callback))
+ {
+ continue;
+ }
+ $merged = call_user_func($callback, $merged, $path, $local_path);
+ if ($merged === false)
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ function print_cssheaders()
+ {
+ debug_push_class(__CLASS__, __FUNCTION__);
+ debug_add('called');
+ if ($this->_cssheaders_printed)
+ {
+ $GLOBALS['midcom_debugger']->print_function_stack('subsequent call to print_cssheaders from');
+ debug_add('CSS headers already printed', MIDCOM_LOG_ERROR);
+ debug_pop();
+ return false;
+ }
+ $this->_cssheaders_printed = true;
+ foreach ($this->_cssfiles as $media => $paths)
+ {
+ if (empty($paths))
+ {
+ continue;
+ }
+ $cache_id = $this->calculate_cache_id_and_merge($paths, 'css_merge');
+ if (empty($cache_id))
+ {
+ debug_add("Could not get cache id from calculate_cache_id_and_merge(\$paths, 'css_merge')", MIDCOM_LOG_ERROR);
+ debug_pop();
+ return false;
+ }
+ $url = "{$_MIDGARD['self']}midcom-servejscsscache-css/{$cache_id}.css";
+ echo "<link rel='stylesheet' type='text/css' media='{$media}' href='{$url}' />\n";
+ }
+
+ debug_pop();
+ return true;
+ }
+
+
+ /**
+ * Can we merge this path (practically: is it a local file referred sanely)
+ *
+ * @return boolean indicating state
+ */
+ function can_merge($path)
+ {
+ if (isset($this->_can_merge_cache[$path]))
+ {
+ return $this->_can_merge_cache[$path];
+ }
+ debug_push_class(__CLASS__, __FUNCTION__);
+ debug_add("called for path {$path}");
+
+ if (!$_MIDCOM->cache->memcache->get('jscss_merged', 'is_up'))
+ {
+ // We need working memcache to use this feature
+ debug_add('memcache seems not to be running');
+ debug_pop();
+ $this->_can_merge_cache[$path] = false;
+ return $this->_can_merge_cache[$path];
+ }
+ if (strpos($path, '?') !== false)
+ {
+ debug_add('path contains query string, cannot merge');
+ debug_pop();
+ return false;
+ }
+
+ // strip protocol://<server name> from beginning of path
+ $path = preg_replace("#^(.*)?://{$_SERVER['SERVER_NAME']}#", '', $path);
+ if (!strpos('/', $path) === 0)
+ {
+ // Does not start with single slash, ie does not refer to "local" resource
+ debug_add("Can't cache uri '{$path}'");
+ debug_pop();
+ $this->_can_merge_cache[$path] = false;
+ return $this->_can_merge_cache[$path];
+ }
+ $local_path = "{$this->documentroot}{$path}";
+ if (!is_readable($local_path))
+ {
+ debug_add("Mapped file '{$local_path}' is not readable");
+ debug_pop();
+ // We can't read the file (likely it does not exist but possibly a permissions issue)
+ $this->_can_merge_cache[$path] = false;
+ return $this->_can_merge_cache[$path];
+ }
+ // Local path resolved, cache the result and return true
+ debug_pop();
+ $this->_resolved_paths[$path] = $local_path;
+ $this->_can_merge_cache[$path] = true;
+ return $this->_can_merge_cache[$path];
+ }
+
+ /**
+ * add a jsfile to be merged
+ *
+ * You should check the path with can_merge() first
+ *
+ * @see midcom_application::add_jsfile
+ * @param string $url The URL to the file to-be referenced.
+ * @param boolean $prepend Whether to add the JS include to beginning of includes
+ * @return boolean indicating success/failure
+ */
+ function add_jsfile($path, $prepend = false)
+ {
+ debug_push_class(__CLASS__, __FUNCTION__);
+ debug_add("called for path '{$path}'");
+ debug_add('disabled untill we figure out a way to rewrite include calls (which may contain variable parts...)');
+ debug_pop();
+ return false;
+ if ($this->_jsheaders_printed)
+ {
+ $GLOBALS['midcom_debugger']->print_function_stack('call to add_jsfile after print_jsheaders from');
+ debug_add('JS headers already printed', MIDCOM_LOG_ERROR);
+ debug_pop();
+ return false;
+ }
+ if ( !is_string($path)
+ || empty($path))
+ {
+ debug_add("invalid path '{$path}', aborting early", MIDCOM_LOG_ERROR);
+ debug_pop();
+ return false;
+ }
+ if (in_array($path, $this->_jsfiles))
+ {
+ debug_add('already added, returnin true');
+ debug_pop();
+ return true;
+ }
+ if (!$this->can_merge($path))
+ {
+ debug_add("can_merge('{$path}') returned false, so fo we", MIDCOM_LOG_WARN);
+ debug_pop();
+ return false;
+ }
+ if ($prepend)
+ {
+ array_unshift($this->_jsfiles, $path);
+ }
+ else
+ {
+ $this->_jsfiles[] = $path;
+ }
+ debug_print_r('$this->_jsfiles now: ', $this->_jsfiles);
+ debug_pop();
+ return true;
+ }
+
+ /**
+ * add a CSS file to be merged
+ *
+ * You should check the path with can_merge() first.
+ * Constraints: 'type' attribute must be 'text/css' (or unset), 'rel' attribute
+ * must be 'stylesheet' (or unset), 'media' attribute will be honored if set, other
+ * attributes will be dropped.
+ *
+ * @see midcom_application::add_jsfile
+ * @param array $attributes Array of attribute => value pairs to be placed in the tag.
+ * @return boolean indicating success/failure
+ */
+ function add_cssfile($attributes = false, $prepend = false)
+ {
+ debug_push_class(__CLASS__, __FUNCTION__);
+ debug_print_r('called for attributes: ', $attributes);
+ if ($this->_cssheaders_printed)
+ {
+ $GLOBALS['midcom_debugger']->print_function_stack('call to add_cssfile after print_cssheaders from');
+ debug_add('CSS headers already printed', MIDCOM_LOG_ERROR);
+ debug_pop();
+ return false;
+ }
+ // Sanity checks
+ if (empty($attributes))
+ {
+ debug_add('attributes is empty, aborting', MIDCOM_LOG_ERROR);
+ debug_pop();
+ return false;
+ }
+
+ // Make sure we have usable path
+ if ( !isset($attributes['href'])
+ || empty($attributes['href']))
+ {
+ debug_add('no href set, aborting', MIDCOM_LOG_WARN);
+ debug_pop();
+ return false;
+ }
+ $path =& $attributes['href'];
+
+ // We only support text/css stylesheets
+ if ( isset($attributes['type'])
+ && $attributes['type'] !== 'text/css')
+ {
+ debug_add('invalid type set, aborting', MIDCOM_LOG_WARN);
+ debug_pop();
+ return false;
+ }
+ $attributes['type'] = 'text/css';
+
+ // We only support link rel="stylesheets"
+ if ( isset($attributes['rel'])
+ && $attributes['rel'] !== 'stylesheet')
+ {
+ debug_add('invalid rel set, aborting', MIDCOM_LOG_WARN);
+ debug_pop();
+ return false;
+ }
+ $attributes['rel'] = 'stylesheet';
+
+
+ if (isset($attributes['media']))
+ {
+ $media = $attributes['media'];
+ }
+ else
+ {
+ debug_add('media is not set, using "all"');
+ $media = 'all';
+ }
+
+ debug_add("using media '{$media}' array for futher checks");
+ if (!isset($this->_cssfiles[$media]))
+ {
+ $this->_cssfiles[$media] = array();
+ }
+ $files_per_media =& $this->_cssfiles[$media];
+
+ if (in_array($path, $files_per_media))
+ {
+ debug_add('already added, returning success');
+ debug_pop();
+ return true;
+ }
+ if (!$this->can_merge($path))
+ {
+ debug_add("can_merge('{$path}') returned false, so fo we", MIDCOM_LOG_WARN);
+ debug_pop();
+ return false;
+ }
+ if ($prepend)
+ {
+ array_unshift($files_per_media, $path);
+ }
+ else
+ {
+ $files_per_media[] = $path;
+ }
+ debug_print_r('$this->_cssfiles now: ', $this->_cssfiles);
+ debug_pop();
+ return true;
+ }
+
+ function _calculate_cache_id(&$paths)
+ {
+ debug_push_class(__CLASS__, __FUNCTION__);
+ if (empty($paths))
+ {
+ debug_add('$paths is empty, aborting', MIDCOM_LOG_ERROR);
+ debug_pop();
+ return false;
+ }
+ $cache_id_base = '';
+ $mtimes = array();
+ foreach ($paths as $path)
+ {
+ if ( !$this->can_merge($path)
+ || !isset($this->_resolved_paths[$path]))
+ {
+ // A path has not been resolved! FATAL !!
+ debug_add("path '{$path}' has not been resolved", MIDCOM_LOG_ERROR);
+ debug_pop();
+ return false;
+ }
+ $local_path =& $this->_resolved_paths[$path];
+ $stat = @stat($local_path);
+ if (!is_array($stat))
+ {
+ // FATAL: Could not stat file
+ debug_add("Could not stat '{$local_path}'", MIDCOM_LOG_ERROR);
+ debug_pop();
+ return false;
+ }
+ $last_modified =& $stat[9];
+ if (empty($last_modified))
+ {
+ // FATAL: Could not read last-modified
+ debug_add("last_modified is empty ('{$last_modified}')", MIDCOM_LOG_ERROR);
+ debug_pop();
+ return false;
+ }
+ $cache_id_base .= "{$local_path}:{$last_modified},";
+ $mtimes[$local_path] = $last_modified;
+ }
+ $cache_id = md5($cache_id_base);
+ $ret = array($cache_id, $mtimes);
+ debug_print_r('done, returning: ', $ret);
+ return $ret;
+ }
+
+ function generate_cache_id(&$paths)
+ {
+ debug_push_class(__CLASS__, __FUNCTION__);
+ if (!$_MIDCOM->cache->memcache->get('jscss_merged', 'is_up'))
+ {
+ // memcache is not up
+ debug_add('memcache seems not to be running', MIDCOM_LOG_ERROR);
+ debug_pop();
+ return false;
+ }
+ $cache_metadata = $_MIDCOM->cache->memcache->get('jscss_merged', 'cache_metadata');
+ if (!is_array($cache_metadata))
+ {
+ $cache_metadata = array();
+ }
+
+ $cache_id_and_mtimes = $this->_calculate_cache_id($paths);
+ if (!is_array($cache_id_and_mtimes))
+ {
+ debug_add('Could not calculate cache_id', MIDCOM_LOG_ERROR);
+ debug_pop();
+ return false;
+ }
+ list ($cache_id, $mtimes) = $cache_id_and_mtimes;
+
+ if (!isset($cache_metadata[$cache_id]))
+ {
+ $cache_metadata[$cache_id] = array
+ (
+ 'generated' => time(),
+ 'mtimes' => $mtimes,
+ 'last_access' => time(),
+ );
+ debug_print_r("adding metadata: ", $cache_metadata[$cache_id]);
+ $_MIDCOM->cache->memcache->put('jscss_merged', 'cache_metadata', $cache_metadata);
+ }
+ debug_pop();
+ return $cache_id;
+ }
+
+ /**
+ * Store data to memcache with given cache id
+ *
+ * @param string $cache_id key to use in cache, must be generated with generate_cache_id
+ * @param string $data data to store in cache
+ * @see $this->generate_cache_id
+ */
+ function store($cache_id, $data)
+ {
+ return $_MIDCOM->cache->memcache->put('jscss_merged', $cache_id . '@jscss', $data);
+ }
+
+ /**
+ * get a cached_id from the cache
+ *
+ * @param string $cache_id cache id to get
+ * @return string value or false on critical failure
+ */
+ function get($cache_id)
+ {
+ // Update access time
+ $cache_metadata = $_MIDCOM->cache->memcache->get('jscss_merged', 'cache_metadata');
+ if (!isset($cache_metadata[$cache_id]))
+ {
+ // We do not have metadata for this key
+ $this->remove($cache_id);
+ return false;
+ }
+ // update last accessed time
+ $cache_metadata[$cache_id]['last_access'] = time();
+ $_MIDCOM->cache->memcache->put('jscss_merged', 'cache_metadata', $cache_metadata);
+
+ // return value
+ return $_MIDCOM->cache->memcache->get('jscss_merged', $cache_id . '@jscss');
+ }
+
+ /**
+ * go through all keys we have metadata for and check that they are still fresh
+ */
+ function garbage_collect()
+ {
+ $cache_metadata = $_MIDCOM->cache->memcache->get('jscss_merged', 'cache_metadata');
+ if (!is_array($cache_metadata))
+ {
+ // Could not read metadata array, is memcache running ??
+ return false;
+ }
+ foreach ($cache_metadata as $cache_id => $metadata)
+ {
+ if ( !isset($metadata['mtimes'])
+ || !is_array($metadata['mtimes']))
+ {
+ $this->remove($cache_id);
+ continue;
+ }
+ foreach ($metadata['mtimes'] as $local_path => $timestamp)
+ {
+ if (!is_readable($local_path))
+ {
+ $this->remove($cache_id);
+ continue 2;
+ }
+ $stat = stat($local_path);
+ if (!is_array($stat))
+ {
+ $this->remove($cache_id);
+ continue 2;
+ }
+ $last_modified =& $stat[9];
+ if ($last_modified != $timestamp)
+ {
+ $this->remove($cache_id);
+ continue 2;
+ }
+ }
+ // safety
+ if (!isset($metadata['last_access']))
+ {
+ $metadata['last_access'] = 0;
+ }
+ // remove stale cache_ids
+ $unaccessed = time() - $metadata['last_access'];
+ if ($unaccessed > $max_unaccessed)
+ {
+ $this->remove($cache_id);
+ continue;
+ }
+ }
+ }
+
+ /**
+ * remove a specific key from the cache
+ *
+ * @param string $cache_id cache key to remove
+ */
+ function remove($cache_id)
+ {
+ $cache_metadata = $_MIDCOM->cache->memcache->get('jscss_merged', 'cache_metadata');
+ if (isset($cache_metadata[$cache_id]))
+ {
+ // remove from metadata if set
+ unset($cache_metadata[$cache_id]);
+ $_MIDCOM->cache->memcache->put('jscss_merged', 'cache_metadata', $cache_metadata);
+ }
+ return $_MIDCOM->cache->memcache->invalidate($cache_id . '@jscss');
+ }
+
+ /**
+ * Serve a "file" from the cache
+ *
+ * @param string $name must be of format <cached_id>.<js|css>
+ */
+ function serve($name)
+ {
+ if (!$_MIDCOM->cache->memcache->get('jscss_merged', 'is_up'))
+ {
+ $_MIDCOM->generate_error(MIDCOM_ERRCRIT, 'Cache is not running');
+ // this will exit()
+ }
+ list ($cache_id, $extension) = explode('.', $name, 2);
+ switch (strtolower($extension))
+ {
+ case 'css':
+ $mimetype = 'text/css';
+ break;
+ case 'js':
+ $mimetype = 'application/javascript';
+ break;
+ default:
+ $_MIDCOM->generate_error(MIDCOM_ERRNOTFOUND, "Don't know what to do with extension '{$extension}'");
+ // this will exit()
+ }
+ $cache_metadata = $_MIDCOM->cache->memcache->get('jscss_merged', 'cache_metadata');
+ if (!isset($cache_metadata[$cache_id]))
+ {
+ // We do not have metadata for this key
+ $this->remove($cache_id);
+ $_MIDCOM->generate_error(MIDCOM_ERRNOTFOUND, "Metadata for key '{$cache_id}' not found in cache");
+ // this will exit()
+ }
+ $last_modified =& $cache_metadata[$cache_id]['generated'];
+ debug_push_class(__CLASS__, __FUNCTION__);
+ if ($_MIDCOM->cache->content->_check_not_modified($last_modified, $cache_id))
+ {
+ debug_add('_check_not_modified returned true, finishing up here then');
+ if (!headers_sent())
+ {
+ debug_add('For the weirdest reason headers have not been sent, send again');
+ // Doublecheck
+ $_MIDCOM->header('HTTP/1.0 304 Not Modified', 304);
+ $_MIDCOM->header("ETag: {$cache_id}");
+ $_MIDCOM->header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 315360000) . ' GMT');
+ $_MIDCOM->header('Cache-Control: public max-age=315360000');
+ $_MIDCOM->header('Pragma: public');
+ }
+ while(@ob_end_flush());
+ debug_pop();
+ exit();
+ }
+
+ $data = $this->get($cache_id);
+ if (empty($data))
+ {
+ debug_pop();
+ $_MIDCOM->generate_error(MIDCOM_ERRNOTFOUND, "Key '{$cache_id}' not found in cache");
+ // this will exit()
+ }
+
+ $_MIDCOM->header("ETag: {$cache_id}");
+ $_MIDCOM->cache->content->content_type($mimetype);
+ $_MIDCOM->header("Content-Type: {$mimetype}");
+ $_MIDCOM->header('Last-Modified: ' . gmdate("D, d M Y H:i:s", $last_modified) . ' GMT');
+ $_MIDCOM->header('Content-Length: ' . strlen($data));
+ // PONDER: Support ranges ("continue download") somehow ?
+ $_MIDCOM->header('Accept-Ranges: none');
+
+ /* We want to cache these so lets override some things
+ $_MIDCOM->cache->content->cache_control_headers();
+ */
+ $_MIDCOM->header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 315360000) . ' GMT');
+ $_MIDCOM->header('Cache-Control: public max-age=315360000');
+ $_MIDCOM->header('Pragma: public');
+ while(@ob_end_flush());
+
+ echo $data;
+ unset($data, $mimetype, $last_modified);
+
+ debug_add('data sent, exit()ing so nothing has a chance the mess things up anymore');
+ debug_pop();
+ exit();
+ }
+}
+
+// Instantiate the server
+$_MIDCOM->jscss = new midcom_services_js_css_merger();
+
+?>
\ No newline at end of file
More information about the midcom-commits
mailing list