Perforce Chronicle 2012.2/486814
API Documentation
|
Aggregating version of HeadLink helper. More...
Public Member Functions | |
__construct () | |
Extend parent constructor to add 'id' to valid item keys. | |
getAssetHandler () | |
Get the asset handler used to store the aggregated css file(s). | |
getDocumentRoot () | |
Get the file-system path to the document root. | |
getQualifiedBaseUrl () | |
Retrieves the qualified base url. | |
setAggregateCss ($aggregate) | |
Enable or disable css aggregation. | |
setAssetHandler (P4Cms_AssetHandlerInterface $handler=null) | |
Set the asset handler used to store the aggregated css file(s). | |
setDocumentRoot ($path) | |
Set the file-system path to the document root. | |
setIncludeTheme ($includeTheme) | |
Enable or disable inclusion of theme links when rendering. | |
toString ($indent=null) | |
Extend toString to aggregate css files where possible and ensure that theme links appear last, for CSS precedence. | |
Protected Member Functions | |
_aggregateCss () | |
Aggregate local, unconditional css files by media type. | |
_canGzipCompress () | |
Check if this PHP can generate gzip compressed data. | |
_clientAcceptsGzip () | |
Check if the client can accept gzip encoded content. | |
_consolidateForInternetExplorer () | |
Internet Explorer limits the number of CSS files that can be linked to 32. | |
_isDuplicateStylesheet ($uri) | |
Is the linked stylesheet a duplicate? Extended to protect against empty 'rel' property. | |
_minifyCss ($content) | |
Minify CSS Strips comments and unnecessary whitespace. | |
_resolveCssUrls ($content, $file) | |
Resolve relative URLs in the given css content against the document root. | |
_stripCharset ($content) | |
Remove declarations from file for standards compliance (only one such declaration may appear in a stylesheet and it must be first line - aggregation breaks these rules). | |
Protected Attributes | |
$_aggregate = false | |
$_aggregated = false | |
$_assetHandler = null | |
$_documentRoot = null | |
$_includeTheme = true |
Aggregating version of HeadLink helper.
Combines css files (resolving relative urls). Ensures that theme links come last.
P4Cms_View_Helper_HeadLink::__construct | ( | ) |
Extend parent constructor to add 'id' to valid item keys.
{ parent::__construct(); $this->_itemKeys[] = "id"; }
P4Cms_View_Helper_HeadLink::_aggregateCss | ( | ) | [protected] |
Aggregate local, unconditional css files by media type.
Resolves relative urls and concatenates css files into build files. Rebuilds whenever a file changes.
{ // bail out if no asset handler is configured if (!$this->getAssetHandler()) { P4Cms_Log::log( "Failed to aggregate CSS. Asset Handler is unset.", P4Cms_Log::ERR ); return; } // bail out if document root is unset. if (!$this->getDocumentRoot()) { P4Cms_Log::log( "Failed to aggregate CSS. Document root is unset.", P4Cms_Log::ERR ); return; } // group css files by media. $ignore = array(); $groups = array(); foreach ($this as $item) { // only aggregate CSS links that are local. if ($item->type !== 'text/css' || P4Cms_Uri::hasScheme($item->href)) { $ignore[] = $item; continue; } // ignore if file does not exist. $file = $this->getDocumentRoot() . $item->href; if (!file_exists($file)) { $ignore[] = $item; continue; } // group files by media, build group, and conditional files. // build groups are named collections of css files that should be // aggregated together $parts = array($item->media); if (isset($item->extras['buildGroup'])) { $parts[] = $item->extras['buildGroup']; } $parts[] = $item->conditionalStylesheet; $name = join('-', $parts); $name = preg_replace('/[^a-zA-Z0-9 .-]/', '', $name); $name = str_replace(' ', '-', $name); if (!isset($groups[$name])) { $groups[$name] = array( 'time' => 0, 'media' => $item->media, 'files' => array(), 'conditional' => $item->conditionalStylesheet ); } // add link to the group. $time = filemtime($file); $groups[$name]['files'][] = $file; $groups[$name]['time'] = $time < $groups[$name]['time'] ? $groups[$name]['time'] : $time; } // determine if compression should be enabled $compressed = $this->_canGzipCompress() && $this->_clientAcceptsGzip(); // process build groups. $styles = array(); foreach ($groups as $name => $group) { // generate build filename. $buildFile = $name . "-" . md5(implode(',', $group['files']) . $group['time']) . ($compressed ? '.cssgz' : '.css'); // rebuild if file does not exist. if (!$this->getAssetHandler()->exists($buildFile)) { $css = ""; foreach ($group['files'] as $file) { $content = file_get_contents($file); $content = $this->_stripCharset($content); $content = $this->_minifyCss($content); $content = $this->_resolveCssUrls($content, $file); $css .= $content; } // also compress if possible. if ($compressed) { $css = gzencode($css, 9); } // write out aggregate file - if any fail, abort aggregation. if (!$this->getAssetHandler()->put($buildFile, $css)) { return; } } // we just capture the stylesheet at this point as we want // to cleanly abort aggregation if any stylesheets fail. $styles[] = array( 'uri' => $this->getAssetHandler()->uri($buildFile), 'media' => $group['media'], 'conditional' => $group['conditional'] ); } // if we made it this far aggregation worked; update the // list to only contain ignored and aggregated links. $this->getContainer()->exchangeArray($ignore); foreach ($styles as $style) { $this->appendStylesheet($style['uri'], $style['media'], $style['conditional']); } $this->_aggregated = true; }
P4Cms_View_Helper_HeadLink::_canGzipCompress | ( | ) | [protected] |
Check if this PHP can generate gzip compressed data.
{ return function_exists('gzencode'); }
P4Cms_View_Helper_HeadLink::_clientAcceptsGzip | ( | ) | [protected] |
Check if the client can accept gzip encoded content.
{ $front = Zend_Controller_Front::getInstance(); $request = $front->getRequest(); $accepts = isset($request) ? $request->getHeader('Accept-Encoding') : ''; return strpos($accepts, 'gzip') !== false; }
P4Cms_View_Helper_HeadLink::_consolidateForInternetExplorer | ( | ) | [protected] |
Internet Explorer limits the number of CSS files that can be linked to 32.
See the MSDN blog post: http://blogs.msdn.com/b/ieinternals/archive/2011/05/14/10164546.aspx It is possible to work around this limitation by using the CSS pragma.
This method emits one or more style blocks containing sufficient directives to ensure that all styles get loaded. However, nothing is done if the CSS has already been aggregated or the requesting browser does not appear to be MSIE.
{ // refuse to do anything for non-MSIE browsers $userAgent = array_key_exists('HTTP_USER_AGENT', $_SERVER) ? $_SERVER['HTTP_USER_AGENT'] : ''; if (!preg_match('/^Mozilla.+MSIE ([0-9]+[\.0-9]*)/', $userAgent)) { return ''; } // group css files by media. $notConsolidated = array(); $groups = array(); foreach ($this as $item) { // only consolidate CSS. if ($item->type !== 'text/css') { $notConsolidated[] = $item; continue; } // group files by media and conditional stylesheet $name = !empty($item->conditionalStylesheet) ? $item->media . '-' . $item->conditionalStylesheet : $item->media; $name = preg_replace('/[^a-zA-Z0-9 .-]/', '', $name); if (!isset($groups[$name])) { $groups[$name] = array( 'urls' => array(), 'attributes' => array( 'media' => $item->media, 'conditional' => $item->conditionalStylesheet ) ); } $groups[$name]['urls'][] = $item->href; } // only keep ignored links in the list. $this->getContainer()->exchangeArray($notConsolidated); // process media groups and construct headStyle content featuring @import statements. // we clone the headLink helper, give it a fresh container, and render its output here // to avoid potential issues with/without headStyle execution elsewhere. $headStyle = clone $this->view->getHelper('headStyle'); $headStyle->setContainer(new Zend_View_Helper_Placeholder_Container); foreach ($groups as $name => $group) { // batch stylesheets up to 31 at a time to meet MSIE limitations. while (count($group['urls'])) { $batch = array_splice($group['urls'], 0, 31); $styles = "@import url('" . join("');\n@import url('", $batch) ."');"; $headStyle->appendStyle($styles, $group['attributes']); } } return $headStyle->toString(); }
P4Cms_View_Helper_HeadLink::_isDuplicateStylesheet | ( | $ | uri | ) | [protected] |
Is the linked stylesheet a duplicate? Extended to protect against empty 'rel' property.
string | $uri | Style sheet |
{ foreach ($this->getContainer() as $item) { if (isset($item->rel) && ($item->rel == 'stylesheet') && ($item->href == $uri)) { return true; } } return false; }
P4Cms_View_Helper_HeadLink::_minifyCss | ( | $ | content | ) | [protected] |
Minify CSS Strips comments and unnecessary whitespace.
string | $content | the css to minify. |
{ // strip comments. $content = preg_replace('#/\*.*\*/#Us', '', $content); // strip needless whitespace. $content = preg_replace('#\s*([\s{},:;])\s*#s', '\\1', $content); return $content; }
P4Cms_View_Helper_HeadLink::_resolveCssUrls | ( | $ | content, |
$ | file | ||
) | [protected] |
Resolve relative URLs in the given css content against the document root.
This ensures the links continue to work post aggregation.
string | $content | the css to resolve links in. |
string | $file | the original location of the css file. |
{ $baseUrl = $this->getAssetHandler()->isOffsite() ? $this->getQualifiedBaseUrl() : ''; $basePath = $baseUrl . str_replace($this->getDocumentRoot(), '', dirname($file)); return preg_replace_callback( '/url\([\'"]?([^\'")]+)[\'"]?\)/i', function ($matches) use ($baseUrl, $basePath) { // if it is a full url with schema just return as-is if (P4Cms_Uri::hasScheme($matches[1])) { return $matches[0]; } // if it isn't relative, but is lacking a schema, glue in the baseUrl if (!P4Cms_Uri::isRelativeUri($matches[1])) { return "url('" . $baseUrl . "/" . $matches[1] . "')"; } // if it's relative, glue in base path (which includes baseUrl) return "url('" . $basePath . "/" . $matches[1] . "')"; }, $content ); }
P4Cms_View_Helper_HeadLink::_stripCharset | ( | $ | content | ) | [protected] |
Remove declarations from file for standards compliance (only one such declaration may appear in a stylesheet and it must be first line - aggregation breaks these rules).
string | $content | the css to strip 's from. |
{ return preg_replace( '/^@charset\s+[\'"](\S*)\b[\'"];/i', '', $content ); }
P4Cms_View_Helper_HeadLink::getAssetHandler | ( | ) |
Get the asset handler used to store the aggregated css file(s).
{
return $this->_assetHandler;
}
P4Cms_View_Helper_HeadLink::getDocumentRoot | ( | ) |
Get the file-system path to the document root.
{
return $this->_documentRoot;
}
P4Cms_View_Helper_HeadLink::getQualifiedBaseUrl | ( | ) |
Retrieves the qualified base url.
{ $request = Zend_Controller_Front::getInstance()->getRequest(); if (!$request instanceof Zend_Controller_Request_Http) { throw new Zend_View_Exception( "Cannot assemble qualified base URL - not an http request." ); } return $request->getScheme() . "://" . $request->getHttpHost() . $request->getBaseUrl(); }
P4Cms_View_Helper_HeadLink::setAggregateCss | ( | $ | aggregate | ) |
Enable or disable css aggregation.
bool | $aggregate | set to true to enable, false to disable. |
{
$this->_aggregate = (bool) $aggregate;
return $this;
}
P4Cms_View_Helper_HeadLink::setAssetHandler | ( | P4Cms_AssetHandlerInterface $ | handler = null | ) |
Set the asset handler used to store the aggregated css file(s).
P4Cms_AssetHandlerInterface | null | $handler | The handler to use or null |
{
$this->_assetHandler = $handler;
return $this;
}
P4Cms_View_Helper_HeadLink::setDocumentRoot | ( | $ | path | ) |
Set the file-system path to the document root.
string | $path | the location of the public folder. |
{ $this->_documentRoot = rtrim($path, '/\\'); return $this; }
P4Cms_View_Helper_HeadLink::setIncludeTheme | ( | $ | includeTheme | ) |
Enable or disable inclusion of theme links when rendering.
In some display contexts it might be desirable to disable theme stylesheets so that they don't influence presentation.
bool | $includeTheme | true to include theme links (the default) false to exclude them |
{
$this->_includeTheme = (bool) $includeTheme;
return $this;
}
P4Cms_View_Helper_HeadLink::toString | ( | $ | indent = null | ) |
Extend toString to aggregate css files where possible and ensure that theme links appear last, for CSS precedence.
string | int | $indent | Zend provides no documentation for this param. |
{ $themePath = P4Cms_Theme::fetchActive()->getBaseUrl(); $items = array(); $themes = array(); foreach ($this as $item) { if (strpos($item->href, $themePath) === false) { $items[] = $item; } else if ($this->_includeTheme) { $themes[] = $item; } } $this->getContainer()->exchangeArray(array_merge($items, $themes)); // aggregate css files (but only once). if ($this->_aggregate && !$this->_aggregated) { $this->_aggregateCss(); } // when aggregation is not enabled, consolidate MSIE stylesheets which limits CSS links to 32. $consolidated = ''; if (!$this->_aggregated) { $consolidated = $this->_consolidateForInternetExplorer(); } return $consolidated . parent::toString(); }
P4Cms_View_Helper_HeadLink::$_aggregate = false [protected] |
P4Cms_View_Helper_HeadLink::$_aggregated = false [protected] |
P4Cms_View_Helper_HeadLink::$_assetHandler = null [protected] |
P4Cms_View_Helper_HeadLink::$_documentRoot = null [protected] |
P4Cms_View_Helper_HeadLink::$_includeTheme = true [protected] |