Commit 62f621c1 authored by Luke√'s avatar Luke√

Create module Onlinebiz_Optimize

parents
<?php
class Onlinebiz_Optimize_Block_Adminhtml_Config_Source_Heading
extends Mage_Adminhtml_Block_Abstract implements Varien_Data_Form_Element_Renderer_Interface
{
/**
* Render element html
*
* @param Varien_Data_Form_Element_Abstract $element
* @return string
*/
public function render(Varien_Data_Form_Element_Abstract $element)
{
$useContainerId = $element->getData('use_container_id');
return sprintf('<tr class="system-fieldset-sub-head" id="row_%s"><td colspan="5"><h4>%s</h4></td></tr>',
$element->getHtmlId(), $element->getLabel()
);
}
}
\ No newline at end of file
<?php
/**
* Diglin
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://opensource.org/licenses/osl-3.0.php
*
*
* @category Diglin
* @package Onlinebiz_Optimize
* @copyright Copyright (c) 2011-2013 Diglin (http://www.onlinebiz.com)
* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
*/
class Onlinebiz_Optimize_Block_Adminhtml_Config_Source_Hint
extends Mage_Adminhtml_Block_Abstract
implements Varien_Data_Form_Element_Renderer_Interface
{
/**
* Render fieldset html
*
* @param Varien_Data_Form_Element_Abstract $element
* @return string
*/
public function render(Varien_Data_Form_Element_Abstract $element)
{
$version = Mage::getConfig()->getModuleConfig('Onlinebiz_Optimize')->version;
$block = $this->getLayout()->createBlock('adminhtml/widget_button')
->setData(array(
'label' => Mage::helper('adminhtml')->__('Flush JavaScript/CSS Cache'),
'onclick' => "setLocation('" . $this->getUrl('*/cache/cleanMedia') ."');",
));
return $block->toHtml() .' ('. $this->__('You will be redirected to the Cache Management page with a successful message') .')</p>';
}
}
\ No newline at end of file
<?php
/**
* Diglin
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://opensource.org/licenses/osl-3.0.php
*
*
* @category Diglin
* @package Onlinebiz_Optimize
* @copyright Copyright (c) 2011-2017 Diglin (http://www.onlinebiz.com)
* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
*/
//set_include_path(BP . DS . 'lib' . DS . 'MatthiasMullie'. DS. 'Minify' . PS . get_include_path());
//require_once 'MatthiasMullie' . DS . 'Minify' . DS . 'JS.php';
//require_once 'MatthiasMullie' . DS . 'Minify' . DS . 'CSS.php';
use MatthiasMullie\Minify\CSS;
use MatthiasMullie\Minify\JS;
class Onlinebiz_Optimize_Block_Optimize_Head extends Onlinebiz_Optimize_Block_Optimize_Head_Abstract
{
/**
* Get HEAD HTML with CSS/JS/RSS definitions
* (actually it also renders other elements, TODO: fix it up or rename this method)
*
* @return string
*/
public function getCssJsHtml()
{
// separate items by types
$lines = array();
foreach ($this->_data['items'] as $item) {
if (!is_null($item['cond']) && !$this->getData($item['cond']) || !isset($item['name'])) {
continue;
}
$if = !empty($item['if']) ? $item['if'] : '';
$params = !empty($item['params']) ? $item['params'] : '';
switch ($item['type']) {
case 'js': // js/*.js
case 'skin_js': // skin/*/*.js
case 'js_css': // js/*.css
case 'skin_css': // skin/*/*.css
$lines[$if][$item['type']][$params][$item['name']] = $item['name'];
break;
default:
$this->_separateOtherHtmlHeadElements($lines, $if, $item['type'], $params, $item['name'], $item);
break;
}
}
// prepare HTML
$shouldMergeJs = Mage::getStoreConfigFlag('dev/js/merge_files');
$shouldMergeCss = Mage::getStoreConfigFlag('dev/css/merge_css_files');
$html = '';
foreach ($lines as $if => $items) {
if (empty($items)) {
continue;
}
if (!empty($if)) {
// open !IE conditional using raw value
if (strpos($if, "><!-->") !== false) {
$html .= $if . "\n";
} else {
$html .= '<!--[if ' . $if . ']>' . "\n";
}
}
$cssPreload = $this->_helper()->getGeneralConfig('css_preload');
// static and skin css
$html .= $this->_prepareStaticAndSkinElements('<link rel="%s" type="text/css" href="%s"%s />' . "\n",
empty($items['js_css']) ? array() : $items['js_css'],
empty($items['skin_css']) ? array() : $items['skin_css'],
$shouldMergeCss ? array(Mage::getDesign(), 'getMergedCssUrl') : null
);
// static and skin javascripts
$html .= $this->_prepareStaticAndSkinElements('<script type="text/javascript"%s src="%s"%s></script>' . "\n",
empty($items['js']) ? array() : $items['js'],
empty($items['skin_js']) ? array() : $items['skin_js'],
$shouldMergeJs ? array(Mage::getDesign(), 'getMergedJsUrl') : null
);
// other stuff
if (!empty($items['other'])) {
$html .= $this->_prepareOtherHtmlHeadElements($items['other']) . "\n";
}
if (!empty($if)) {
// close !IE conditional comments correctly
if (strpos($if, "><!-->") !== false) {
$html .= '<!--<![endif]-->' . "\n";
} else {
$html .= '<![endif]-->' . "\n";
}
}
}
if($cssPreload){
$html .= '<script type="text/javascript">' . Onlinebiz_Optimize_Model_Css_Observer::LOADCSS. '</script>';
}
return $html;
}
/**
* Merge static and skin files of the same format into a set of HEAD directives or even into a directive
*
* Will attempt to merge into 1 directive, if merging callback is provided. In this case it will generate
* filenames, rather than render urls.
* The merger callback is responsible for checking whether files exist, merging them and giving result URL
*
* @param string $format - HTML element format for sprintf('<element src="%s"%s />', $src, $params)
* @param array $staticItems - array of relative names of static items to be grabbed from js/ folder
* @param array $skinItems - array of relative names of skin items to be found in skins according to design config
* @param callback $mergeCallback
* @return string
*/
protected function &_prepareStaticAndSkinElements($format, array $staticItems, array $skinItems, $mergeCallback = null)
{
$designPackage = Mage::getDesign();
$baseJsUrl = Mage::getBaseUrl('js');
$items = array();
if ($mergeCallback && !is_callable($mergeCallback)) {
$mergeCallback = null;
}
// get static files from the js folder, no need in lookups
foreach ($staticItems as $params => $rows) {
foreach ($rows as $name) {
if (strpos($name, '.css') !== false && $this->_helper()->isUseCompression()) {
$items[$params][] = $this->_prepareCssCompression($name, $mergeCallback, true);
} else if (strpos($name, '.js') !== false && $this->_helper()->isUseCompression('js')) {
$items[$params][] = $this->_prepareJsCompression($name, $mergeCallback, true);
} else {
$items[$params][] = $mergeCallback ? Mage::getBaseDir() . DS . 'js' . DS . $name : $baseJsUrl . $name;
}
}
}
// lookup each file basing on current theme configuration
foreach ($skinItems as $params => $rows) {
foreach ($rows as $name) {
if (strpos($name, '.css') !== false && $this->_helper()->isUseCompression()) {
if ($result = $this->_prepareCssCompression($name, $mergeCallback))
$items[$params][] = $result;
} else if (strpos($name, '.js') !== false && $this->_helper()->isUseCompression('js')) {
if ($result = $this->_prepareJsCompression($name, $mergeCallback)) ;
$items[$params][] = $result;
} else {
$items[$params][] = $mergeCallback ? $designPackage->getFilename($name, array('_type' => 'skin'))
: $designPackage->getSkinUrl($name, array());
}
}
}
$html = '';
// Change postion for cirtical files;
if (isset($items['cirtical']) && !empty($items['cirtical'])){
$criticalCss = array();
$criticalCss['cirtical'] = $items['cirtical'];
unset($items['cirtical']);
$items = array_merge($criticalCss, $items);
}
foreach ($items as $params => $rows) {
// attempt to merge
$mergedUrl = false;
if ($mergeCallback) {
$mergedUrl = call_user_func($mergeCallback, $rows);
}
// render elements
$params = trim($params);
$params = $params ? ' ' . $params : '';
$usePreload = $this->_helper()->getGeneralConfig('css_preload');
$isCss = (strpos($format, 'type="text/css"') !== false) ? true : false;
$cssRel = $usePreload ? 'preload' : 'stylesheet';
if (!$isCss){
$cssRel = '';
}
$preload = ($usePreload && $isCss) ? ' as="style" onload="this.onload=null;this.rel=\'stylesheet\'"' : '';
$includeCss = $this->_helper()->getGeneralConfig('critical');
if ($mergedUrl) {
if ($isCss && $includeCss && trim($params) == 'cirtical' && !$this->_helper()->isUrlsBlacklist()) {
$filename = Zend_Uri::factory($mergedUrl)->getPath();
$html .= $this->getContentCirticalStyle($filename);
if (!$html) {
$html .= sprintf($format, $cssRel, $mergedUrl, $params . $preload);
if ($usePreload){
$cssRel = 'stylesheet';
$html .= '<noscript>' . str_replace( PHP_EOL,'', sprintf($format, $cssRel, $mergedUrl, $params)) .'</noscript>' ."\n";
}
}
} else {
$html .= sprintf($format, $cssRel, $mergedUrl, $params . $preload);
if ($usePreload && $isCss){
$cssRel = 'stylesheet';
$html .= '<noscript>' . str_replace( PHP_EOL,'', sprintf($format, $cssRel, $mergedUrl, $params)) .'</noscript>' ."\n";
}
}
} else {
foreach ($rows as $src) {
if ($isCss && $includeCss && trim($params) == 'cirtical' && !$this->_helper()->isUrlsBlacklist()) {
$filename = Zend_Uri::factory($src)->getPath();
$html .= $this->getContentCirticalStyle($filename);
if (!$html) {
if ($usePreload){
$cssRel = 'stylesheet';
$html .= '<noscript>' . str_replace( PHP_EOL,'', sprintf($format, $cssRel, $src, $params)) .'</noscript>' ."\n";
}
}
} else {
$html .= sprintf($format, $cssRel, $src, $params . $preload);
if ($usePreload && $isCss){
$cssRel = 'stylesheet';
$html .= '<noscript>' . str_replace( PHP_EOL,'', sprintf($format, $cssRel, $src, $params)) .'</noscript>' ."\n";
}
}
}
}
}
return $html;
}
protected function _helper()
{
return Mage::helper('onlinebiz_optimize');
}
/**
* Compress CSS files and write them to media/css folders
*
* @param string $name
* @param string|array $mergeCallback
* @param bool $static
* @return string
*/
protected function _prepareCssCompression($name, $mergeCallback = null, $static = false)
{
$info = $this->_helper()->getCompressedInfo($name, 'css', $static);
if (!is_array($info))
return;
if (!isset($info['result']) || $info['result'] != false) {
$info['result'] = true;
}
if (!file_exists($info['targetPathFile']) && $info['result'] ||
$info['result'] && filemtime($info['orgskin_path']) > filemtime($info['targetPathFile'])) {
$minifyCSS = new CSS($info['orgskin_path']);
$info['result'] = $minifyCSS->minify($info['targetPathFile']);
};
return $this->_helper()->getResultPath($info, $mergeCallback);
}
/**
* Compress JS files and write them to media/js folders
*
* @param string $name
* @param string|array $mergeCallback
* @param bool $static
* @return string
*/
protected function _prepareJsCompression($name, $mergeCallback = null, $static = false)
{
$info = $this->_helper()->getCompressedInfo($name, 'js', $static);
if (!isset($info['result']) || $info['result'] != false) {
$info['result'] = true;
}
if (!file_exists($info['targetPathFile']) && $info['result'] || $info['result'] && filemtime($info['orgskin_path']) > filemtime($info['targetPathFile'])) {
$minifyJS = new JS($info['orgskin_path']);
$info['result'] = $minifyJS->minify($info['targetPathFile']);
}
return $this->_helper()->getResultPath($info, $mergeCallback);
}
protected function getContentCirticalStyle($filename)
{
$fileDir = Mage::getBaseDir() . $filename;
try {
if (file_exists($fileDir)) {
return '<style type="text/css">' . @file_get_contents($fileDir) . '</style>' ."\n";
} else {
return null;
}
} catch (Exception $e) {
return null;
}
}
}
<?php
if ((string) Mage::getConfig()->getModuleConfig('Smartwave_Porto')->active == 'true'){
class Onlinebiz_Optimize_Block_Optimize_Head_Abstract extends Smartwave_Porto_Block_Html_Head {}
} else {
class Onlinebiz_Optimize_Block_Optimize_Head_Abstract extends Mage_Page_Block_Html_Head {}
}
?>
<?php
class Onlinebiz_Optimize_Helper_Autoloader
{
const CLOUDINARY_EXTENSION_LIB_PATH = 'CloudinaryExtension';
const CLOUDINARY_LIB_PATH = 'MatthiasMullie';
const CONVERT_CLASS_TO_PATH_REGEX = '#\\\|_(?!.*\\\)#';
private $_originalAutoloaders;
public function register()
{
$this->_deregisterVarienAutoloaders();
$this->_registerCloudinaryAutoloader();
$this->_registerCloudinaryExtensionAutoloader();
$this->_reregisterVarienAutoloaders();
}
private function _registerCloudinaryExtensionAutoloader()
{
spl_autoload_register(
function ($className) {
if(
strpos($className, Onlinebiz_Optimize_Helper_Autoloader::CLOUDINARY_EXTENSION_LIB_PATH . '\\') === 0 ||
strpos($className, Onlinebiz_Optimize_Helper_Autoloader::CLOUDINARY_LIB_PATH . '\\') === 0
) {
include_once preg_replace(Onlinebiz_Optimize_Helper_Autoloader::CONVERT_CLASS_TO_PATH_REGEX, '/', $className) . '.php';
}
}
);
return $this;
}
private function _registerCloudinaryAutoloader()
{
$libFolder = Mage::getBaseDir('lib');
spl_autoload_register(
function ($className) use ($libFolder) {
if($className === Onlinebiz_Optimize_Helper_Autoloader::CLOUDINARY_LIB_PATH) {
foreach(new GlobIterator($libFolder . DS . Onlinebiz_Optimize_Helper_Autoloader::CLOUDINARY_LIB_PATH . DS . '*.php') as $phpFile) {
include_once $phpFile;
}
}
}
);
return $this;
}
private function _deregisterVarienAutoloaders()
{
$this->_originalAutoloaders = array();
foreach (spl_autoload_functions() as $callback) {
if (is_array($callback) && $callback[0] instanceof Varien_Autoload) {
$this->_originalAutoloaders[] = $callback;
spl_autoload_unregister($callback);
}
}
}
private function _reregisterVarienAutoloaders()
{
foreach ($this->_originalAutoloaders as $autoloader) {
spl_autoload_register($autoloader);
}
}
}
\ No newline at end of file
<?php
class Onlinebiz_Optimize_Helper_Data extends Mage_Core_Helper_Abstract
{
const XML_SYSTEM_PATH_CONFIG_URL_CLOUDINARY = 'onlinebiz_optimize/cloudmedia/';
const XML_SYSTEM_PATH_CONFIG_GENERAL = 'onlinebiz_optimize/general/';
const XML_SYSTEM_PATH_CONFIG_URL_ADAPTIVE = 'onlinebiz_optimize/adaptive/';
const CLOUDINARY_RAW_UPLOAD_VARIABLE = '{{cloudmedia_raw_upload_url}}';
const CLOUDINARY_IMAGE_UPLOAD_VARIABLE = '{{cloudmedia_image_upload_url}}';
const CLOUDINARY_IMAGE_OPTIONS_VARIABLE = '{{cloudmedia_image_upload_options}}';
const XML_SYSTEM_PATH_COMPRESSION = 'onlinebiz_optimize/general/{{type}}compression';
const XML_SYSTEM_PATH_IS_FILE_VERSION = 'onlinebiz_optimize/general/version';
const POSTION_TOP = 'top';
const POSITION_BOTTOM = 'bottom';
const POSITION_CENTER = 'center';
/**
* Compare param $version with magento version
*
* @param string $version Version to compare
* @param string $operator
*
* @return boolean
*/
public function checkVersion($version, $operator = '>=')
{
return version_compare(Mage::getVersion(), $version, $operator);
}
/**
* @param mixed $storeId
* @return string
*/
public function isFilesVersion($storeId = null)
{
if (Mage::app()->getStore()->isAdmin())
return false;
return (int)$this->getCloudinaryConfigData('version', $storeId);
}
/**
* @param string $field
* @param mixed $storeId
* @return array
*/
public function getCloudinaryConfigData($field, $storeId = null)
{
$path = self::XML_SYSTEM_PATH_CONFIG_URL_CLOUDINARY . $field;
return Mage::getStoreConfig($path, $storeId);
}
/**
* @param string $field
* @param mixed $storeId
* @return array
*/
public function getGeneralConfig($field, $storeId = null)
{
$path = self::XML_SYSTEM_PATH_CONFIG_GENERAL . $field;
return Mage::getStoreConfig($path, $storeId);
}
/**
* @param mixed $storeId
* @return string
*/
public function getIMGixOptions($storeId = null)
{
if (!$this->isCloudinaryActive())
return null;
return trim(Mage::helper('core')->htmlEscape((string)$this->getCloudinaryConfigData('options', $storeId)));
}
/**
* @param mixed $storeId
* @return string
*/
public function isCloudinaryActive($storeId = null)
{
return (int)$this->getCloudinaryConfigData('enable', $storeId);
}
/**
* @param mixed $storeId
* @return string
*/
public function getIMGixAdvancedOptions($storeId = null)
{
if (!$this->isCloudinaryActive())
return null;
$advancedOptions = trim(Mage::helper('core')->htmlEscape((string)$this->getCloudinaryConfigData('advanced_options', $storeId)));
if(!$advancedOptions){
$advancedOptions = $this->getIMGixOptions($storeId);
}
return $advancedOptions;
}
/**
* @param mixed $storeId
* @return string
*/
public function isDisabledIMGCache($storeId = null)
{
if (!$this->isCloudinaryActive())
return false;
return (int)$this->getCloudinaryConfigData('cached', $storeId);
}
/**
* @param mixed $storeId
* @return string
*/
public function getURLSkinMerge($secure = true, $storeId = null)
{
if (!$this->isCloudinaryActive() || Mage::app()->getStore()->isAdmin()) {
return Mage::getBaseUrl(Mage_Core_Model_Store::URL_TYPE_SKIN);
}
$value = $this->getWebConfigData('skin_merge_url', $secure, $storeId);
return $this->replaceCloudinaryVariable($value, $storeId);
}
/**
* @param string $field
* @param mixed $storeId
* @return array
*/
public function getWebConfigData($field, $secure = true, $storeId = null)
{
$path = 'web/' . ($secure ? 'secure' : 'unsecure') . '/' . $field;
return Mage::getStoreConfig($path, $storeId);
}
/**
* @param mixed $storeId
* @return string
*/
public function replaceCloudinaryVariable($value, $storeId = null)
{
if (strpos($value, self::CLOUDINARY_RAW_UPLOAD_VARIABLE) !== false) {
$rawUrl = $this->getCloudinaryRawURL($storeId);
$value = str_replace(self::CLOUDINARY_RAW_UPLOAD_VARIABLE, $rawUrl, $value);
} elseif (strpos($value, self::CLOUDINARY_IMAGE_UPLOAD_VARIABLE) !== false) {
$imageUrl = $this->getCloudinaryImageURL($storeId);
$value = str_replace(self::CLOUDINARY_IMAGE_UPLOAD_VARIABLE, $imageUrl, $value);
}
if (strpos($value, self::CLOUDINARY_IMAGE_OPTIONS_VARIABLE) !== false) {
$imageOptions = $this->getCloudinaryImageOptions($storeId);
$value = str_replace(self::CLOUDINARY_IMAGE_OPTIONS_VARIABLE, $imageOptions, $value);
}
return $value;
}
/**
* @param mixed $storeId
* @return string
*/
public function getCloudinaryRawURL($storeId = null)
{
return (string)$this->getCloudinaryConfigData('raw_upload', $storeId);
}
/**
* @param mixed $storeId
* @return string
*/
public function getCloudinaryImageURL($storeId = null)
{
return (string)$this->getCloudinaryConfigData('image_upload', $storeId);
}
/**
* @param mixed $storeId
* @return string
*/
public function getCloudinaryImageOptions($storeId = null)
{
return (string)$this->getCloudinaryConfigData('image_options', $storeId);
}
/**
* @param mixed $storeId
* @return string
*/
public function getURLMediaMerge($secure = true, $storeId = null)
{
if (!$this->isCloudinaryActive() || Mage::app()->getStore()->isAdmin())
return Mage::getBaseUrl(Mage_Core_Model_Store::URL_TYPE_MEDIA);
$value = $this->getWebConfigData('media_merge_url', $secure, $storeId);
return $this->replaceCloudinaryVariable($value, $storeId);
}
public function isFontsFile($filename)
{
$fontsExtensions = array('eot', 'woff', 'ttf', 'svg', 'woff2');
$filename = parse_url($filename);
$ext = pathinfo($filename['path'], PATHINFO_EXTENSION);
if (in_array($ext, $fontsExtensions))
return true;
else
return false;
}
public function replaceCloudWpImageUrl($content)
{
if (!$this->isCloudinaryActive())
return $content;
$wpUrl = Mage::helper('wordpress')->getBaseUrl();
$urlPath = parse_url($wpUrl);
$wpPath = $urlPath['path'];
$urlCloud = str_replace('/skin/', $wpPath, Mage::getBaseUrl('skin'));
return str_replace($wpUrl, $urlCloud, $content);
}
public function getCssJsCloudUrl($storeId = null)
{
return Mage::getStoreConfig(Onlinebiz_Optimize_Model_Core_Store::XML_PATH_CLOUDINARY . 'basejscss', $storeId);
}
public function _getFileVersion($baseDirFile, $type = 'media')
{
if (!$this->isFileVersion())
return;
$baseDir = ($type == 'js') ? Mage::getBaseDir() . DS . 'js' . DS : Mage::getBaseDir($type) . DS;
$file = $baseDir . trim($baseDirFile);
return file_exists($file) ? '?v=' . filemtime($file) : '?v=na';
}
/**
* @param is use file version
* @param mixed $store
* @param string $type
* @return bool
*/
public function isFileVersion()
{
return (int)Mage::getStoreConfig(self::XML_SYSTEM_PATH_IS_FILE_VERSION);
}
/**
* Retreive some basic information depending on the type of file: original path, destination path, type, name, static
*
* @param string $name
* @param string $type
* @param bool $static
* @return array $info
*/
public function getCompressedInfo($name, $type = 'css', $static = false)
{
$info = array();
$designPackage = Mage::getDesign();
$orgname = $name;
if (!$static) {
$info['orgskin_path'] = $designPackage->getFilename($orgname, array('_type' => 'skin'));
} else {
$info['orgskin_path'] = Mage::getBaseDir() . DS . 'js' . DS . $name;
}
if (isset($info['orgskin_path']) && !file_exists($info['orgskin_path']) || !filesize($info['orgskin_path']))
return false;
$orgpath = explode(DS, $info['orgskin_path']);
if (count($orgpath) > 1 && !$static) {
// Get the theme of the original file
$info['theme_name'] = $orgpath [count($orgpath) - (count(explode('/', $orgname)) + 1)];
}
$name = $this->_getFileNameFromPath($name);
$name = !$name ? substr($name, 0, strpos($name, '.' . $type)) : $name;
$name = md5($info['orgskin_path']) . '_' . $name;
$file_path = explode('/', $name);
if (count($file_path) > 1) {
$name = array_pop($file_path);
$info['file_path'] = implode(DS, $file_path);
} else {
$info['file_path'] = '';
}
$info['original_name'] = $orgname;
$info['new_name'] = $name;
$info['type'] = $type;
$info['static'] = $static;
$targetDir = $this->initMergerDir($type);
if (!$targetDir) {
$info['success'] = false;
return $info;
}
if (Mage::app()->getStore()->isCurrentlySecure() && $type == 'css') {
$info['targetPath'] = Mage::getBaseDir('media') . DS . 'css_secure' . DS;
} else {
$info['targetPath'] = Mage::getBaseDir('media') . DS . $type . DS;
}
$info['targetPathFile'] = $info['targetPath'] . $name;
if (!file_exists($info['targetPath'])) {
mkdir($info['targetPath'], 0755, true);
}
return $info;
}
/**
* Make sure merger dir exists and writeable
* Also can clean it up
*
* @param string $dirRelativeName
* @param bool $cleanup
* @return bool|string
*/
public function initMergerDir($dirRelativeName, $cleanup = false)
{
$mediaDir = Mage::getBaseDir('media');
try {
$dir = Mage::getBaseDir('media') . DS . $dirRelativeName;
if ($cleanup) {
Diglin_Io_File::rmdirRecursive($dir);
}
if (!is_dir($dir)) {
mkdir($dir);
}
return is_writeable($dir) ? $dir : false;
} catch (Exception $e) {
Mage::logException($e);
}
return false;
}
/**
* Provide the path URL of the filesystem for js or css files compressed or not
*
* @param array $info
* @param string|array $mergeCallback
* @return string
*/
public function getResultPath($info, $mergeCallback)
{
$designPackage = Mage::getDesign();
// Skin items
if (!$info['result']) { // Default Folder
return ($mergeCallback ? $designPackage->getFilename($info['original_name'], array('_type' => 'skin')) : $designPackage->getSkinUrl($info['original_name'], array()));
} else {
$type = $info['type'];
$version = $this->isFileVersion();
$time = '';
$isSecure = Mage::app()->getStore()->isCurrentlySecure();
if ($version) {
$time = '?v=' . filectime($info['targetPathFile']);
}
$baseFilePath = ($isSecure && $type == 'css') ? 'css_secure' . '/' . $info['new_name'] : $type . '/' . $info['new_name'];
$mediaMerge = $this->getMediaMergeUrl($baseFilePath, Mage::app()->getStore()->getId());
if ($isSecure && $type == 'css') {
return $mediaMerge . 'css_secure' . '/' . $info['new_name'] . $time;
} else {
return $mediaMerge . $type . '/' . $info['new_name'] . $time;
}
}
}
public function getMediaMergeUrl($baseDirFile, $storeId = null)
{
return Mage::getBaseUrl(Onlinebiz_Optimize_Model_Core_Store::URL_TYPE_MEDIA_MERGE);
}
/**
* Add trailing slash if necessary
*
* @param string $url
*/
public function getTrailingSlash($url)
{
if (Mage::getStoreConfig('web/seo/trailingslash')) {
if (!preg_match('/\\.(rss|html|htm|xml|php?)$/', strtolower($url)) && substr($url, -1) != '/') {
$url .= '/';
}
}
return $url;
}
/**
* @param is use csscompression
* @param mixed $store
* @return bool
*/
public function isUseCompression($type = 'css', $store = null)
{
$path = str_replace('{{type}}', $type, self::XML_SYSTEM_PATH_COMPRESSION);
return (int)Mage::getStoreConfig($path, $store);
}
public function getCropPositionsOptions()
{
return array(
self::POSTION_TOP => $this->__('Top image'),
self::POSITION_CENTER => $this->__('Center image'),
self::POSITION_BOTTOM => $this->__('Bottom image')
);
}
public function _getCropPosition()
{
return (int)$this->getAdaptiveConfigData('position');
}
/**
* @param string $field
* @param mixed $storeId
* @return array
*/
public function getAdaptiveConfigData($field, $storeId = null)
{
$path = self::XML_SYSTEM_PATH_CONFIG_URL_ADAPTIVE . $field;
return Mage::getStoreConfig($path, $storeId);
}
public function useAdaptive()
{
return (int)$this->getAdaptiveConfigData('enable');
}
public function isDevMode()
{
return (int)$this->getCloudinaryConfigData('devmode') && $this->isCloudinaryActive();
}
public function _getImageSrcset($imgUrl)
{
if (!$imgUrl || !$this->isCloudinaryActive())
return;
$html = 'srcset="%s" ';
$urlSet = array();
for ($i = 1; $i < 4; $i++) {
$urlSet[] = $imgUrl . '&dpr=' . $i . ' ' . $i . 'x';
}
return sprintf($html, implode(',', $urlSet));
}
private function _getFileNameFromPath($filePath)
{
try {
$filePathInfo = explode('/', $filePath);
return array_pop($filePathInfo);
} catch (Exception $e) {
return false;
}
}
public function isUrlsBlacklist()
{
$config = $this->getGeneralConfig('url_blacklist');
$urls = explode(PHP_EOL, $config);
if (count($urls)){
$urls = array_unique(array_filter($urls));
$requestUri = Mage::app()->getRequest()->getRequestUri();
foreach ($urls as $url){
if(strpos($requestUri, $url) !== false) {
return true;
}
}
}
return false;
}
}
<?php
/**
* Magento
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://opensource.org/licenses/osl-3.0.php
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@magentocommerce.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade Magento to newer
* versions in the future. If you wish to customize Magento for your
* needs please refer to http://www.magentocommerce.com for more information.
*
* @category Mage
* @package Mage_Catalog
* @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com)
* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
*/
/**
* Catalog image helper
*
* @author Magento Core Team <core@magentocommerce.com>
*/
class Onlinebiz_Optimize_Helper_Image extends Onlinebiz_Optimize_Helper_Image_Abstract
{
/**
* Used Advanced options
*
* @var int
*/
protected $_useAdvanced = false;
/**
* Reset all previous data
*
* @return Mage_Catalog_Helper_Image
*/
protected function _reset()
{
parent::_reset();
$this->_useAdvanced = false;
return $this;
}
/**
* Initialize Helper to work with Image
*
* @param Mage_Catalog_Model_Product $product
* @param string $attributeName
* @param mixed $imageFile
* @return Mage_Catalog_Helper_Image
*/
public function init(Mage_Catalog_Model_Product $product, $attributeName, $imageFile = null, $useAdvanced = false)
{
parent::init($product, $attributeName, $imageFile, $useAdvanced);
$this->_useAdvanced = $useAdvanced;
return $this;
}
/**
* Return Image URL
*
* @return string
*/
public function __toString()
{
if (Mage::app()->getStore()->isAdmin()) {
return parent::__toString();
}
try {
$model = $this->_getModel();
if ($this->getImageFile()) {
$model->setBaseFile($this->getImageFile(), $this->_useAdvanced);
} else {
$model->setBaseFile($this->getProduct()->getData($model->getDestinationSubdir()), $this->_useAdvanced);
}
$disableIMGCache = Mage::helper('onlinebiz_optimize')->isDisabledIMGCache();
if ($model->isCached() || $disableIMGCache) {
return $model->getUrl();
} else {
if ($this->_scheduleRotate) {
$model->rotate($this->getAngle());
}
if ($this->_scheduleResize) {
$model->resize();
}
if ($this->getWatermark()) {
$model->setWatermark($this->getWatermark());
}
$url = $model->saveFile()->getUrl();
}
} catch (Exception $e) {
$url = Mage::getDesign()->getSkinUrl($this->getPlaceholder());
}
return $url;
}
}
<?php
if ((string) Mage::getConfig()->getModuleConfig('Amasty_Shopby')->active == 'true'){
class Onlinebiz_Optimize_Helper_Image_Abstract extends Amasty_Shopby_Helper_Image {}
} else {
class Onlinebiz_Optimize_Helper_Image_Abstract extends Mage_Catalog_Helper_Image {}
}
?>
<?php
class Onlinebiz_Optimize_Model_Core_Design_Package extends Mage_Core_Model_Design_Package
{
protected function _helper()
{
return Mage::helper('onlinebiz_optimize');
}
protected function isCloud()
{
return $this->_helper()->isCloudinaryActive($this->getStore());
}
/**
* Get skin file url
*
* @param string $file
* @param array $params
* @return string
*/
public function getSkinUrl($file = null, array $params = array())
{
Varien_Profiler::start(__METHOD__);
if (empty($params['_type'])) {
$params['_type'] = 'skin';
}
if (empty($params['_default'])) {
$params['_default'] = false;
}
$this->updateParamDefaults($params);
if (!empty($file)) {
if ($this->_helper()->checkVersion('1.9')) {
$result = $this->_fallback(
$file,
$params,
$this->_fallback->getFallbackScheme(
$params['_area'],
$params['_package'],
$params['_theme']
)
);
} else {
$result = $this->_fallback(
$file,
$params,
array(
array(),
array('_theme' => $this->getFallbackTheme()),
array('_theme' => self::DEFAULT_THEME),
)
);
}
}
if($this->isNotImageFile($file))
$result = $this->getSkinJsCss($file, $params) . (empty($file) ? '' : $file);
else
$result = $this->getSkinBaseUrl($params) . (empty($file) ? '' : $file . $this->_helper()->getIMGixOptions());
Varien_Profiler::stop(__METHOD__);
return $result;
}
/**
* Get skin file url
*
* @param string $file
* @param array $params
* @return string
*/
public function getSkinMageUrl($file = null, array $params = array())
{
Varien_Profiler::start(__METHOD__);
if (empty($params['_type'])) {
$params['_type'] = 'skin';
}
if (empty($params['_default'])) {
$params['_default'] = false;
}
$this->updateParamDefaults($params);
if (!empty($file)) {
$result = $this->_fallback($file, $params, array(
array(),
array('_theme' => $this->getFallbackTheme()),
array('_theme' => self::DEFAULT_THEME),
));
}
$result = $this->getSkinBaseMageUrl($params) . (empty($file) ? '' : $file);
Varien_Profiler::stop(__METHOD__);
return $result;
}
public function getSkinJsCss($file = null, array $params=array())
{
$skinMergeURL = Mage::getBaseUrl(Onlinebiz_Optimize_Model_Core_Store::URL_TYPE_SKIN_MERGE);
$params['_type'] = 'skin';
$this->updateParamDefaults($params);
$baseUrl = $skinMergeURL . $params['_area'] . '/' . $params['_package'] . '/' . $params['_theme'] . '/';
return $baseUrl;
}
public function isNotImageFile($file) {
$info = pathinfo($file);
$extension = '';
if(isset($info['extension']))
$extension = $info['extension'];
return in_array(strtolower($extension), array("css", "js"));
}
/**
* Merge specified css files and return URL to the merged file on success
*
* @param $files
* @return string
*/
public function getMergedCssUrl($files)
{
// secure or unsecure
$isSecure = Mage::app()->getRequest()->isSecure();
$mergerDir = $isSecure ? 'css_secure' : 'css';
$targetDir = $this->_initMergerDir($mergerDir);
if (!$targetDir) {
return '';
}
// base hostname & port
$baseMediaUrl = Mage::getBaseUrl('media', $isSecure);
$hostname = parse_url($baseMediaUrl, PHP_URL_HOST);
$port = parse_url($baseMediaUrl, PHP_URL_PORT);
if (false === $port) {
$port = $isSecure ? 443 : 80;
}
// merge into target file
$targetFilename = md5(implode(',', $files) . "|{$hostname}|{$port}") . '.min.css';
$localFiles = $this->_removeParamFiles($files);
$mergeFilesResult = $this->_mergeFiles(
$localFiles, $targetDir . DS . $targetFilename,
false,
array($this, 'beforeMergeCss'),
'css'
);
// get Merge Media URL
$mediaMerge = $this->_helper()->getMediaMergeUrl($mergerDir . DS . $targetFilename, $this->getStore());
$version = $this->_helper()->_getFileVersion($mergerDir . '/' . $targetFilename);
if(!empty($mediaMerge)){
return $mediaMerge . $mergerDir . '/' . $targetFilename . $version;
}
if ($mergeFilesResult) {
return $baseMediaUrl . $mergerDir . '/' . $targetFilename . $version;
}
return '';
}
/**
* Merge specified javascript files and return URL to the merged file on success
*
* @param $files
* @return string
*/
public function getMergedJsUrl($files)
{
$targetFilename = md5(implode(',', $files)) . '.min.js';
$targetDir = $this->_initMergerDir('js');
if (!$targetDir) {
return '';
}
$linkMerge = $this->_helper()->getMediaMergeUrl('js' . DS . $targetFilename, $this->getStore());
$localFiles = $this->_removeParamFiles($files, 'js');
if(!empty($linkMerge)){
if ($this->_mergeFiles($localFiles, $targetDir . DS . $targetFilename, false, null, 'js')) {
$version = $this->_helper()->_getFileVersion('js' . DS . $targetFilename);
return $linkMerge . 'js/' . $targetFilename . $version;
}
}
return '';
}
public function getSkinBaseMageUrl(array $params=array())
{
$params['_type'] = 'skin';
$this->updateParamDefaults($params);
$baseUrl = Mage::getBaseUrl(Onlinebiz_Optimize_Model_Core_Store::URL_TYPE_SKIN_BASE, isset($params['_secure'])?(bool)$params['_secure']:null)
.$params['_area'].'/'.$params['_package'].'/'.$params['_theme'].'/';
return $baseUrl;
}
private function _removeParamFiles( $files, $type = 'css' )
{
$localFiles = array();
$storeId = Mage::app()->getStore()->getId();
$baseWebUrl = Mage::getBaseUrl(Onlinebiz_Optimize_Model_Core_Store::URL_TYPE_WEB);
if($this->isCloud()){
$baseWebUrl = str_replace( '{{type}}', 'raw', $this->_helper()->getCloudinaryConfigData('url',$storeId) );
if($this->_helper()->getCssJsCloudUrl()){
$baseWebUrl = $this->_helper()->getCssJsCloudUrl();
}
}
$useVersion = $this->_helper()->isFileVersion() ;
$baseMediaDir = Mage::getBaseDir() . DS;
if(is_array($files)){
foreach ($files as $file) {
$newFile = $file;
if( $useVersion ){
$fileInfo = parse_url($file);
if( isset($fileInfo['query'])){
$newFile = str_replace( '?'. $fileInfo['query'],'', $newFile);
}
}
$localFiles[] = str_replace( $baseWebUrl, $baseMediaDir, $newFile );
}
}
return $localFiles;
}
/**
* Prepare url for css replacement
*
* @param string $uri
* @return string
*/
protected function _prepareUrl($uri)
{
// check absolute or relative url
if (!preg_match('/^https?:/i', $uri) && !preg_match('/^\//i', $uri)) {
$fileDir = '';
$pathParts = explode(DS, $uri);
$fileDirParts = explode(DS, $this->_callbackFileDir);
$store = $this->getStore();
$isCssCompression = $this->_helper()->isUseCompression('css',$store);
if($isCssCompression && !$store->isAdmin()){
array_unshift($fileDirParts, 'skin');
}
if (is_int($store)) {
$store = Mage::app()->getStore($store);
}
if ($store->isAdmin()) {
$secure = $store->isAdminUrlSecure();
} else {
$secure = $store->isFrontUrlSecure() && Mage::app()->getRequest()->isSecure();
}
if ('skin' == $fileDirParts[0]) {
$baseUrl = Mage::getBaseUrl('skin', $secure);
$fileDirParts = array_slice($fileDirParts, 1);
} elseif ('media' == $fileDirParts[0]) {
$baseUrl = Mage::getBaseUrl('media', $secure);
$fileDirParts = array_slice($fileDirParts, 1);
} else {
$baseUrl = Mage::getBaseUrl('web', $secure);
}
foreach ($pathParts as $key=>$part) {
if ($part == '.' || $part == '..') {
unset($pathParts[$key]);
}
if ($part == '..' && count($fileDirParts)) {
$fileDirParts = array_slice($fileDirParts, 0, count($fileDirParts) - 1);
}
}
if (count($fileDirParts)) {
$fileDir = implode('/', $fileDirParts).'/';
}
if( $fileDir == 'media/css_secure/' || $fileDir == '/media/css_secure/')
$fileDir = '';
$pathParts = implode('/', $pathParts);
if($isCssCompression){
$pathParts = str_replace('../../skin/','',$pathParts);
$pathParts = str_replace('skin/','',$pathParts);
}
if( $this->_helper()->isFontsFile($pathParts) && $this->isCloud()){
$baseUrl = Mage::getBaseUrl('skincloud', $secure);
}
$uri = $baseUrl . $fileDir . $pathParts;
}
return $uri;
}
/**
* Callback function replaces relative links for @import matches in css file
*
* @param array $match
* @return string
*/
protected function _cssMergerImportCallback($match)
{
$quote = $match[1];
$uri = $this->_prepareUrl($match[2]);
return "@import {$quote}{$uri}{$quote}";
}
/**
* Callback function replaces relative links for url() matches in css file
*
* @param array $match
* @return string
*/
protected function _cssMergerUrlCallback($match)
{
$quote = ($match[1][0] == "'" || $match[1][0] == '"') ? $match[1][0] : '';
$uri = ($quote == '') ? $match[1] : substr($match[1], 1, strlen($match[1]) - 2);
$uri = $this->_prepareUrl($uri);
if($this->isCloud() && !$quote)
return "url('{$uri}')";
return "url({$quote}{$uri}{$quote})";
}
}
<?php
/**
* Magento
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://opensource.org/licenses/osl-3.0.php
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@magentocommerce.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade Magento to newer
* versions in the future. If you wish to customize Magento for your
* needs please refer to http://www.magentocommerce.com for more information.
*
* @category Mage
* @package Mage_Core
* @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com)
* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
*/
/**
* Template model
*
* Example:
*
* // Loading of template
* $emailTemplate = Mage::getModel('core/email_template')
* ->load(Mage::getStoreConfig('path_to_email_template_id_config'));
* $variables = array(
* 'someObject' => Mage::getSingleton('some_model')
* 'someString' => 'Some string value'
* );
* $emailTemplate->send('some@domain.com', 'Name Of User', $variables);
*
* @method Mage_Core_Model_Resource_Email_Template _getResource()
* @method Mage_Core_Model_Resource_Email_Template getResource()
* @method string getTemplateCode()
* @method Mage_Core_Model_Email_Template setTemplateCode(string $value)
* @method string getTemplateText()
* @method Mage_Core_Model_Email_Template setTemplateText(string $value)
* @method string getTemplateStyles()
* @method Mage_Core_Model_Email_Template setTemplateStyles(string $value)
* @method int getTemplateType()
* @method Mage_Core_Model_Email_Template setTemplateType(int $value)
* @method string getTemplateSubject()
* @method Mage_Core_Model_Email_Template setTemplateSubject(string $value)
* @method string getTemplateSenderName()
* @method Mage_Core_Model_Email_Template setTemplateSenderName(string $value)
* @method string getTemplateSenderEmail()
* @method Mage_Core_Model_Email_Template setTemplateSenderEmail(string $value)
* @method string getAddedAt()
* @method Mage_Core_Model_Email_Template setAddedAt(string $value)
* @method string getModifiedAt()
* @method Mage_Core_Model_Email_Template setModifiedAt(string $value)
* @method string getOrigTemplateCode()
* @method Mage_Core_Model_Email_Template setOrigTemplateCode(string $value)
* @method string getOrigTemplateVariables()
* @method Mage_Core_Model_Email_Template setOrigTemplateVariables(string $value)
*
* @category Mage
* @package Mage_Core
* @author Magento Core Team <core@magentocommerce.com>
*/
class Onlinebiz_Optimize_Model_Core_Email_Template extends Onlinebiz_Optimize_Model_Core_Email_Template_Abstract
{
protected function _getLogoUrl($store)
{
$store = Mage::app()->getStore($store);
$fileName = $store->getConfig(self::XML_PATH_DESIGN_EMAIL_LOGO);
if ($fileName) {
$uploadDir = Mage_Adminhtml_Model_System_Config_Backend_Email_Logo::UPLOAD_DIR;
$fullFileName = Mage::getBaseDir('media') . DS . $uploadDir . DS . $fileName;
if (file_exists($fullFileName)) {
return Mage::getBaseUrl(Onlinebiz_Optimize_Model_Core_Store::URL_TYPE_MEDIA_BASE) . $uploadDir . '/' . $fileName;
}
}
return Mage::getDesign()->getSkinMageUrl('images/logo_email.gif');
}
}
<?php
if((string) Mage::getConfig()->getModuleConfig('Ebizmarts_Mandrill')->active == 'true'){
class Onlinebiz_Optimize_Model_Core_Email_Template_Abstract extends Ebizmarts_Mandrill_Model_Email_Template {}
}elseif((string) Mage::getConfig()->getModuleConfig('Ebizmarts_MailChimp')->active == 'true'){
class Onlinebiz_Optimize_Model_Core_Email_Template_Abstract extends Ebizmarts_MailChimp_Model_Email_Template {}
}else {
class Onlinebiz_Optimize_Model_Core_Email_Template_Abstract extends Mage_Core_Model_Email_Template {}
}
<?php
/**
* Magento
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://opensource.org/licenses/osl-3.0.php
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@magentocommerce.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade Magento to newer
* versions in the future. If you wish to customize Magento for your
* needs please refer to http://www.magentocommerce.com for more information.
*
* @category Mage
* @package Mage_Core
* @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com)
* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
*/
/**
* Store model
*
* @method Mage_Core_Model_Resource_Store _getResource()
* @method Mage_Core_Model_Resource_Store getResource()
* @method Mage_Core_Model_Store setCode(string $value)
* @method Mage_Core_Model_Store setWebsiteId(int $value)
* @method Mage_Core_Model_Store setGroupId(int $value)
* @method Mage_Core_Model_Store setName(string $value)
* @method int getSortOrder()
* @method Mage_Core_Model_Store setSortOrder(int $value)
* @method Mage_Core_Model_Store setIsActive(int $value)
*
* @category Mage
* @package Mage_Core
* @author Magento Core Team <core@magentocommerce.com>
*/
class Onlinebiz_Optimize_Model_Core_Store extends Mage_Core_Model_Store
{
/**
* Possible URL types
*/
const URL_TYPE_SKIN_BASE = 'skinbase';
const URL_TYPE_SKIN_CLOUD = 'skincloud';
const URL_TYPE_SKIN_MERGE = 'skin_merge';
const URL_TYPE_MEDIA_BASE = 'mediabase';
const URL_TYPE_MEDIA_MERGE = 'media_merge';
const XML_PATH_CLOUDINARY = 'onlinebiz_optimize/cloudmedia/';
/**
* Retrieve base URL
*
* @param string $type
* @param boolean|null $secure
* @return string
*/
public function getBaseUrl($type = self::URL_TYPE_LINK, $secure = null)
{
$cacheKey = $type . '/' . (is_null($secure) ? 'null' : ($secure ? 'true' : 'false'));
if (!isset($this->_baseUrlCache[$cacheKey])) {
switch ($type) {
case self::URL_TYPE_WEB:
$secure = is_null($secure) ? $this->isCurrentlySecure() : (bool)$secure;
$url = $this->getConfig('web/' . ($secure ? 'secure' : 'unsecure') . '/base_url');
break;
case self::URL_TYPE_LINK:
$secure = (bool)$secure;
$url = $this->getConfig('web/' . ($secure ? 'secure' : 'unsecure') . '/base_link_url');
$url = $this->_updatePathUseRewrites($url);
$url = $this->_updatePathUseStoreView($url);
break;
case self::URL_TYPE_DIRECT_LINK:
$secure = (bool)$secure;
$url = $this->getConfig('web/' . ($secure ? 'secure' : 'unsecure') . '/base_link_url');
$url = $this->_updatePathUseRewrites($url);
break;
case self::URL_TYPE_SKIN:
case self::URL_TYPE_SKIN_BASE:
case self::URL_TYPE_SKIN_CLOUD:
case self::URL_TYPE_SKIN_MERGE:
$secure = is_null($secure) ? $this->isCurrentlySecure() : (bool)$secure;
$url = $this->getConfig('web/' . ($secure ? 'secure' : 'unsecure') . '/base_' . self::URL_TYPE_SKIN . '_url');
if ($type != self::URL_TYPE_SKIN_BASE) {
$cloudType = (self::URL_TYPE_SKIN == $type) ? 'image' : 'raw';
$isCloud = (self::URL_TYPE_SKIN_CLOUD == $type) ? true : false;
$rawUrl = $this->getCloudinaryUrl($cloudType, $isCloud);
if (!is_numeric($rawUrl)) {
$rawUrl = rtrim($rawUrl, DS);
$url = $rawUrl . DS . self::URL_TYPE_SKIN;
}
}
break;
case self::URL_TYPE_JS:
$secure = is_null($secure) ? $this->isCurrentlySecure() : (bool)$secure;
$url = $this->getConfig('web/' . ($secure ? 'secure' : 'unsecure') . '/base_' . $type . '_url');
if (!is_numeric($this->getCloudinaryUrl('raw')))
$url = $this->getCloudinaryUrl('raw') . self::URL_TYPE_JS;
break;
case self::URL_TYPE_MEDIA:
case self::URL_TYPE_MEDIA_MERGE:
case self::URL_TYPE_MEDIA_BASE:
$url = $this->_updateMediaPathUseRewrites($secure);
if ($type != self::URL_TYPE_MEDIA_BASE) {
$cloudType = (self::URL_TYPE_MEDIA_MERGE == $type) ? 'raw' : 'image';
$cloudUrl = $this->getCloudinaryUrl($cloudType);
if (!is_numeric($cloudUrl)){
$cloudUrl = rtrim($cloudUrl, DS);
$url = $cloudUrl . DS . self::URL_TYPE_MEDIA;
}
}
break;
default:
throw Mage::exception('Mage_Core', Mage::helper('core')->__('Invalid base url type'));
}
if (false !== strpos($url, '{{base_url}}')) {
$baseUrl = Mage::getConfig()->substDistroServerVars('{{base_url}}');
$url = str_replace('{{base_url}}', $baseUrl, $url);
}
$this->_baseUrlCache[$cacheKey] = rtrim($url, '/') . '/';
}
return $this->_baseUrlCache[$cacheKey];
}
public function getCloudinaryUrl($type = null, $cloud = false)
{
if (!(int)$this->isCloudinaryActive())
return 0;
$baseJSCSS = rtrim($this->getConfig(self::XML_PATH_CLOUDINARY . 'basejscss'), '/');
if ($type == 'raw' && $baseJSCSS && !$cloud)
return $baseJSCSS . '/';
$value = str_replace('{{type}}', $type, $this->getConfig(self::XML_PATH_CLOUDINARY . 'url'));
$value = rtrim($value, '/');
if ($type == 'image')
$value = $value . '/';
return $value;
}
public function isCloudinaryActive()
{
if (Mage::app()->getStore()->isAdmin())
return 0;
else
return $this->getConfig(self::XML_PATH_CLOUDINARY . 'enable');
}
}
<?php
/**
* @package Pagespeed_Css
* @copyright Copyright (c) 2015 mediarox UG (haftungsbeschraenkt) (http://www.mediarox.de)
* @author Steven Fritzsche <sfritzsche@mediarox.de>
* @author Thomas Uhlig <tuhlig@mediarox.de>
*/
/**
* Standard observer class
*/
class Onlinebiz_Optimize_Model_Css_Observer
{
const LOADCSS = '(function(w){"use strict";if(!w.loadCSS){w.loadCSS=function(){}}
var rp=loadCSS.relpreload={};rp.support=(function(){var ret;try{ret=w.document.createElement("link").relList.supports("preload")}catch(e){ret=!1}
return function(){return ret}})();rp.bindMediaToggle=function(link){var finalMedia=link.media||"all";function enableStylesheet(){link.media=finalMedia}
if(link.addEventListener){link.addEventListener("load",enableStylesheet)}else if(link.attachEvent){link.attachEvent("onload",enableStylesheet)}
setTimeout(function(){link.rel="stylesheet";link.media="only x"});setTimeout(enableStylesheet,3000)};rp.poly=function(){if(rp.support()){return}
var links=w.document.getElementsByTagName("link");for(var i=0;i<links.length;i++){var link=links[i];if(link.rel==="preload"&&link.getAttribute("as")==="style"&&!link.getAttribute("data-loadcss")){link.setAttribute("data-loadcss",!0);rp.bindMediaToggle(link)}}};if(!rp.support()){rp.poly();var run=w.setInterval(rp.poly,500);if(w.addEventListener){w.addEventListener("load",function(){rp.poly();w.clearInterval(run)})}else if(w.attachEvent){w.attachEvent("onload",function(){rp.poly();w.clearInterval(run)})}}
if(typeof exports!=="undefined"){exports.loadCSS=loadCSS}
else{w.loadCSS=loadCSS}}(typeof global!=="undefined"?global:this))';
/**
* @const string
*/
const HTML_TAG_BODY = '</body>';
/**
* Will finally contain all css tags to move.
* @var string
*/
private $cssTags = '';
/**
* Contains all exclude regex patterns.
* @var array
*/
private $excludeList = array();
/**
* Processes the matched single css tag or the conditional css tag group.
*
* Step 1: Return if hit is blacklisted by exclude list.
* Step 2: Add hit to css tag list and return empty string for the replacement.
*
* @param array $hits
* @return string
*/
public function processHit($hits)
{
// Step 1
if ($this->isHitExcluded($hits[0])) return $hits[0];
// Step 2
$this->cssTags .= $hits[0];
return '';
}
/**
* Is hit on exclude list?
*
* @param string $hit
* @return bool
*/
protected function isHitExcluded($hit)
{
$c = 0;
preg_replace($this->excludeList, '', $hit, -1, $c);
return ($c > 0);
}
/**
* Move Css (head & inline) to the bottom. ({excluded_css}{stripped_html}{css}</body></html>)
*
* Step 1: Return if module is disabled.
* Step 2: Load needed data.
* Step 3: Return if no </body> is found in html.
* Step 4: Search and replace conditional css units. (example: <!--[if lt IE 7]>{multiple css tags}<![endif]-->)
* Step 5: Search and replace external css tags. (link-tags must xhtml-compliant closed by "/>")
* Step 6: Search and replace inline css tags.
* Step 7: Return if no css is found.
* Step 8: Remove blank lines from html.
* Step 9: Recalculating </body> position, insert css groups right before body ends and set response.
* Final order:
* 1. excluded css
* 2. stripped html
* 3. conditional css tags
* 4. external css tags
* 5. inline css tags
* 6. </body></html>
*
* @param Varien_Event_Observer $observer
*/
public function parseCssToBottom(Varien_Event_Observer $observer)
{
// Step 1
$helper = Mage::helper('onlinebiz_optimize');
if (!$helper->getGeneralConfig('move_to_bottom')) return;
if ($helper->isUrlsBlacklist()) return;
// Step 2
$response = $observer->getFront()->getResponse();
$html = $response->getBody();
$this->excludeList = array();
// Step 3
$closedBodyPosition = strripos($html, self::HTML_TAG_BODY);
if (false === $closedBodyPosition) return;
// Step 4
$html = preg_replace_callback(
'#\<\!--\[if[^\>]*>\s*<link[^>]*type\="text\/css"[^>]*/>\s*<\!\[endif\]-->#isU',
'self::processHit',
$html
);
// Step 5
$html = preg_replace_callback(
'#<link[^>]*type\=["\']text\/css["\'][^>]*/>#isU',
'self::processHit',
$html
);
// Step 6
//$html = preg_replace_callback('#<style.*</style>#isUm','self::processHit',$html);
// Step 7
if (!$this->cssTags) return;
// Step 8
$html = preg_replace('/^\h*\v+/m', '', $html);
// Step 9
$closedBodyPosition = strripos($html, self::HTML_TAG_BODY);
$html = substr_replace($html, $this->cssTags, $closedBodyPosition, 0);
$response->setBody($html);
}
}
\ No newline at end of file
<?php
/**
* @author Amasty Team
* @copyright Copyright (c) 2008-2012 Amasty (http://www.amasty.com)
* @package Amasty_Label
*/
class Onlinebiz_Optimize_Model_Label extends Onlinebiz_Optimize_Model_Label_Abstract
{
protected function _mediaHelper()
{
return Mage::helper('onlinebiz_optimize');
}
public function getImageUrl()
{
return Mage::getBaseUrl('media') . 'amlabel/' . $this->getValue('img') . $this->_mediaHelper()->getIMGixOptions();
}
}
<?php
if((string) Mage::getConfig()->getModuleConfig('OnlineBiz_AmLabelExtend')->active == 'true'){
class Onlinebiz_Optimize_Model_Label_Abstract extends OnlineBiz_AmLabelExtend_Model_Label {}
} else {
class Onlinebiz_Optimize_Model_Label_Abstract extends Amasty_Label_Model_Label {}
}
<?php
class Onlinebiz_Optimize_Model_Observer extends Mage_Core_Model_Abstract
{
public function loadCustomAutoloaders(Varien_Event_Observer $event)
{
Mage::helper('onlinebiz_optimize/autoloader')->register();
return $event;
}
}
\ No newline at end of file
<?php
/**
* Magento
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://opensource.org/licenses/osl-3.0.php
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@magentocommerce.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade Magento to newer
* versions in the future. If you wish to customize Magento for your
* needs please refer to http://www.magentocommerce.com for more information.
*
* @category Mage
* @package Mage_Catalog
* @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com)
* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
*/
/**
* Catalog product link model
*
* @category Mage
* @package Mage_Catalog
* @author Magento Core Team <core@magentocommerce.com>
*/
class Onlinebiz_Optimize_Model_Product_Image extends Mage_Catalog_Model_Product_Image
{
private function _mediaHelper()
{
return Mage::helper('onlinebiz_optimize');
}
/**
* Set filenames for base file and new file
*
* @param string $file
* @return Mage_Catalog_Model_Product_Image
*/
public function setBaseFile($file, $isAdvanced = false)
{
if( Mage::app()->getStore()->isAdmin() ){
return parent::setBaseFile($file);
}
$this->_isBaseFilePlaceholder = false;
$suffixParams = !$isAdvanced ? $this->_mediaHelper()->getIMGixOptions() : $this->_mediaHelper()->getIMGixAdvancedOptions();
$disableIMGCache = $this->_mediaHelper()->isDisabledIMGCache();
if (($file) && (0 !== strpos($file, '/', 0))) {
$file = '/' . $file;
}
$baseDir = Mage::getSingleton('catalog/product_media_config')->getBaseMediaPath();
if ('/no_selection' == $file) {
$file = null;
}
if ($file) {
if ((!$this->_fileExists($baseDir . $file)) || !$this->_checkMemory($baseDir . $file)) {
$file = null;
}
}
if (!$file) {
// check if placeholder defined in config
$isConfigPlaceholder = Mage::getStoreConfig("catalog/placeholder/{$this->getDestinationSubdir()}_placeholder");
$configPlaceholder = '/placeholder/' . $isConfigPlaceholder;
if ($isConfigPlaceholder && $this->_fileExists($baseDir . $configPlaceholder)) {
$file = $configPlaceholder;
} else {
// replace file with skin or default skin placeholder
$skinBaseDir = Mage::getDesign()->getSkinBaseDir();
$skinPlaceholder = "/images/catalog/product/placeholder/{$this->getDestinationSubdir()}.jpg";
$file = $skinPlaceholder;
if (file_exists($skinBaseDir . $file)) {
$baseDir = $skinBaseDir;
} else {
$baseDir = Mage::getDesign()->getSkinBaseDir(array('_theme' => 'default'));
if (!file_exists($baseDir . $file)) {
$baseDir = Mage::getDesign()->getSkinBaseDir(array('_theme' => 'default', '_package' => 'base'));
}
}
}
$this->_isBaseFilePlaceholder = true;
}
$baseFile = $baseDir . $file;
if (((!$file) || (!file_exists($baseFile))) && !$this->_mediaHelper()->isDevMode()) {
throw new Exception(Mage::helper('catalog')->__('Image file was not found.'));
}
$this->_baseFile = $baseFile;
// build new filename (most important params)
$path = array(
Mage::getSingleton('catalog/product_media_config')->getBaseMediaPath(),
'cache',
Mage::app()->getStore()->getId(),
$path[] = $this->getDestinationSubdir()
);
if($disableIMGCache){
$path = array(Mage::getSingleton('catalog/product_media_config')->getBaseMediaPath());
}
if((!empty($this->_width)) || (!empty($this->_height))){
if(!$this->_height)
$this->_height = $this->_width;
if($disableIMGCache){
$suffixParams .= ($suffixParams != '' ) ? "&w={$this->_width}&h={$this->_height}" : "?w={$this->_width}&h={$this->_height}";
} else {
$path[] = "{$this->_width}x{$this->_height}";
}
}
// add misk params as a hash
$miscParams = array(
($this->_keepAspectRatio ? '' : 'non') . 'proportional',
($this->_keepFrame ? '' : 'no') . 'frame',
($this->_keepTransparency ? '' : 'no') . 'transparency',
($this->_constrainOnly ? 'do' : 'not') . 'constrainonly',
$this->_rgbToString($this->_backgroundColor),
'angle' . $this->_angle,
'quality' . $this->_quality
);
// if has watermark add watermark params to hash
if ($this->getWatermarkFile()) {
$miscParams[] = $this->getWatermarkFile();
$miscParams[] = $this->getWatermarkImageOpacity();
$miscParams[] = $this->getWatermarkPosition();
$miscParams[] = $this->getWatermarkWidth();
$miscParams[] = $this->getWatermarkHeigth();
}
if(!$disableIMGCache)
$path[] = md5(implode('_', $miscParams));
// append prepared filename
$this->_newFile = implode('/', $path) . $file . $suffixParams; // the $file contains heading slash
return $this;
}
/**
* First check this file on FS
* If it doesn't exist - try to download it from DB
*
* @param string $filename
* @return bool
*/
protected function _fileExists($filename)
{
if($this->_mediaHelper()->isDevMode())
return true;
$filename = $this->_removeQueryFile($filename);
if (file_exists($filename)) {
return true;
} else {
return Mage::helper('core/file_storage_database')->saveFileToFilesystem($filename);
}
}
/**
* @return Mage_Catalog_Model_Product_Image
*/
public function saveFile()
{
$filename = $this->getNewFile();
$filename = $this->_removeQueryFile($filename);
$this->getImageProcessor()->save($filename);
Mage::helper('core/file_storage_database')->saveFile($filename);
return $this;
}
private function _removeQueryFile($filename)
{
$fileInfo = parse_url($filename);
if(isset($fileInfo['query']))
$filename = str_replace('?' .$fileInfo['query'], '', $filename);
return $filename;
}
}
<?php
class Onlinebiz_Optimize_Model_System_Config_Source_Position
{
/**
* Reteirves array with options
* @return array
*/
public function toOptionArray()
{
return Mage::helper('onlinebiz_optimize')->getCropPositionsOptions();
}
}
?>
<?xml version="1.0" encoding="UTF-8"?>
<config>
<acl>
<resources>
<all>
<title>Allow Everything</title>
</all>
<admin>
<children>
<system>
<children>
<config>
<children>
<onlinebiz_optimize translate="title">
<title>Media Optimize</title>
</onlinebiz_optimize>
</children>
</config>
</children>
</system>
</children>
</admin>
</resources>
</acl>
</config>
\ No newline at end of file
<?xml version="1.0"?>
<config>
<modules>
<Onlinebiz_Optimize>
<version>0.0.2</version>
</Onlinebiz_Optimize>
</modules>
<default>
<onlinebiz_optimize>
<general>
<csscompression><![CDATA[0]]></csscompression>
<jscompression><![CDATA[0]]></jscompression>
<version><![CDATA[1]]></version>
</general>
<cloudmedia>
<enable><![CDATA[0]]></enable>
<options><![CDATA[?auto=compress,format,enhance,strip]]></options>
<cached>1</cached>
</cloudmedia>
</onlinebiz_optimize>
</default>
<global>
<models>
<onlinebiz_optimize>
<class>Onlinebiz_Optimize_Model</class>
</onlinebiz_optimize>
<core>
<rewrite>
<design_package>Onlinebiz_Optimize_Model_Core_Design_Package</design_package>
<store>Onlinebiz_Optimize_Model_Core_Store</store>
<email_template>Onlinebiz_Optimize_Model_Core_Email_Template</email_template>
</rewrite>
</core>
<catalog>
<rewrite>
<product_image>Onlinebiz_Optimize_Model_Product_Image</product_image>
</rewrite>
</catalog>
</models>
<helpers>
<onlinebiz_optimize>
<class>Onlinebiz_Optimize_Helper</class>
</onlinebiz_optimize>
<catalog>
<rewrite>
<image>Onlinebiz_Optimize_Helper_Image</image>
</rewrite>
</catalog>
</helpers>
<blocks>
<onlinebiz_optimize>
<class>Onlinebiz_Optimize_Block</class>
</onlinebiz_optimize>
<page>
<rewrite>
<html_head>Onlinebiz_Optimize_Block_Optimize_Head</html_head>
</rewrite>
</page>
</blocks>
<events>
<controller_front_init_before>
<observers>
<onlinebiz_custom_autoloaders>
<type>singleton</type>
<class>onlinebiz_optimize/observer</class>
<method>loadCustomAutoloaders</method>
</onlinebiz_custom_autoloaders>
</observers>
</controller_front_init_before>
</events>
</global>
</config>
<?xml version="1.0"?>
<config>
<sections>
<web>
<groups>
<secure>
<fields>
<media_merge_url translate="label">
<label>Media Url for Merge js,css</label>
<frontend_type>text</frontend_type>
<sort_order>31</sort_order>
<show_in_default>0</show_in_default>
<show_in_website>0</show_in_website>
<show_in_store>0</show_in_store>
</media_merge_url>
<skin_merge_url translate="label">
<label>Skin Url for js,css Cloud</label>
<frontend_type>text</frontend_type>
<sort_order>31</sort_order>
<show_in_default>0</show_in_default>
<show_in_website>0</show_in_website>
<show_in_store>0</show_in_store>
</skin_merge_url>
</fields>
</secure>
<unsecure>
<fields>
<media_merge_url translate="label">
<label>Media Url for Merge js,css</label>
<frontend_type>text</frontend_type>
<sort_order>31</sort_order>
<show_in_default>0</show_in_default>
<show_in_website>0</show_in_website>
<show_in_store>0</show_in_store>
</media_merge_url>
<skin_merge_url translate="label">
<label>Skin Url for js,css Cloud</label>
<frontend_type>text</frontend_type>
<sort_order>31</sort_order>
<show_in_default>0</show_in_default>
<show_in_website>0</show_in_website>
<show_in_store>0</show_in_store>
</skin_merge_url>
</fields>
</unsecure>
</groups>
</web>
<onlinebiz_optimize translate="label" module="onlinebiz_optimize">
<label>Media Optimize</label>
<tab>advanced</tab>
<frontend_type>text</frontend_type>
<sort_order>120</sort_order>
<show_in_default>1</show_in_default>
<show_in_website>1</show_in_website>
<show_in_store>1</show_in_store>
<groups>
<hint>
<frontend_model>onlinebiz_optimize/adminhtml_config_source_hint</frontend_model>
<sort_order>0</sort_order>
<show_in_default>1</show_in_default>
<show_in_website>1</show_in_website>
<show_in_store>1</show_in_store>
</hint>
<general>
<label>General</label>
<frontend_type>text</frontend_type>
<sort_order>5</sort_order>
<show_in_default>1</show_in_default>
<show_in_website>1</show_in_website>
<show_in_store>1</show_in_store>
<expanded>1</expanded>
<fields>
<version translate="label">
<label>Use File Version</label>
<frontend_type>select</frontend_type>
<source_model>adminhtml/system_config_source_yesno</source_model>
<sort_order>5</sort_order>
<show_in_default>1</show_in_default>
<show_in_website>1</show_in_website>
<show_in_store>1</show_in_store>
<comment><![CDATA[We recommend that you use the file version for CSS, Js. To ensure the files are always in the latest version.]]></comment>
</version>
<csscompression>
<label>CSS Compression / Optimization</label>
<frontend_type>select</frontend_type>
<source_model>adminhtml/system_config_source_yesno</source_model>
<sort_order>10</sort_order>
<show_in_default>1</show_in_default>
<show_in_website>1</show_in_website>
<show_in_store>1</show_in_store>
<comment><![CDATA[If deactivated, the CSS files taken will be the original one, otherwise your compressed css files.]]></comment>
</csscompression>
<jscompression>
<label>Javascript Compression / Optimization</label>
<frontend_type>select</frontend_type>
<source_model>adminhtml/system_config_source_yesno</source_model>
<sort_order>15</sort_order>
<show_in_default>1</show_in_default>
<show_in_website>1</show_in_website>
<show_in_store>1</show_in_store>
<comment><![CDATA[If deactivated, Javascript CSS files taken will be the original one, otherwise your compressed javascript files.]]></comment>
</jscompression>
<critical>
<label>Critical CSS</label>
<frontend_type>select</frontend_type>
<source_model>adminhtml/system_config_source_yesno</source_model>
<sort_order>20</sort_order>
<show_in_default>1</show_in_default>
<show_in_website>1</show_in_website>
<show_in_store>1</show_in_store>
<comment><![CDATA[The critical CSS in your project is the CSS that’s used to style the above-the-fold content of your website. Above-the-fold content is what users see on your website first, which can include navigation and other elements. So it’s very important to properly style and render this part of the website as quickly as possible.]]></comment>
</critical>
<css_preload>
<label>CSS Preload</label>
<frontend_type>select</frontend_type>
<source_model>adminhtml/system_config_source_yesno</source_model>
<sort_order>25</sort_order>
<show_in_default>1</show_in_default>
<show_in_website>1</show_in_website>
<show_in_store>1</show_in_store>
<comment><![CDATA[A feature for loading CSS asynchronously.]]></comment>
</css_preload>
<move_to_bottom>
<label>Move to Bottom of the Page</label>
<comment><![CDATA[Move all CSS to just before the closing &lt;/body&gt; tag]]></comment>
<frontend_type>select</frontend_type>
<source_model>adminhtml/system_config_source_yesno</source_model>
<sort_order>30</sort_order>
<show_in_default>1</show_in_default>
<show_in_website>1</show_in_website>
<show_in_store>1</show_in_store>
</move_to_bottom>
<url_blacklist translate="label,comment">
<label>URL Blacklist</label>
<frontend_type>textarea</frontend_type>
<comment>List of relative URLs to exclude move css to bottom. Each URL per row</comment>
<sort_order>40</sort_order>
<show_in_default>1</show_in_default>
<show_in_website>0</show_in_website>
<show_in_store>0</show_in_store>
<depends><move_to_bottom>1</move_to_bottom></depends>
</url_blacklist>
</fields>
</general>
<cloudmedia>
<label>Cloud Media</label>
<frontend_type>text</frontend_type>
<sort_order>10</sort_order>
<show_in_default>1</show_in_default>
<show_in_website>1</show_in_website>
<show_in_store>1</show_in_store>
<expanded>1</expanded>
<fields>
<enable translate="label comment">
<label>Use Clould Media</label>
<frontend_type>select</frontend_type>
<source_model>adminhtml/system_config_source_yesno</source_model>
<sort_order>30</sort_order>
<show_in_default>1</show_in_default>
<show_in_website>1</show_in_website>
<show_in_store>1</show_in_store>
<comment><![CDATA[If you want used media from Cloud Server, please select Yes.]]></comment>
</enable>
<basejscss translate="label">
<label>Base JavaScript, CSS URL</label>
<frontend_type>text</frontend_type>
<sort_order>35</sort_order>
<show_in_default>1</show_in_default>
<show_in_website>1</show_in_website>
<show_in_store>1</show_in_store>
<depends>
<enable>1</enable>
</depends>
<comment><![CDATA[When using CDN, All JavaScript and CSS files will be get from this URL]]></comment>
</basejscss>
<url translate="label">
<label>Clould URL</label>
<frontend_type>text</frontend_type>
<sort_order>40</sort_order>
<show_in_default>1</show_in_default>
<show_in_website>1</show_in_website>
<show_in_store>1</show_in_store>
<depends>
<enable>1</enable>
</depends>
<comment><![CDATA[<strong style="color:red">Notice!</strong> This URL will be used for css, js files. Eg: Make sure that base URL ends without '/' (slash)]]></comment>
</url>
<options translate="label">
<label>Clould Options</label>
<frontend_type>text</frontend_type>
<sort_order>60</sort_order>
<show_in_default>1</show_in_default>
<show_in_website>1</show_in_website>
<show_in_store>1</show_in_store>
<depends>
<enable>1</enable>
</depends>
<comment><![CDATA[Optimize your images with options from Cloud Server.]]></comment>
</options>
<advanced_options translate="label">
<label>Advanced Clould Options</label>
<frontend_type>text</frontend_type>
<sort_order>70</sort_order>
<show_in_default>1</show_in_default>
<show_in_website>1</show_in_website>
<show_in_store>1</show_in_store>
<depends>
<enable>1</enable>
</depends>
<comment><![CDATA[Optimize your images with options from Cloud Server. This options will be only apply for product images on category pages and aw blocks]]></comment>
</advanced_options>
<cached translate="label">
<label>Disabled Cache</label>
<frontend_type>select</frontend_type>
<source_model>adminhtml/system_config_source_yesno</source_model>
<sort_order>80</sort_order>
<show_in_default>1</show_in_default>
<show_in_website>1</show_in_website>
<show_in_store>1</show_in_store>
<depends>
<enable>1</enable>
</depends>
<comment><![CDATA[Disabled use cache for product images.]]></comment>
</cached>
<devmode translate="label">
<label>Develop Test</label>
<frontend_type>select</frontend_type>
<source_model>adminhtml/system_config_source_yesno</source_model>
<sort_order>90</sort_order>
<show_in_default>1</show_in_default>
<show_in_website>1</show_in_website>
<show_in_store>1</show_in_store>
<depends>
<enable>1</enable>
</depends>
<comment><![CDATA[If method is enabled all image will be load from cloud without checking the existing image on server.]]></comment>
</devmode>
</fields>
</cloudmedia>
</groups>
</onlinebiz_optimize>
</sections>
</config>
<?xml version="1.0"?>
<config>
<modules>
<Onlinebiz_Optimize>
<active>true</active>
<codePool>local</codePool>
</Onlinebiz_Optimize>
</modules>
</config>
\ No newline at end of file
<?php
namespace MatthiasMullie\Minify;
use MatthiasMullie\Minify\Exceptions\FileImportException;
use MatthiasMullie\PathConverter\ConverterInterface;
use MatthiasMullie\PathConverter\Converter;
/**
* CSS minifier.
*
* Please report bugs on https://github.com/matthiasmullie/minify/issues
*
* @author Matthias Mullie <minify@mullie.eu>
* @author Tijs Verkoyen <minify@verkoyen.eu>
* @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
* @license MIT License
*/
class CSS extends Minify
{
/**
* @var int
*/
protected $maxImportSize = 5;
/**
* @var string[]
*/
protected $importExtensions = array(
'gif' => 'data:image/gif',
'png' => 'data:image/png',
'jpe' => 'data:image/jpeg',
'jpg' => 'data:image/jpeg',
'jpeg' => 'data:image/jpeg',
'svg' => 'data:image/svg+xml',
'woff' => 'data:application/x-font-woff',
'tif' => 'image/tiff',
'tiff' => 'image/tiff',
'xbm' => 'image/x-xbitmap',
);
/**
* Set the maximum size if files to be imported.
*
* Files larger than this size (in kB) will not be imported into the CSS.
* Importing files into the CSS as data-uri will save you some connections,
* but we should only import relatively small decorative images so that our
* CSS file doesn't get too bulky.
*
* @param int $size Size in kB
*/
public function setMaxImportSize($size)
{
$this->maxImportSize = $size;
}
/**
* Set the type of extensions to be imported into the CSS (to save network
* connections).
* Keys of the array should be the file extensions & respective values
* should be the data type.
*
* @param string[] $extensions Array of file extensions
*/
public function setImportExtensions(array $extensions)
{
$this->importExtensions = $extensions;
}
/**
* Move any import statements to the top.
*
* @param string $content Nearly finished CSS content
*
* @return string
*/
protected function moveImportsToTop($content)
{
if (preg_match_all('/@import[^;]+;/', $content, $matches)) {
// remove from content
foreach ($matches[0] as $import) {
$content = str_replace($import, '', $content);
}
// add to top
$content = implode('', $matches[0]).$content;
}
return $content;
}
/**
* Combine CSS from import statements.
*
* @import's will be loaded and their content merged into the original file,
* to save HTTP requests.
*
* @param string $source The file to combine imports for
* @param string $content The CSS content to combine imports for
* @param string[] $parents Parent paths, for circular reference checks
*
* @return string
*
* @throws FileImportException
*/
protected function combineImports($source, $content, $parents)
{
$importRegexes = array(
// @import url(xxx)
'/
# import statement
@import
# whitespace
\s+
# open url()
url\(
# (optional) open path enclosure
(?P<quotes>["\']?)
# fetch path
(?P<path>.+?)
# (optional) close path enclosure
(?P=quotes)
# close url()
\)
# (optional) trailing whitespace
\s*
# (optional) media statement(s)
(?P<media>[^;]*)
# (optional) trailing whitespace
\s*
# (optional) closing semi-colon
;?
/ix',
// @import 'xxx'
'/
# import statement
@import
# whitespace
\s+
# open path enclosure
(?P<quotes>["\'])
# fetch path
(?P<path>.+?)
# close path enclosure
(?P=quotes)
# (optional) trailing whitespace
\s*
# (optional) media statement(s)
(?P<media>[^;]*)
# (optional) trailing whitespace
\s*
# (optional) closing semi-colon
;?
/ix',
);
// find all relative imports in css
$matches = array();
foreach ($importRegexes as $importRegex) {
if (preg_match_all($importRegex, $content, $regexMatches, PREG_SET_ORDER)) {
$matches = array_merge($matches, $regexMatches);
}
}
$search = array();
$replace = array();
// loop the matches
foreach ($matches as $match) {
// get the path for the file that will be imported
$importPath = dirname($source).'/'.$match['path'];
// only replace the import with the content if we can grab the
// content of the file
if (!$this->canImportByPath($match['path']) || !$this->canImportFile($importPath)) {
continue;
}
// check if current file was not imported previously in the same
// import chain.
if (in_array($importPath, $parents)) {
throw new FileImportException('Failed to import file "'.$importPath.'": circular reference detected.');
}
// grab referenced file & minify it (which may include importing
// yet other @import statements recursively)
$minifier = new static($importPath);
$importContent = $minifier->execute($source, $parents);
// check if this is only valid for certain media
if (!empty($match['media'])) {
$importContent = '@media '.$match['media'].'{'.$importContent.'}';
}
// add to replacement array
$search[] = $match[0];
$replace[] = $importContent;
}
// replace the import statements
return str_replace($search, $replace, $content);
}
/**
* Import files into the CSS, base64-ized.
*
* @url(image.jpg) images will be loaded and their content merged into the
* original file, to save HTTP requests.
*
* @param string $source The file to import files for
* @param string $content The CSS content to import files for
*
* @return string
*/
protected function importFiles($source, $content)
{
$regex = '/url\((["\']?)(.+?)\\1\)/i';
if ($this->importExtensions && preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) {
$search = array();
$replace = array();
// loop the matches
foreach ($matches as $match) {
$extension = substr(strrchr($match[2], '.'), 1);
if ($extension && !array_key_exists($extension, $this->importExtensions)) {
continue;
}
// get the path for the file that will be imported
$path = $match[2];
$path = dirname($source).'/'.$path;
// only replace the import with the content if we're able to get
// the content of the file, and it's relatively small
if ($this->canImportFile($path) && $this->canImportBySize($path)) {
// grab content && base64-ize
$importContent = $this->load($path);
$importContent = base64_encode($importContent);
// build replacement
$search[] = $match[0];
$replace[] = 'url('.$this->importExtensions[$extension].';base64,'.$importContent.')';
}
}
// replace the import statements
$content = str_replace($search, $replace, $content);
}
return $content;
}
/**
* Minify the data.
* Perform CSS optimizations.
*
* @param string[optional] $path Path to write the data to
* @param string[] $parents Parent paths, for circular reference checks
*
* @return string The minified data
*/
public function execute($path = null, $parents = array())
{
$content = '';
// loop CSS data (raw data and files)
foreach ($this->data as $source => $css) {
/*
* Let's first take out strings & comments, since we can't just
* remove whitespace anywhere. If whitespace occurs inside a string,
* we should leave it alone. E.g.:
* p { content: "a test" }
*/
$this->extractStrings();
$this->stripComments();
$css = $this->replace($css);
$css = $this->stripWhitespace($css);
$css = $this->shortenHex($css);
$css = $this->shortenZeroes($css);
$css = $this->shortenFontWeights($css);
$css = $this->stripEmptyTags($css);
// restore the string we've extracted earlier
$css = $this->restoreExtractedData($css);
$source = is_int($source) ? '' : $source;
$parents = $source ? array_merge($parents, array($source)) : $parents;
$css = $this->combineImports($source, $css, $parents);
$css = $this->importFiles($source, $css);
/*
* If we'll save to a new path, we'll have to fix the relative paths
* to be relative no longer to the source file, but to the new path.
* If we don't write to a file, fall back to same path so no
* conversion happens (because we still want it to go through most
* of the move code, which also addresses url() & @import syntax...)
*/
$converter = $this->getPathConverter($source, $path ?: $source);
$css = $this->move($converter, $css);
// combine css
$content .= $css;
}
$content = $this->moveImportsToTop($content);
return $content;
}
/**
* Moving a css file should update all relative urls.
* Relative references (e.g. ../images/image.gif) in a certain css file,
* will have to be updated when a file is being saved at another location
* (e.g. ../../images/image.gif, if the new CSS file is 1 folder deeper).
*
* @param ConverterInterface $converter Relative path converter
* @param string $content The CSS content to update relative urls for
*
* @return string
*/
protected function move(ConverterInterface $converter, $content)
{
/*
* Relative path references will usually be enclosed by url(). @import
* is an exception, where url() is not necessary around the path (but is
* allowed).
* This *could* be 1 regular expression, where both regular expressions
* in this array are on different sides of a |. But we're using named
* patterns in both regexes, the same name on both regexes. This is only
* possible with a (?J) modifier, but that only works after a fairly
* recent PCRE version. That's why I'm doing 2 separate regular
* expressions & combining the matches after executing of both.
*/
$relativeRegexes = array(
// url(xxx)
'/
# open url()
url\(
\s*
# open path enclosure
(?P<quotes>["\'])?
# fetch path
(?P<path>.+?)
# close path enclosure
(?(quotes)(?P=quotes))
\s*
# close url()
\)
/ix',
// @import "xxx"
'/
# import statement
@import
# whitespace
\s+
# we don\'t have to check for @import url(), because the
# condition above will already catch these
# open path enclosure
(?P<quotes>["\'])
# fetch path
(?P<path>.+?)
# close path enclosure
(?P=quotes)
/ix',
);
// find all relative urls in css
$matches = array();
foreach ($relativeRegexes as $relativeRegex) {
if (preg_match_all($relativeRegex, $content, $regexMatches, PREG_SET_ORDER)) {
$matches = array_merge($matches, $regexMatches);
}
}
$search = array();
$replace = array();
// loop all urls
foreach ($matches as $match) {
// determine if it's a url() or an @import match
$type = (strpos($match[0], '@import') === 0 ? 'import' : 'url');
$url = $match['path'];
if ($this->canImportByPath($url)) {
// attempting to interpret GET-params makes no sense, so let's discard them for awhile
$params = strrchr($url, '?');
$url = $params ? substr($url, 0, -strlen($params)) : $url;
// fix relative url
$url = $converter->convert($url);
// now that the path has been converted, re-apply GET-params
$url .= $params;
}
/*
* Urls with control characters above 0x7e should be quoted.
* According to Mozilla's parser, whitespace is only allowed at the
* end of unquoted urls.
* Urls with `)` (as could happen with data: uris) should also be
* quoted to avoid being confused for the url() closing parentheses.
* And urls with a # have also been reported to cause issues.
*
* @see https://developer.mozilla.org/nl/docs/Web/CSS/url#The_url()_functional_notation
* @see https://hg.mozilla.org/mozilla-central/rev/14abca4e7378
*/
$url = trim($url);
if (preg_match('/[\s\)#\x{7f}-\x{9f}]/u', $url)) {
$url = $match['quotes'] . $url . $match['quotes'];
}
// build replacement
$search[] = $match[0];
if ($type === 'url') {
$replace[] = 'url('.$url.')';
} elseif ($type === 'import') {
$replace[] = '@import "'.$url.'"';
}
}
// replace urls
return str_replace($search, $replace, $content);
}
/**
* Shorthand hex color codes.
* #FF0000 -> #F00.
*
* @param string $content The CSS content to shorten the hex color codes for
*
* @return string
*/
protected function shortenHex($content)
{
$content = preg_replace('/(?<=[: ])#([0-9a-z])\\1([0-9a-z])\\2([0-9a-z])\\3(?=[; }])/i', '#$1$2$3', $content);
// we can shorten some even more by replacing them with their color name
$colors = array(
'#F0FFFF' => 'azure',
'#F5F5DC' => 'beige',
'#A52A2A' => 'brown',
'#FF7F50' => 'coral',
'#FFD700' => 'gold',
'#808080' => 'gray',
'#008000' => 'green',
'#4B0082' => 'indigo',
'#FFFFF0' => 'ivory',
'#F0E68C' => 'khaki',
'#FAF0E6' => 'linen',
'#800000' => 'maroon',
'#000080' => 'navy',
'#808000' => 'olive',
'#CD853F' => 'peru',
'#FFC0CB' => 'pink',
'#DDA0DD' => 'plum',
'#800080' => 'purple',
'#F00' => 'red',
'#FA8072' => 'salmon',
'#A0522D' => 'sienna',
'#C0C0C0' => 'silver',
'#FFFAFA' => 'snow',
'#D2B48C' => 'tan',
'#FF6347' => 'tomato',
'#EE82EE' => 'violet',
'#F5DEB3' => 'wheat',
);
return preg_replace_callback(
'/(?<=[: ])('.implode(array_keys($colors), '|').')(?=[; }])/i',
function ($match) use ($colors) {
return $colors[strtoupper($match[0])];
},
$content
);
}
/**
* Shorten CSS font weights.
*
* @param string $content The CSS content to shorten the font weights for
*
* @return string
*/
protected function shortenFontWeights($content)
{
$weights = array(
'normal' => 400,
'bold' => 700,
);
$callback = function ($match) use ($weights) {
return $match[1].$weights[$match[2]];
};
return preg_replace_callback('/(font-weight\s*:\s*)('.implode('|', array_keys($weights)).')(?=[;}])/', $callback, $content);
}
/**
* Shorthand 0 values to plain 0, instead of e.g. -0em.
*
* @param string $content The CSS content to shorten the zero values for
*
* @return string
*/
protected function shortenZeroes($content)
{
// reusable bits of code throughout these regexes:
// before & after are used to make sure we don't match lose unintended
// 0-like values (e.g. in #000, or in http://url/1.0)
// units can be stripped from 0 values, or used to recognize non 0
// values (where wa may be able to strip a .0 suffix)
$before = '(?<=[:(, ])';
$after = '(?=[ ,);}])';
$units = '(em|ex|%|px|cm|mm|in|pt|pc|ch|rem|vh|vw|vmin|vmax|vm)';
// strip units after zeroes (0px -> 0)
// NOTE: it should be safe to remove all units for a 0 value, but in
// practice, Webkit (especially Safari) seems to stumble over at least
// 0%, potentially other units as well. Only stripping 'px' for now.
// @see https://github.com/matthiasmullie/minify/issues/60
$content = preg_replace('/'.$before.'(-?0*(\.0+)?)(?<=0)px'.$after.'/', '\\1', $content);
// strip 0-digits (.0 -> 0)
$content = preg_replace('/'.$before.'\.0+'.$units.'?'.$after.'/', '0\\1', $content);
// strip trailing 0: 50.10 -> 50.1, 50.10px -> 50.1px
$content = preg_replace('/'.$before.'(-?[0-9]+\.[0-9]+)0+'.$units.'?'.$after.'/', '\\1\\2', $content);
// strip trailing 0: 50.00 -> 50, 50.00px -> 50px
$content = preg_replace('/'.$before.'(-?[0-9]+)\.0+'.$units.'?'.$after.'/', '\\1\\2', $content);
// strip leading 0: 0.1 -> .1, 01.1 -> 1.1
$content = preg_replace('/'.$before.'(-?)0+([0-9]*\.[0-9]+)'.$units.'?'.$after.'/', '\\1\\2\\3', $content);
// strip negative zeroes (-0 -> 0) & truncate zeroes (00 -> 0)
$content = preg_replace('/'.$before.'-?0+'.$units.'?'.$after.'/', '0\\1', $content);
// remove zeroes where they make no sense in calc: e.g. calc(100px - 0)
// the 0 doesn't have any effect, and this isn't even valid without unit
// strip all `+ 0` or `- 0` occurrences: calc(10% + 0) -> calc(10%)
// looped because there may be multiple 0s inside 1 group of parentheses
do {
$previous = $content;
$content = preg_replace('/\(([^\(\)]+) [\+\-] 0( [^\(\)]+)?\)/', '(\\1\\2)', $content);
} while ($content !== $previous);
// strip all `0 +` occurrences: calc(0 + 10%) -> calc(10%)
$content = preg_replace('/\(0 \+ ([^\(\)]+)\)/', '(\\1)', $content);
// strip all `0 -` occurrences: calc(0 - 10%) -> calc(-10%)
$content = preg_replace('/\(0 \- ([^\(\)]+)\)/', '(-\\1)', $content);
// I'm not going to attempt to optimize away `x * 0` instances:
// it's dumb enough code already that it likely won't occur, and it's
// too complex to do right (order of operations would have to be
// respected etc)
// what I cared about most here was fixing incorrectly truncated units
// IE doesn't seem to understand a unitless flex-basis value, so let's
// add it in again (make it `%`, which is only 1 char: 0%, 0px, 0
// anything, it's all just the same)
$content = preg_replace('/flex:([^ ]+ [^ ]+ )0([;\}])/', 'flex:${1}0%${2}', $content);
$content = preg_replace('/flex-basis:0([;\}])/', 'flex-basis:0%${1}', $content);
return $content;
}
/**
* Strip comments from source code.
*
* @param string $content
*
* @return string
*/
protected function stripEmptyTags($content)
{
return preg_replace('/(^|\}|;)[^\{\};]+\{\s*\}/', '\\1', $content);
}
/**
* Strip comments from source code.
*/
protected function stripComments()
{
$this->registerPattern('/\/\*.*?\*\//s', '');
}
/**
* Strip whitespace.
*
* @param string $content The CSS content to strip the whitespace for
*
* @return string
*/
protected function stripWhitespace($content)
{
// remove leading & trailing whitespace
$content = preg_replace('/^\s*/m', '', $content);
$content = preg_replace('/\s*$/m', '', $content);
// replace newlines with a single space
$content = preg_replace('/\s+/', ' ', $content);
// remove whitespace around meta characters
// inspired by stackoverflow.com/questions/15195750/minify-compress-css-with-regex
$content = preg_replace('/\s*([\*$~^|]?+=|[{};,>~]|!important\b)\s*/', '$1', $content);
$content = preg_replace('/([\[(:])\s+/', '$1', $content);
$content = preg_replace('/\s+([\]\)])/', '$1', $content);
$content = preg_replace('/\s+(:)(?![^\}]*\{)/', '$1', $content);
// whitespace around + and - can only be stripped in selectors, like
// :nth-child(3+2n), not in things like calc(3px + 2px) or shorthands
// like 3px -2px
$content = preg_replace('/\s*([+-])\s*(?=[^}]*{)/', '$1', $content);
// remove semicolon/whitespace followed by closing bracket
$content = str_replace(';}', '}', $content);
return trim($content);
}
/**
* Check if file is small enough to be imported.
*
* @param string $path The path to the file
*
* @return bool
*/
protected function canImportBySize($path)
{
return ($size = @filesize($path)) && $size <= $this->maxImportSize * 1024;
}
/**
* Check if file a file can be imported, going by the path.
*
* @param string $path
*
* @return bool
*/
protected function canImportByPath($path)
{
return preg_match('/^(data:|https?:|\\/)/', $path) === 0;
}
/**
* Return a converter to update relative paths to be relative to the new
* destination.
*
* @param string $source
* @param string $target
*
* @return ConverterInterface
*/
protected function getPathConverter($source, $target)
{
return new Converter($source, $target);
}
}
<?php
namespace MatthiasMullie\Minify;
/**
* @deprecated Use Exceptions\BasicException instead
*
* @author Matthias Mullie <minify@mullie.eu>
*/
abstract class Exception extends \Exception
{
}
<?php
namespace MatthiasMullie\Minify\Exceptions;
use MatthiasMullie\Minify\Exception;
/**
* @author Matthias Mullie <minify@mullie.eu>
*/
abstract class BasicException extends Exception
{
}
<?php
namespace MatthiasMullie\Minify\Exceptions;
/**
* @author Matthias Mullie <minify@mullie.eu>
*/
class FileImportException extends BasicException
{
}
<?php
namespace MatthiasMullie\Minify\Exceptions;
/**
* @author Matthias Mullie <minify@mullie.eu>
*/
class IOException extends BasicException
{
}
<?php
namespace MatthiasMullie\Minify;
/**
* JavaScript minifier.
*
* Please report bugs on https://github.com/matthiasmullie/minify/issues
*
* @author Matthias Mullie <minify@mullie.eu>
* @author Tijs Verkoyen <minify@verkoyen.eu>
* @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
* @license MIT License
*/
class JS extends Minify
{
/**
* Var-matching regex based on http://stackoverflow.com/a/9337047/802993.
*
* Note that regular expressions using that bit must have the PCRE_UTF8
* pattern modifier (/u) set.
*
* @var string
*/
const REGEX_VARIABLE = '\b[$A-Z\_a-z\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\x{02c1}\x{02c6}-\x{02d1}\x{02e0}-\x{02e4}\x{02ec}\x{02ee}\x{0370}-\x{0374}\x{0376}\x{0377}\x{037a}-\x{037d}\x{0386}\x{0388}-\x{038a}\x{038c}\x{038e}-\x{03a1}\x{03a3}-\x{03f5}\x{03f7}-\x{0481}\x{048a}-\x{0527}\x{0531}-\x{0556}\x{0559}\x{0561}-\x{0587}\x{05d0}-\x{05ea}\x{05f0}-\x{05f2}\x{0620}-\x{064a}\x{066e}\x{066f}\x{0671}-\x{06d3}\x{06d5}\x{06e5}\x{06e6}\x{06ee}\x{06ef}\x{06fa}-\x{06fc}\x{06ff}\x{0710}\x{0712}-\x{072f}\x{074d}-\x{07a5}\x{07b1}\x{07ca}-\x{07ea}\x{07f4}\x{07f5}\x{07fa}\x{0800}-\x{0815}\x{081a}\x{0824}\x{0828}\x{0840}-\x{0858}\x{08a0}\x{08a2}-\x{08ac}\x{0904}-\x{0939}\x{093d}\x{0950}\x{0958}-\x{0961}\x{0971}-\x{0977}\x{0979}-\x{097f}\x{0985}-\x{098c}\x{098f}\x{0990}\x{0993}-\x{09a8}\x{09aa}-\x{09b0}\x{09b2}\x{09b6}-\x{09b9}\x{09bd}\x{09ce}\x{09dc}\x{09dd}\x{09df}-\x{09e1}\x{09f0}\x{09f1}\x{0a05}-\x{0a0a}\x{0a0f}\x{0a10}\x{0a13}-\x{0a28}\x{0a2a}-\x{0a30}\x{0a32}\x{0a33}\x{0a35}\x{0a36}\x{0a38}\x{0a39}\x{0a59}-\x{0a5c}\x{0a5e}\x{0a72}-\x{0a74}\x{0a85}-\x{0a8d}\x{0a8f}-\x{0a91}\x{0a93}-\x{0aa8}\x{0aaa}-\x{0ab0}\x{0ab2}\x{0ab3}\x{0ab5}-\x{0ab9}\x{0abd}\x{0ad0}\x{0ae0}\x{0ae1}\x{0b05}-\x{0b0c}\x{0b0f}\x{0b10}\x{0b13}-\x{0b28}\x{0b2a}-\x{0b30}\x{0b32}\x{0b33}\x{0b35}-\x{0b39}\x{0b3d}\x{0b5c}\x{0b5d}\x{0b5f}-\x{0b61}\x{0b71}\x{0b83}\x{0b85}-\x{0b8a}\x{0b8e}-\x{0b90}\x{0b92}-\x{0b95}\x{0b99}\x{0b9a}\x{0b9c}\x{0b9e}\x{0b9f}\x{0ba3}\x{0ba4}\x{0ba8}-\x{0baa}\x{0bae}-\x{0bb9}\x{0bd0}\x{0c05}-\x{0c0c}\x{0c0e}-\x{0c10}\x{0c12}-\x{0c28}\x{0c2a}-\x{0c33}\x{0c35}-\x{0c39}\x{0c3d}\x{0c58}\x{0c59}\x{0c60}\x{0c61}\x{0c85}-\x{0c8c}\x{0c8e}-\x{0c90}\x{0c92}-\x{0ca8}\x{0caa}-\x{0cb3}\x{0cb5}-\x{0cb9}\x{0cbd}\x{0cde}\x{0ce0}\x{0ce1}\x{0cf1}\x{0cf2}\x{0d05}-\x{0d0c}\x{0d0e}-\x{0d10}\x{0d12}-\x{0d3a}\x{0d3d}\x{0d4e}\x{0d60}\x{0d61}\x{0d7a}-\x{0d7f}\x{0d85}-\x{0d96}\x{0d9a}-\x{0db1}\x{0db3}-\x{0dbb}\x{0dbd}\x{0dc0}-\x{0dc6}\x{0e01}-\x{0e30}\x{0e32}\x{0e33}\x{0e40}-\x{0e46}\x{0e81}\x{0e82}\x{0e84}\x{0e87}\x{0e88}\x{0e8a}\x{0e8d}\x{0e94}-\x{0e97}\x{0e99}-\x{0e9f}\x{0ea1}-\x{0ea3}\x{0ea5}\x{0ea7}\x{0eaa}\x{0eab}\x{0ead}-\x{0eb0}\x{0eb2}\x{0eb3}\x{0ebd}\x{0ec0}-\x{0ec4}\x{0ec6}\x{0edc}-\x{0edf}\x{0f00}\x{0f40}-\x{0f47}\x{0f49}-\x{0f6c}\x{0f88}-\x{0f8c}\x{1000}-\x{102a}\x{103f}\x{1050}-\x{1055}\x{105a}-\x{105d}\x{1061}\x{1065}\x{1066}\x{106e}-\x{1070}\x{1075}-\x{1081}\x{108e}\x{10a0}-\x{10c5}\x{10c7}\x{10cd}\x{10d0}-\x{10fa}\x{10fc}-\x{1248}\x{124a}-\x{124d}\x{1250}-\x{1256}\x{1258}\x{125a}-\x{125d}\x{1260}-\x{1288}\x{128a}-\x{128d}\x{1290}-\x{12b0}\x{12b2}-\x{12b5}\x{12b8}-\x{12be}\x{12c0}\x{12c2}-\x{12c5}\x{12c8}-\x{12d6}\x{12d8}-\x{1310}\x{1312}-\x{1315}\x{1318}-\x{135a}\x{1380}-\x{138f}\x{13a0}-\x{13f4}\x{1401}-\x{166c}\x{166f}-\x{167f}\x{1681}-\x{169a}\x{16a0}-\x{16ea}\x{16ee}-\x{16f0}\x{1700}-\x{170c}\x{170e}-\x{1711}\x{1720}-\x{1731}\x{1740}-\x{1751}\x{1760}-\x{176c}\x{176e}-\x{1770}\x{1780}-\x{17b3}\x{17d7}\x{17dc}\x{1820}-\x{1877}\x{1880}-\x{18a8}\x{18aa}\x{18b0}-\x{18f5}\x{1900}-\x{191c}\x{1950}-\x{196d}\x{1970}-\x{1974}\x{1980}-\x{19ab}\x{19c1}-\x{19c7}\x{1a00}-\x{1a16}\x{1a20}-\x{1a54}\x{1aa7}\x{1b05}-\x{1b33}\x{1b45}-\x{1b4b}\x{1b83}-\x{1ba0}\x{1bae}\x{1baf}\x{1bba}-\x{1be5}\x{1c00}-\x{1c23}\x{1c4d}-\x{1c4f}\x{1c5a}-\x{1c7d}\x{1ce9}-\x{1cec}\x{1cee}-\x{1cf1}\x{1cf5}\x{1cf6}\x{1d00}-\x{1dbf}\x{1e00}-\x{1f15}\x{1f18}-\x{1f1d}\x{1f20}-\x{1f45}\x{1f48}-\x{1f4d}\x{1f50}-\x{1f57}\x{1f59}\x{1f5b}\x{1f5d}\x{1f5f}-\x{1f7d}\x{1f80}-\x{1fb4}\x{1fb6}-\x{1fbc}\x{1fbe}\x{1fc2}-\x{1fc4}\x{1fc6}-\x{1fcc}\x{1fd0}-\x{1fd3}\x{1fd6}-\x{1fdb}\x{1fe0}-\x{1fec}\x{1ff2}-\x{1ff4}\x{1ff6}-\x{1ffc}\x{2071}\x{207f}\x{2090}-\x{209c}\x{2102}\x{2107}\x{210a}-\x{2113}\x{2115}\x{2119}-\x{211d}\x{2124}\x{2126}\x{2128}\x{212a}-\x{212d}\x{212f}-\x{2139}\x{213c}-\x{213f}\x{2145}-\x{2149}\x{214e}\x{2160}-\x{2188}\x{2c00}-\x{2c2e}\x{2c30}-\x{2c5e}\x{2c60}-\x{2ce4}\x{2ceb}-\x{2cee}\x{2cf2}\x{2cf3}\x{2d00}-\x{2d25}\x{2d27}\x{2d2d}\x{2d30}-\x{2d67}\x{2d6f}\x{2d80}-\x{2d96}\x{2da0}-\x{2da6}\x{2da8}-\x{2dae}\x{2db0}-\x{2db6}\x{2db8}-\x{2dbe}\x{2dc0}-\x{2dc6}\x{2dc8}-\x{2dce}\x{2dd0}-\x{2dd6}\x{2dd8}-\x{2dde}\x{2e2f}\x{3005}-\x{3007}\x{3021}-\x{3029}\x{3031}-\x{3035}\x{3038}-\x{303c}\x{3041}-\x{3096}\x{309d}-\x{309f}\x{30a1}-\x{30fa}\x{30fc}-\x{30ff}\x{3105}-\x{312d}\x{3131}-\x{318e}\x{31a0}-\x{31ba}\x{31f0}-\x{31ff}\x{3400}-\x{4db5}\x{4e00}-\x{9fcc}\x{a000}-\x{a48c}\x{a4d0}-\x{a4fd}\x{a500}-\x{a60c}\x{a610}-\x{a61f}\x{a62a}\x{a62b}\x{a640}-\x{a66e}\x{a67f}-\x{a697}\x{a6a0}-\x{a6ef}\x{a717}-\x{a71f}\x{a722}-\x{a788}\x{a78b}-\x{a78e}\x{a790}-\x{a793}\x{a7a0}-\x{a7aa}\x{a7f8}-\x{a801}\x{a803}-\x{a805}\x{a807}-\x{a80a}\x{a80c}-\x{a822}\x{a840}-\x{a873}\x{a882}-\x{a8b3}\x{a8f2}-\x{a8f7}\x{a8fb}\x{a90a}-\x{a925}\x{a930}-\x{a946}\x{a960}-\x{a97c}\x{a984}-\x{a9b2}\x{a9cf}\x{aa00}-\x{aa28}\x{aa40}-\x{aa42}\x{aa44}-\x{aa4b}\x{aa60}-\x{aa76}\x{aa7a}\x{aa80}-\x{aaaf}\x{aab1}\x{aab5}\x{aab6}\x{aab9}-\x{aabd}\x{aac0}\x{aac2}\x{aadb}-\x{aadd}\x{aae0}-\x{aaea}\x{aaf2}-\x{aaf4}\x{ab01}-\x{ab06}\x{ab09}-\x{ab0e}\x{ab11}-\x{ab16}\x{ab20}-\x{ab26}\x{ab28}-\x{ab2e}\x{abc0}-\x{abe2}\x{ac00}-\x{d7a3}\x{d7b0}-\x{d7c6}\x{d7cb}-\x{d7fb}\x{f900}-\x{fa6d}\x{fa70}-\x{fad9}\x{fb00}-\x{fb06}\x{fb13}-\x{fb17}\x{fb1d}\x{fb1f}-\x{fb28}\x{fb2a}-\x{fb36}\x{fb38}-\x{fb3c}\x{fb3e}\x{fb40}\x{fb41}\x{fb43}\x{fb44}\x{fb46}-\x{fbb1}\x{fbd3}-\x{fd3d}\x{fd50}-\x{fd8f}\x{fd92}-\x{fdc7}\x{fdf0}-\x{fdfb}\x{fe70}-\x{fe74}\x{fe76}-\x{fefc}\x{ff21}-\x{ff3a}\x{ff41}-\x{ff5a}\x{ff66}-\x{ffbe}\x{ffc2}-\x{ffc7}\x{ffca}-\x{ffcf}\x{ffd2}-\x{ffd7}\x{ffda}-\x{ffdc}][$A-Z\_a-z\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\x{02c1}\x{02c6}-\x{02d1}\x{02e0}-\x{02e4}\x{02ec}\x{02ee}\x{0370}-\x{0374}\x{0376}\x{0377}\x{037a}-\x{037d}\x{0386}\x{0388}-\x{038a}\x{038c}\x{038e}-\x{03a1}\x{03a3}-\x{03f5}\x{03f7}-\x{0481}\x{048a}-\x{0527}\x{0531}-\x{0556}\x{0559}\x{0561}-\x{0587}\x{05d0}-\x{05ea}\x{05f0}-\x{05f2}\x{0620}-\x{064a}\x{066e}\x{066f}\x{0671}-\x{06d3}\x{06d5}\x{06e5}\x{06e6}\x{06ee}\x{06ef}\x{06fa}-\x{06fc}\x{06ff}\x{0710}\x{0712}-\x{072f}\x{074d}-\x{07a5}\x{07b1}\x{07ca}-\x{07ea}\x{07f4}\x{07f5}\x{07fa}\x{0800}-\x{0815}\x{081a}\x{0824}\x{0828}\x{0840}-\x{0858}\x{08a0}\x{08a2}-\x{08ac}\x{0904}-\x{0939}\x{093d}\x{0950}\x{0958}-\x{0961}\x{0971}-\x{0977}\x{0979}-\x{097f}\x{0985}-\x{098c}\x{098f}\x{0990}\x{0993}-\x{09a8}\x{09aa}-\x{09b0}\x{09b2}\x{09b6}-\x{09b9}\x{09bd}\x{09ce}\x{09dc}\x{09dd}\x{09df}-\x{09e1}\x{09f0}\x{09f1}\x{0a05}-\x{0a0a}\x{0a0f}\x{0a10}\x{0a13}-\x{0a28}\x{0a2a}-\x{0a30}\x{0a32}\x{0a33}\x{0a35}\x{0a36}\x{0a38}\x{0a39}\x{0a59}-\x{0a5c}\x{0a5e}\x{0a72}-\x{0a74}\x{0a85}-\x{0a8d}\x{0a8f}-\x{0a91}\x{0a93}-\x{0aa8}\x{0aaa}-\x{0ab0}\x{0ab2}\x{0ab3}\x{0ab5}-\x{0ab9}\x{0abd}\x{0ad0}\x{0ae0}\x{0ae1}\x{0b05}-\x{0b0c}\x{0b0f}\x{0b10}\x{0b13}-\x{0b28}\x{0b2a}-\x{0b30}\x{0b32}\x{0b33}\x{0b35}-\x{0b39}\x{0b3d}\x{0b5c}\x{0b5d}\x{0b5f}-\x{0b61}\x{0b71}\x{0b83}\x{0b85}-\x{0b8a}\x{0b8e}-\x{0b90}\x{0b92}-\x{0b95}\x{0b99}\x{0b9a}\x{0b9c}\x{0b9e}\x{0b9f}\x{0ba3}\x{0ba4}\x{0ba8}-\x{0baa}\x{0bae}-\x{0bb9}\x{0bd0}\x{0c05}-\x{0c0c}\x{0c0e}-\x{0c10}\x{0c12}-\x{0c28}\x{0c2a}-\x{0c33}\x{0c35}-\x{0c39}\x{0c3d}\x{0c58}\x{0c59}\x{0c60}\x{0c61}\x{0c85}-\x{0c8c}\x{0c8e}-\x{0c90}\x{0c92}-\x{0ca8}\x{0caa}-\x{0cb3}\x{0cb5}-\x{0cb9}\x{0cbd}\x{0cde}\x{0ce0}\x{0ce1}\x{0cf1}\x{0cf2}\x{0d05}-\x{0d0c}\x{0d0e}-\x{0d10}\x{0d12}-\x{0d3a}\x{0d3d}\x{0d4e}\x{0d60}\x{0d61}\x{0d7a}-\x{0d7f}\x{0d85}-\x{0d96}\x{0d9a}-\x{0db1}\x{0db3}-\x{0dbb}\x{0dbd}\x{0dc0}-\x{0dc6}\x{0e01}-\x{0e30}\x{0e32}\x{0e33}\x{0e40}-\x{0e46}\x{0e81}\x{0e82}\x{0e84}\x{0e87}\x{0e88}\x{0e8a}\x{0e8d}\x{0e94}-\x{0e97}\x{0e99}-\x{0e9f}\x{0ea1}-\x{0ea3}\x{0ea5}\x{0ea7}\x{0eaa}\x{0eab}\x{0ead}-\x{0eb0}\x{0eb2}\x{0eb3}\x{0ebd}\x{0ec0}-\x{0ec4}\x{0ec6}\x{0edc}-\x{0edf}\x{0f00}\x{0f40}-\x{0f47}\x{0f49}-\x{0f6c}\x{0f88}-\x{0f8c}\x{1000}-\x{102a}\x{103f}\x{1050}-\x{1055}\x{105a}-\x{105d}\x{1061}\x{1065}\x{1066}\x{106e}-\x{1070}\x{1075}-\x{1081}\x{108e}\x{10a0}-\x{10c5}\x{10c7}\x{10cd}\x{10d0}-\x{10fa}\x{10fc}-\x{1248}\x{124a}-\x{124d}\x{1250}-\x{1256}\x{1258}\x{125a}-\x{125d}\x{1260}-\x{1288}\x{128a}-\x{128d}\x{1290}-\x{12b0}\x{12b2}-\x{12b5}\x{12b8}-\x{12be}\x{12c0}\x{12c2}-\x{12c5}\x{12c8}-\x{12d6}\x{12d8}-\x{1310}\x{1312}-\x{1315}\x{1318}-\x{135a}\x{1380}-\x{138f}\x{13a0}-\x{13f4}\x{1401}-\x{166c}\x{166f}-\x{167f}\x{1681}-\x{169a}\x{16a0}-\x{16ea}\x{16ee}-\x{16f0}\x{1700}-\x{170c}\x{170e}-\x{1711}\x{1720}-\x{1731}\x{1740}-\x{1751}\x{1760}-\x{176c}\x{176e}-\x{1770}\x{1780}-\x{17b3}\x{17d7}\x{17dc}\x{1820}-\x{1877}\x{1880}-\x{18a8}\x{18aa}\x{18b0}-\x{18f5}\x{1900}-\x{191c}\x{1950}-\x{196d}\x{1970}-\x{1974}\x{1980}-\x{19ab}\x{19c1}-\x{19c7}\x{1a00}-\x{1a16}\x{1a20}-\x{1a54}\x{1aa7}\x{1b05}-\x{1b33}\x{1b45}-\x{1b4b}\x{1b83}-\x{1ba0}\x{1bae}\x{1baf}\x{1bba}-\x{1be5}\x{1c00}-\x{1c23}\x{1c4d}-\x{1c4f}\x{1c5a}-\x{1c7d}\x{1ce9}-\x{1cec}\x{1cee}-\x{1cf1}\x{1cf5}\x{1cf6}\x{1d00}-\x{1dbf}\x{1e00}-\x{1f15}\x{1f18}-\x{1f1d}\x{1f20}-\x{1f45}\x{1f48}-\x{1f4d}\x{1f50}-\x{1f57}\x{1f59}\x{1f5b}\x{1f5d}\x{1f5f}-\x{1f7d}\x{1f80}-\x{1fb4}\x{1fb6}-\x{1fbc}\x{1fbe}\x{1fc2}-\x{1fc4}\x{1fc6}-\x{1fcc}\x{1fd0}-\x{1fd3}\x{1fd6}-\x{1fdb}\x{1fe0}-\x{1fec}\x{1ff2}-\x{1ff4}\x{1ff6}-\x{1ffc}\x{2071}\x{207f}\x{2090}-\x{209c}\x{2102}\x{2107}\x{210a}-\x{2113}\x{2115}\x{2119}-\x{211d}\x{2124}\x{2126}\x{2128}\x{212a}-\x{212d}\x{212f}-\x{2139}\x{213c}-\x{213f}\x{2145}-\x{2149}\x{214e}\x{2160}-\x{2188}\x{2c00}-\x{2c2e}\x{2c30}-\x{2c5e}\x{2c60}-\x{2ce4}\x{2ceb}-\x{2cee}\x{2cf2}\x{2cf3}\x{2d00}-\x{2d25}\x{2d27}\x{2d2d}\x{2d30}-\x{2d67}\x{2d6f}\x{2d80}-\x{2d96}\x{2da0}-\x{2da6}\x{2da8}-\x{2dae}\x{2db0}-\x{2db6}\x{2db8}-\x{2dbe}\x{2dc0}-\x{2dc6}\x{2dc8}-\x{2dce}\x{2dd0}-\x{2dd6}\x{2dd8}-\x{2dde}\x{2e2f}\x{3005}-\x{3007}\x{3021}-\x{3029}\x{3031}-\x{3035}\x{3038}-\x{303c}\x{3041}-\x{3096}\x{309d}-\x{309f}\x{30a1}-\x{30fa}\x{30fc}-\x{30ff}\x{3105}-\x{312d}\x{3131}-\x{318e}\x{31a0}-\x{31ba}\x{31f0}-\x{31ff}\x{3400}-\x{4db5}\x{4e00}-\x{9fcc}\x{a000}-\x{a48c}\x{a4d0}-\x{a4fd}\x{a500}-\x{a60c}\x{a610}-\x{a61f}\x{a62a}\x{a62b}\x{a640}-\x{a66e}\x{a67f}-\x{a697}\x{a6a0}-\x{a6ef}\x{a717}-\x{a71f}\x{a722}-\x{a788}\x{a78b}-\x{a78e}\x{a790}-\x{a793}\x{a7a0}-\x{a7aa}\x{a7f8}-\x{a801}\x{a803}-\x{a805}\x{a807}-\x{a80a}\x{a80c}-\x{a822}\x{a840}-\x{a873}\x{a882}-\x{a8b3}\x{a8f2}-\x{a8f7}\x{a8fb}\x{a90a}-\x{a925}\x{a930}-\x{a946}\x{a960}-\x{a97c}\x{a984}-\x{a9b2}\x{a9cf}\x{aa00}-\x{aa28}\x{aa40}-\x{aa42}\x{aa44}-\x{aa4b}\x{aa60}-\x{aa76}\x{aa7a}\x{aa80}-\x{aaaf}\x{aab1}\x{aab5}\x{aab6}\x{aab9}-\x{aabd}\x{aac0}\x{aac2}\x{aadb}-\x{aadd}\x{aae0}-\x{aaea}\x{aaf2}-\x{aaf4}\x{ab01}-\x{ab06}\x{ab09}-\x{ab0e}\x{ab11}-\x{ab16}\x{ab20}-\x{ab26}\x{ab28}-\x{ab2e}\x{abc0}-\x{abe2}\x{ac00}-\x{d7a3}\x{d7b0}-\x{d7c6}\x{d7cb}-\x{d7fb}\x{f900}-\x{fa6d}\x{fa70}-\x{fad9}\x{fb00}-\x{fb06}\x{fb13}-\x{fb17}\x{fb1d}\x{fb1f}-\x{fb28}\x{fb2a}-\x{fb36}\x{fb38}-\x{fb3c}\x{fb3e}\x{fb40}\x{fb41}\x{fb43}\x{fb44}\x{fb46}-\x{fbb1}\x{fbd3}-\x{fd3d}\x{fd50}-\x{fd8f}\x{fd92}-\x{fdc7}\x{fdf0}-\x{fdfb}\x{fe70}-\x{fe74}\x{fe76}-\x{fefc}\x{ff21}-\x{ff3a}\x{ff41}-\x{ff5a}\x{ff66}-\x{ffbe}\x{ffc2}-\x{ffc7}\x{ffca}-\x{ffcf}\x{ffd2}-\x{ffd7}\x{ffda}-\x{ffdc}0-9\x{0300}-\x{036f}\x{0483}-\x{0487}\x{0591}-\x{05bd}\x{05bf}\x{05c1}\x{05c2}\x{05c4}\x{05c5}\x{05c7}\x{0610}-\x{061a}\x{064b}-\x{0669}\x{0670}\x{06d6}-\x{06dc}\x{06df}-\x{06e4}\x{06e7}\x{06e8}\x{06ea}-\x{06ed}\x{06f0}-\x{06f9}\x{0711}\x{0730}-\x{074a}\x{07a6}-\x{07b0}\x{07c0}-\x{07c9}\x{07eb}-\x{07f3}\x{0816}-\x{0819}\x{081b}-\x{0823}\x{0825}-\x{0827}\x{0829}-\x{082d}\x{0859}-\x{085b}\x{08e4}-\x{08fe}\x{0900}-\x{0903}\x{093a}-\x{093c}\x{093e}-\x{094f}\x{0951}-\x{0957}\x{0962}\x{0963}\x{0966}-\x{096f}\x{0981}-\x{0983}\x{09bc}\x{09be}-\x{09c4}\x{09c7}\x{09c8}\x{09cb}-\x{09cd}\x{09d7}\x{09e2}\x{09e3}\x{09e6}-\x{09ef}\x{0a01}-\x{0a03}\x{0a3c}\x{0a3e}-\x{0a42}\x{0a47}\x{0a48}\x{0a4b}-\x{0a4d}\x{0a51}\x{0a66}-\x{0a71}\x{0a75}\x{0a81}-\x{0a83}\x{0abc}\x{0abe}-\x{0ac5}\x{0ac7}-\x{0ac9}\x{0acb}-\x{0acd}\x{0ae2}\x{0ae3}\x{0ae6}-\x{0aef}\x{0b01}-\x{0b03}\x{0b3c}\x{0b3e}-\x{0b44}\x{0b47}\x{0b48}\x{0b4b}-\x{0b4d}\x{0b56}\x{0b57}\x{0b62}\x{0b63}\x{0b66}-\x{0b6f}\x{0b82}\x{0bbe}-\x{0bc2}\x{0bc6}-\x{0bc8}\x{0bca}-\x{0bcd}\x{0bd7}\x{0be6}-\x{0bef}\x{0c01}-\x{0c03}\x{0c3e}-\x{0c44}\x{0c46}-\x{0c48}\x{0c4a}-\x{0c4d}\x{0c55}\x{0c56}\x{0c62}\x{0c63}\x{0c66}-\x{0c6f}\x{0c82}\x{0c83}\x{0cbc}\x{0cbe}-\x{0cc4}\x{0cc6}-\x{0cc8}\x{0cca}-\x{0ccd}\x{0cd5}\x{0cd6}\x{0ce2}\x{0ce3}\x{0ce6}-\x{0cef}\x{0d02}\x{0d03}\x{0d3e}-\x{0d44}\x{0d46}-\x{0d48}\x{0d4a}-\x{0d4d}\x{0d57}\x{0d62}\x{0d63}\x{0d66}-\x{0d6f}\x{0d82}\x{0d83}\x{0dca}\x{0dcf}-\x{0dd4}\x{0dd6}\x{0dd8}-\x{0ddf}\x{0df2}\x{0df3}\x{0e31}\x{0e34}-\x{0e3a}\x{0e47}-\x{0e4e}\x{0e50}-\x{0e59}\x{0eb1}\x{0eb4}-\x{0eb9}\x{0ebb}\x{0ebc}\x{0ec8}-\x{0ecd}\x{0ed0}-\x{0ed9}\x{0f18}\x{0f19}\x{0f20}-\x{0f29}\x{0f35}\x{0f37}\x{0f39}\x{0f3e}\x{0f3f}\x{0f71}-\x{0f84}\x{0f86}\x{0f87}\x{0f8d}-\x{0f97}\x{0f99}-\x{0fbc}\x{0fc6}\x{102b}-\x{103e}\x{1040}-\x{1049}\x{1056}-\x{1059}\x{105e}-\x{1060}\x{1062}-\x{1064}\x{1067}-\x{106d}\x{1071}-\x{1074}\x{1082}-\x{108d}\x{108f}-\x{109d}\x{135d}-\x{135f}\x{1712}-\x{1714}\x{1732}-\x{1734}\x{1752}\x{1753}\x{1772}\x{1773}\x{17b4}-\x{17d3}\x{17dd}\x{17e0}-\x{17e9}\x{180b}-\x{180d}\x{1810}-\x{1819}\x{18a9}\x{1920}-\x{192b}\x{1930}-\x{193b}\x{1946}-\x{194f}\x{19b0}-\x{19c0}\x{19c8}\x{19c9}\x{19d0}-\x{19d9}\x{1a17}-\x{1a1b}\x{1a55}-\x{1a5e}\x{1a60}-\x{1a7c}\x{1a7f}-\x{1a89}\x{1a90}-\x{1a99}\x{1b00}-\x{1b04}\x{1b34}-\x{1b44}\x{1b50}-\x{1b59}\x{1b6b}-\x{1b73}\x{1b80}-\x{1b82}\x{1ba1}-\x{1bad}\x{1bb0}-\x{1bb9}\x{1be6}-\x{1bf3}\x{1c24}-\x{1c37}\x{1c40}-\x{1c49}\x{1c50}-\x{1c59}\x{1cd0}-\x{1cd2}\x{1cd4}-\x{1ce8}\x{1ced}\x{1cf2}-\x{1cf4}\x{1dc0}-\x{1de6}\x{1dfc}-\x{1dff}\x{200c}\x{200d}\x{203f}\x{2040}\x{2054}\x{20d0}-\x{20dc}\x{20e1}\x{20e5}-\x{20f0}\x{2cef}-\x{2cf1}\x{2d7f}\x{2de0}-\x{2dff}\x{302a}-\x{302f}\x{3099}\x{309a}\x{a620}-\x{a629}\x{a66f}\x{a674}-\x{a67d}\x{a69f}\x{a6f0}\x{a6f1}\x{a802}\x{a806}\x{a80b}\x{a823}-\x{a827}\x{a880}\x{a881}\x{a8b4}-\x{a8c4}\x{a8d0}-\x{a8d9}\x{a8e0}-\x{a8f1}\x{a900}-\x{a909}\x{a926}-\x{a92d}\x{a947}-\x{a953}\x{a980}-\x{a983}\x{a9b3}-\x{a9c0}\x{a9d0}-\x{a9d9}\x{aa29}-\x{aa36}\x{aa43}\x{aa4c}\x{aa4d}\x{aa50}-\x{aa59}\x{aa7b}\x{aab0}\x{aab2}-\x{aab4}\x{aab7}\x{aab8}\x{aabe}\x{aabf}\x{aac1}\x{aaeb}-\x{aaef}\x{aaf5}\x{aaf6}\x{abe3}-\x{abea}\x{abec}\x{abed}\x{abf0}-\x{abf9}\x{fb1e}\x{fe00}-\x{fe0f}\x{fe20}-\x{fe26}\x{fe33}\x{fe34}\x{fe4d}-\x{fe4f}\x{ff10}-\x{ff19}\x{ff3f}]*\b';
/**
* Full list of JavaScript reserved words.
* Will be loaded from /data/js/keywords_reserved.txt.
*
* @see https://mathiasbynens.be/notes/reserved-keywords
*
* @var string[]
*/
protected $keywordsReserved = array();
/**
* List of JavaScript reserved words that accept a <variable, value, ...>
* after them. Some end of lines are not the end of a statement, like with
* these keywords.
*
* E.g.: we shouldn't insert a ; after this else
* else
* console.log('this is quite fine')
*
* Will be loaded from /data/js/keywords_before.txt
*
* @var string[]
*/
protected $keywordsBefore = array();
/**
* List of JavaScript reserved words that accept a <variable, value, ...>
* before them. Some end of lines are not the end of a statement, like when
* continued by one of these keywords on the newline.
*
* E.g.: we shouldn't insert a ; before this instanceof
* variable
* instanceof String
*
* Will be loaded from /data/js/keywords_after.txt
*
* @var string[]
*/
protected $keywordsAfter = array();
/**
* List of all JavaScript operators.
*
* Will be loaded from /data/js/operators.txt
*
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators
*
* @var string[]
*/
protected $operators = array();
/**
* List of JavaScript operators that accept a <variable, value, ...> after
* them. Some end of lines are not the end of a statement, like with these
* operators.
*
* Note: Most operators are fine, we've only removed !, ++ and --.
* There can't be a newline separating ! and whatever it is negating.
* ++ & -- have to be joined with the value they're in-/decrementing.
*
* Will be loaded from /data/js/operators_before.txt
*
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators
*
* @var string[]
*/
protected $operatorsBefore = array();
/**
* List of JavaScript operators that accept a <variable, value, ...> before
* them. Some end of lines are not the end of a statement, like when
* continued by one of these operators on the newline.
*
* Note: Most operators are fine, we've only removed ), ], ++ and --.
* ++ & -- have to be joined with the value they're in-/decrementing.
* ) & ] are "special" in that they have lots or usecases. () for example
* is used for function calls, for grouping, in if () and for (), ...
*
* Will be loaded from /data/js/operators_after.txt
*
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators
*
* @var string[]
*/
protected $operatorsAfter = array();
/**
* {@inheritdoc}
*/
public function __construct()
{
call_user_func_array(array('parent', '__construct'), func_get_args());
$dataDir = __DIR__.'/../data/js/';
$options = FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES;
$this->keywordsReserved = file($dataDir.'keywords_reserved.txt', $options);
$this->keywordsBefore = file($dataDir.'keywords_before.txt', $options);
$this->keywordsAfter = file($dataDir.'keywords_after.txt', $options);
$this->operators = file($dataDir.'operators.txt', $options);
$this->operatorsBefore = file($dataDir.'operators_before.txt', $options);
$this->operatorsAfter = file($dataDir.'operators_after.txt', $options);
}
/**
* Minify the data.
* Perform JS optimizations.
*
* @param string[optional] $path Path to write the data to
*
* @return string The minified data
*/
public function execute($path = null)
{
$content = '';
// loop files
foreach ($this->data as $source => $js) {
/*
* Combine js: separating the scripts by a ;
* I'm also adding a newline: it will be eaten when whitespace is
* stripped, but we need to make sure we're not just appending
* a new script right after a previous script that ended with a
* singe-line comment on the last line (in which case it would also
* be seen as part of that comment)
*/
$content .= $js."\n;";
}
/*
* Let's first take out strings, comments and regular expressions.
* All of these can contain JS code-like characters, and we should make
* sure any further magic ignores anything inside of these.
*
* Consider this example, where we should not strip any whitespace:
* var str = "a test";
*
* Comments will be removed altogether, strings and regular expressions
* will be replaced by placeholder text, which we'll restore later.
*/
$this->extractStrings('\'"`');
$this->stripComments();
$this->extractRegex();
$content = $this->replace($content);
$content = $this->propertyNotation($content);
$content = $this->shortenBools($content);
$content = $this->stripWhitespace($content);
/*
* Earlier, we extracted strings & regular expressions and replaced them
* with placeholder text. This will restore them.
*/
$content = $this->restoreExtractedData($content);
return $content;
}
/**
* Strip comments from source code.
*/
protected function stripComments()
{
// single-line comments
$this->registerPattern('/\/\/.*$/m', '');
// multi-line comments
$this->registerPattern('/\/\*.*?\*\//s', '');
}
/**
* JS can have /-delimited regular expressions, like: /ab+c/.match(string).
*
* The content inside the regex can contain characters that may be confused
* for JS code: e.g. it could contain whitespace it needs to match & we
* don't want to strip whitespace in there.
*
* The regex can be pretty simple: we don't have to care about comments,
* (which also use slashes) because stripComments() will have stripped those
* already.
*
* This method will replace all string content with simple REGEX#
* placeholder text, so we've rid all regular expressions from characters
* that may be misinterpreted. Original regex content will be saved in
* $this->extracted and after doing all other minifying, we can restore the
* original content via restoreRegex()
*/
protected function extractRegex()
{
// PHP only supports $this inside anonymous functions since 5.4
$minifier = $this;
$callback = function ($match) use ($minifier) {
$count = count($minifier->extracted);
$placeholder = '/'.$count.'/';
$minifier->extracted[$placeholder] = $match[0];
return $placeholder;
};
$pattern = '\/.*?(?<!\\\\)(\\\\\\\\)*\/[gimy]*(?![0-9a-zA-Z\/])';
// a regular expression can only be followed by a few operators or some
// of the RegExp methods (a `\` followed by a variable or value is
// likely part of a division, not a regex)
$after = '[\.,;\)\}]';
$methods = '\.(exec|test|match|search|replace|split)\(';
$this->registerPattern('/'.$pattern.'(?=\s*('.$after.'|'.$methods.'))/', $callback);
// 1 more edge case: a regex can be followed by a lot more operators or
// keywords if there's a newline (ASI) in between, where the operator
// actually starts a new statement
// (https://github.com/matthiasmullie/minify/issues/56)
$operators = $this->getOperatorsForRegex($this->operatorsBefore, '/');
$operators += $this->getOperatorsForRegex($this->keywordsReserved, '/');
$this->registerPattern('/'.$pattern.'\s*\n(?=\s*('.implode('|', $operators).'))/', $callback);
}
/**
* Strip whitespace.
*
* We won't strip *all* whitespace, but as much as possible. The thing that
* we'll preserve are newlines we're unsure about.
* JavaScript doesn't require statements to be terminated with a semicolon.
* It will automatically fix missing semicolons with ASI (automatic semi-
* colon insertion) at the end of line causing errors (without semicolon.)
*
* Because it's sometimes hard to tell if a newline is part of a statement
* that should be terminated or not, we'll just leave some of them alone.
*
* @param string $content The content to strip the whitespace for
*
* @return string
*/
protected function stripWhitespace($content)
{
// uniform line endings, make them all line feed
$content = str_replace(array("\r\n", "\r"), "\n", $content);
// collapse all non-line feed whitespace into a single space
$content = preg_replace('/[^\S\n]+/', ' ', $content);
// strip leading & trailing whitespace
$content = str_replace(array(" \n", "\n "), "\n", $content);
// collapse consecutive line feeds into just 1
$content = preg_replace('/\n+/', "\n", $content);
$operatorsBefore = $this->getOperatorsForRegex($this->operatorsBefore, '/');
$operatorsAfter = $this->getOperatorsForRegex($this->operatorsAfter, '/');
$operators = $this->getOperatorsForRegex($this->operators, '/');
$keywordsBefore = $this->getKeywordsForRegex($this->keywordsBefore, '/');
$keywordsAfter = $this->getKeywordsForRegex($this->keywordsAfter, '/');
// strip whitespace that ends in (or next line begin with) an operator
// that allows statements to be broken up over multiple lines
unset($operatorsBefore['+'], $operatorsBefore['-'], $operatorsAfter['+'], $operatorsAfter['-']);
$content = preg_replace(
array(
'/('.implode('|', $operatorsBefore).')\s+/',
'/\s+('.implode('|', $operatorsAfter).')/',
), '\\1', $content
);
// make sure + and - can't be mistaken for, or joined into ++ and --
$content = preg_replace(
array(
'/(?<![\+\-])\s*([\+\-])(?![\+\-])/',
'/(?<![\+\-])([\+\-])\s*(?![\+\-])/',
), '\\1', $content
);
// collapse whitespace around reserved words into single space
$content = preg_replace('/(^|[;\}\s])\K('.implode('|', $keywordsBefore).')\s+/', '\\2 ', $content);
$content = preg_replace('/\s+('.implode('|', $keywordsAfter).')(?=([;\{\s]|$))/', ' \\1', $content);
/*
* We didn't strip whitespace after a couple of operators because they
* could be used in different contexts and we can't be sure it's ok to
* strip the newlines. However, we can safely strip any non-line feed
* whitespace that follows them.
*/
$operatorsDiffBefore = array_diff($operators, $operatorsBefore);
$operatorsDiffAfter = array_diff($operators, $operatorsAfter);
$content = preg_replace('/('.implode('|', $operatorsDiffBefore).')[^\S\n]+/', '\\1', $content);
$content = preg_replace('/[^\S\n]+('.implode('|', $operatorsDiffAfter).')/', '\\1', $content);
/*
* Get rid of double semicolons, except where they can be used like:
* "for(v=1,_=b;;)", "for(v=1;;v++)" or "for(;;ja||(ja=true))".
* I'll safeguard these double semicolons inside for-loops by
* temporarily replacing them with an invalid condition: they won't have
* a double semicolon and will be easy to spot to restore afterwards.
*/
$content = preg_replace('/\bfor\(([^;]*);;([^;]*)\)/', 'for(\\1;-;\\2)', $content);
$content = preg_replace('/;+/', ';', $content);
$content = preg_replace('/\bfor\(([^;]*);-;([^;]*)\)/', 'for(\\1;;\\2)', $content);
/*
* Next, we'll be removing all semicolons where ASI kicks in.
* for-loops however, can have an empty body (ending in only a
* semicolon), like: `for(i=1;i<3;i++);`, of `for(i in list);`
* Here, nothing happens during the loop; it's just used to keep
* increasing `i`. With that ; omitted, the next line would be expected
* to be the for-loop's body...
* I'm going to double that semicolon (if any) so after the next line,
* which strips semicolons here & there, we're still left with this one.
*/
$content = preg_replace('/(for\([^;\{]*;[^;\{]*;[^;\{]*\));(\}|$)/s', '\\1;;\\2', $content);
$content = preg_replace('/(for\([^;\{]+\s+in\s+[^;\{]+\));(\}|$)/s', '\\1;;\\2', $content);
/*
* We also can't strip empty else-statements. Even though they're
* useless and probably shouldn't be in the code in the first place, we
* shouldn't be stripping the `;` that follows it as it breaks the code.
* We can just remove those useless else-statements completely.
*
* @see https://github.com/matthiasmullie/minify/issues/91
*/
$content = preg_replace('/else;/s', '', $content);
/*
* We also don't really want to terminate statements followed by closing
* curly braces (which we've ignored completely up until now) or end-of-
* script: ASI will kick in here & we're all about minifying.
* Semicolons at beginning of the file don't make any sense either.
*/
$content = preg_replace('/;(\}|$)/s', '\\1', $content);
$content = ltrim($content, ';');
// get rid of remaining whitespace af beginning/end
return trim($content.';');
}
/**
* We'll strip whitespace around certain operators with regular expressions.
* This will prepare the given array by escaping all characters.
*
* @param string[] $operators
* @param string $delimiter
*
* @return string[]
*/
protected function getOperatorsForRegex(array $operators, $delimiter = '/')
{
// escape operators for use in regex
$delimiters = array_fill(0, count($operators), $delimiter);
$escaped = array_map('preg_quote', $operators, $delimiters);
$operators = array_combine($operators, $escaped);
// ignore + & - for now, they'll get special treatment
unset($operators['+'], $operators['-']);
// dot can not just immediately follow a number; it can be confused for
// decimal point, or calling a method on it, e.g. 42 .toString()
$operators['.'] = '(?<![0-9]\s)\.';
// don't confuse = with other assignment shortcuts (e.g. +=)
$chars = preg_quote('+-*\=<>%&|', $delimiter);
$operators['='] = '(?<!['.$chars.'])\=';
return $operators;
}
/**
* We'll strip whitespace around certain keywords with regular expressions.
* This will prepare the given array by escaping all characters.
*
* @param string[] $keywords
* @param string $delimiter
*
* @return string[]
*/
protected function getKeywordsForRegex(array $keywords, $delimiter = '/')
{
// escape keywords for use in regex
$delimiter = array_fill(0, count($keywords), $delimiter);
$escaped = array_map('preg_quote', $keywords, $delimiter);
// add word boundaries
array_walk($keywords, function ($value) {
return '\b'.$value.'\b';
});
$keywords = array_combine($keywords, $escaped);
return $keywords;
}
/**
* Replaces all occurrences of array['key'] by array.key.
*
* @param string $content
*
* @return string
*/
protected function propertyNotation($content)
{
// PHP only supports $this inside anonymous functions since 5.4
$minifier = $this;
$keywords = $this->keywordsReserved;
$callback = function ($match) use ($minifier, $keywords) {
$property = trim($minifier->extracted[$match[1]], '\'"');
/*
* Check if the property is a reserved keyword. In this context (as
* property of an object literal/array) it shouldn't matter, but IE8
* freaks out with "Expected identifier".
*/
if (in_array($property, $keywords)) {
return $match[0];
}
/*
* See if the property is in a variable-like format (e.g.
* array['key-here'] can't be replaced by array.key-here since '-'
* is not a valid character there.
*/
if (!preg_match('/^'.$minifier::REGEX_VARIABLE.'$/u', $property)) {
return $match[0];
}
return '.'.$property;
};
/*
* Figure out if previous character is a variable name (of the array
* we want to use property notation on) - this is to make sure
* standalone ['value'] arrays aren't confused for keys-of-an-array.
* We can (and only have to) check the last character, because PHP's
* regex implementation doesn't allow unfixed-length look-behind
* assertions.
*/
preg_match('/(\[[^\]]+\])[^\]]*$/', static::REGEX_VARIABLE, $previousChar);
$previousChar = $previousChar[1];
/*
* Make sure word preceding the ['value'] is not a keyword, e.g.
* return['x']. Because -again- PHP's regex implementation doesn't allow
* unfixed-length look-behind assertions, I'm just going to do a lot of
* separate look-behind assertions, one for each keyword.
*/
$keywords = $this->getKeywordsForRegex($keywords);
$keywords = '(?<!'.implode(')(?<!', $keywords).')';
return preg_replace_callback('/(?<='.$previousChar.'|\])'.$keywords.'\[\s*(([\'"])[0-9]+\\2)\s*\]/u', $callback, $content);
}
/**
* Replaces true & false by !0 and !1.
*
* @param string $content
*
* @return string
*/
protected function shortenBools($content)
{
/*
* 'true' or 'false' could be used as property names (which may be
* followed by whitespace) - we must not replace those!
* Since PHP doesn't allow variable-length (to account for the
* whitespace) lookbehind assertions, I need to capture the leading
* character and check if it's a `.`
*/
$callback = function ($match) {
if (trim($match[1]) === '.') {
return $match[0];
}
return $match[1].($match[2] === 'true' ? '!0' : '!1');
};
$content = preg_replace_callback('/(^|.\s*)\b(true|false)\b(?!:)/', $callback, $content);
// for(;;) is exactly the same as while(true), but shorter :)
$content = preg_replace('/\bwhile\(!0\){/', 'for(;;){', $content);
// now make sure we didn't turn any do ... while(true) into do ... for(;;)
preg_match_all('/\bdo\b/', $content, $dos, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
// go backward to make sure positional offsets aren't altered when $content changes
$dos = array_reverse($dos);
foreach ($dos as $do) {
$offsetDo = $do[0][1];
// find all `while` (now `for`) following `do`: one of those must be
// associated with the `do` and be turned back into `while`
preg_match_all('/\bfor\(;;\)/', $content, $whiles, PREG_OFFSET_CAPTURE | PREG_SET_ORDER, $offsetDo);
foreach ($whiles as $while) {
$offsetWhile = $while[0][1];
$open = substr_count($content, '{', $offsetDo, $offsetWhile - $offsetDo);
$close = substr_count($content, '}', $offsetDo, $offsetWhile - $offsetDo);
if ($open === $close) {
// only restore `while` if amount of `{` and `}` are the same;
// otherwise, that `for` isn't associated with this `do`
$content = substr_replace($content, 'while(!0)', $offsetWhile, strlen('for(;;)'));
break;
}
}
}
return $content;
}
}
<?php
namespace MatthiasMullie\Minify;
use MatthiasMullie\Minify\Exceptions\IOException;
use Psr\Cache\CacheItemInterface;
/**
* Abstract minifier class.
*
* Please report bugs on https://github.com/matthiasmullie/minify/issues
*
* @author Matthias Mullie <minify@mullie.eu>
* @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
* @license MIT License
*/
abstract class Minify
{
/**
* The data to be minified.
*
* @var string[]
*/
protected $data = array();
/**
* Array of patterns to match.
*
* @var string[]
*/
protected $patterns = array();
/**
* This array will hold content of strings and regular expressions that have
* been extracted from the JS source code, so we can reliably match "code",
* without having to worry about potential "code-like" characters inside.
*
* @var string[]
*/
public $extracted = array();
/**
* Init the minify class - optionally, code may be passed along already.
*/
public function __construct(/* $data = null, ... */)
{
// it's possible to add the source through the constructor as well ;)
if (func_num_args()) {
call_user_func_array(array($this, 'add'), func_get_args());
}
}
/**
* Add a file or straight-up code to be minified.
*
* @param string|string[] $data
*
* @return static
*/
public function add($data /* $data = null, ... */)
{
// bogus "usage" of parameter $data: scrutinizer warns this variable is
// not used (we're using func_get_args instead to support overloading),
// but it still needs to be defined because it makes no sense to have
// this function without argument :)
$args = array($data) + func_get_args();
// this method can be overloaded
foreach ($args as $data) {
if (is_array($data)) {
call_user_func_array(array($this, 'add'), $data);
continue;
}
// redefine var
$data = (string) $data;
// load data
$value = $this->load($data);
$key = ($data != $value) ? $data : count($this->data);
// replace CR linefeeds etc.
// @see https://github.com/matthiasmullie/minify/pull/139
$value = str_replace(array("\r\n", "\r"), "\n", $value);
// store data
$this->data[$key] = $value;
}
return $this;
}
/**
* Minify the data & (optionally) saves it to a file.
*
* @param string[optional] $path Path to write the data to
*
* @return string The minified data
*/
public function minify($path = null)
{
$content = $this->execute($path);
// save to path
if ($path !== null) {
$this->save($content, $path);
}
return $content;
}
/**
* Minify & gzip the data & (optionally) saves it to a file.
*
* @param string[optional] $path Path to write the data to
* @param int[optional] $level Compression level, from 0 to 9
*
* @return string The minified & gzipped data
*/
public function gzip($path = null, $level = 9)
{
$content = $this->execute($path);
$content = gzencode($content, $level, FORCE_GZIP);
// save to path
if ($path !== null) {
$this->save($content, $path);
}
return $content;
}
/**
* Minify the data & write it to a CacheItemInterface object.
*
* @param CacheItemInterface $item Cache item to write the data to
*
* @return CacheItemInterface Cache item with the minifier data
*/
public function cache(CacheItemInterface $item)
{
$content = $this->execute();
$item->set($content);
return $item;
}
/**
* Minify the data.
*
* @param string[optional] $path Path to write the data to
*
* @return string The minified data
*/
abstract public function execute($path = null);
/**
* Load data.
*
* @param string $data Either a path to a file or the content itself
*
* @return string
*/
protected function load($data)
{
// check if the data is a file
if ($this->canImportFile($data)) {
$data = file_get_contents($data);
// strip BOM, if any
if (substr($data, 0, 3) == "\xef\xbb\xbf") {
$data = substr($data, 3);
}
}
return $data;
}
/**
* Save to file.
*
* @param string $content The minified data
* @param string $path The path to save the minified data to
*
* @throws IOException
*/
protected function save($content, $path)
{
$handler = $this->openFileForWriting($path);
$this->writeToFile($handler, $content);
@fclose($handler);
}
/**
* Register a pattern to execute against the source content.
*
* @param string $pattern PCRE pattern
* @param string|callable $replacement Replacement value for matched pattern
*/
protected function registerPattern($pattern, $replacement = '')
{
// study the pattern, we'll execute it more than once
$pattern .= 'S';
$this->patterns[] = array($pattern, $replacement);
}
/**
* We can't "just" run some regular expressions against JavaScript: it's a
* complex language. E.g. having an occurrence of // xyz would be a comment,
* unless it's used within a string. Of you could have something that looks
* like a 'string', but inside a comment.
* The only way to accurately replace these pieces is to traverse the JS one
* character at a time and try to find whatever starts first.
*
* @param string $content The content to replace patterns in
*
* @return string The (manipulated) content
*/
protected function replace($content)
{
$processed = '';
$positions = array_fill(0, count($this->patterns), -1);
$matches = array();
while ($content) {
// find first match for all patterns
foreach ($this->patterns as $i => $pattern) {
list($pattern, $replacement) = $pattern;
// no need to re-run matches that are still in the part of the
// content that hasn't been processed
if ($positions[$i] >= 0) {
continue;
}
$match = null;
if (preg_match($pattern, $content, $match)) {
$matches[$i] = $match;
// we'll store the match position as well; that way, we
// don't have to redo all preg_matches after changing only
// the first (we'll still know where those others are)
$positions[$i] = strpos($content, $match[0]);
} else {
// if the pattern couldn't be matched, there's no point in
// executing it again in later runs on this same content;
// ignore this one until we reach end of content
unset($matches[$i]);
$positions[$i] = strlen($content);
}
}
// no more matches to find: everything's been processed, break out
if (!$matches) {
$processed .= $content;
break;
}
// see which of the patterns actually found the first thing (we'll
// only want to execute that one, since we're unsure if what the
// other found was not inside what the first found)
$discardLength = min($positions);
$firstPattern = array_search($discardLength, $positions);
$match = $matches[$firstPattern][0];
// execute the pattern that matches earliest in the content string
list($pattern, $replacement) = $this->patterns[$firstPattern];
$replacement = $this->replacePattern($pattern, $replacement, $content);
// figure out which part of the string was unmatched; that's the
// part we'll execute the patterns on again next
$content = substr($content, $discardLength);
$unmatched = (string) substr($content, strpos($content, $match) + strlen($match));
// move the replaced part to $processed and prepare $content to
// again match batch of patterns against
$processed .= substr($replacement, 0, strlen($replacement) - strlen($unmatched));
$content = $unmatched;
// first match has been replaced & that content is to be left alone,
// the next matches will start after this replacement, so we should
// fix their offsets
foreach ($positions as $i => $position) {
$positions[$i] -= $discardLength + strlen($match);
}
}
return $processed;
}
/**
* This is where a pattern is matched against $content and the matches
* are replaced by their respective value.
* This function will be called plenty of times, where $content will always
* move up 1 character.
*
* @param string $pattern Pattern to match
* @param string|callable $replacement Replacement value
* @param string $content Content to match pattern against
*
* @return string
*/
protected function replacePattern($pattern, $replacement, $content)
{
if (is_callable($replacement)) {
return preg_replace_callback($pattern, $replacement, $content, 1, $count);
} else {
return preg_replace($pattern, $replacement, $content, 1, $count);
}
}
/**
* Strings are a pattern we need to match, in order to ignore potential
* code-like content inside them, but we just want all of the string
* content to remain untouched.
*
* This method will replace all string content with simple STRING#
* placeholder text, so we've rid all strings from characters that may be
* misinterpreted. Original string content will be saved in $this->extracted
* and after doing all other minifying, we can restore the original content
* via restoreStrings().
*
* @param string[optional] $chars
*/
protected function extractStrings($chars = '\'"')
{
// PHP only supports $this inside anonymous functions since 5.4
$minifier = $this;
$callback = function ($match) use ($minifier) {
// check the second index here, because the first always contains a quote
if ($match[2] === '') {
/*
* Empty strings need no placeholder; they can't be confused for
* anything else anyway.
* But we still needed to match them, for the extraction routine
* to skip over this particular string.
*/
return $match[0];
}
$count = count($minifier->extracted);
$placeholder = $match[1].$count.$match[1];
$minifier->extracted[$placeholder] = $match[1].$match[2].$match[1];
return $placeholder;
};
/*
* The \\ messiness explained:
* * Don't count ' or " as end-of-string if it's escaped (has backslash
* in front of it)
* * Unless... that backslash itself is escaped (another leading slash),
* in which case it's no longer escaping the ' or "
* * So there can be either no backslash, or an even number
* * multiply all of that times 4, to account for the escaping that has
* to be done to pass the backslash into the PHP string without it being
* considered as escape-char (times 2) and to get it in the regex,
* escaped (times 2)
*/
$this->registerPattern('/(['.$chars.'])(.*?(?<!\\\\)(\\\\\\\\)*+)\\1/s', $callback);
}
/**
* This method will restore all extracted data (strings, regexes) that were
* replaced with placeholder text in extract*(). The original content was
* saved in $this->extracted.
*
* @param string $content
*
* @return string
*/
protected function restoreExtractedData($content)
{
if (!$this->extracted) {
// nothing was extracted, nothing to restore
return $content;
}
$content = strtr($content, $this->extracted);
$this->extracted = array();
return $content;
}
/**
* Check if the path is a regular file and can be read.
*
* @param string $path
*
* @return bool
*/
protected function canImportFile($path)
{
return strlen($path) < PHP_MAXPATHLEN && @is_file($path) && is_readable($path);
}
/**
* Attempts to open file specified by $path for writing.
*
* @param string $path The path to the file
*
* @return resource Specifier for the target file
*
* @throws IOException
*/
protected function openFileForWriting($path)
{
if (($handler = @fopen($path, 'w')) === false) {
throw new IOException('The file "'.$path.'" could not be opened for writing. Check if PHP has enough permissions.');
}
return $handler;
}
/**
* Attempts to write $content to the file specified by $handler. $path is used for printing exceptions.
*
* @param resource $handler The resource to write to
* @param string $content The content to write
* @param string $path The path to the file (for exception printing only)
*
* @throws IOException
*/
protected function writeToFile($handler, $content, $path = '')
{
if (($result = @fwrite($handler, $content)) === false || ($result < strlen($content))) {
throw new IOException('The file "'.$path.'" could not be written to. Check your disk space and file permissions.');
}
}
}
<?php
namespace MatthiasMullie\PathConverter;
/**
* Convert paths relative from 1 file to another.
*
* E.g.
* ../../images/icon.jpg relative to /css/imports/icons.css
* becomes
* ../images/icon.jpg relative to /css/minified.css
*
* Please report bugs on https://github.com/matthiasmullie/path-converter/issues
*
* @author Matthias Mullie <pathconverter@mullie.eu>
* @copyright Copyright (c) 2015, Matthias Mullie. All rights reserved
* @license MIT License
*/
class Converter implements ConverterInterface
{
/**
* @var string
*/
protected $from;
/**
* @var string
*/
protected $to;
/**
* @param string $from The original base path (directory, not file!)
* @param string $to The new base path (directory, not file!)
*/
public function __construct($from, $to)
{
$shared = $this->shared($from, $to);
if ($shared === '') {
// when both paths have nothing in common, one of them is probably
// absolute while the other is relative
$cwd = getcwd();
$from = strpos($from, $cwd) === 0 ? $from : $cwd.'/'.$from;
$to = strpos($to, $cwd) === 0 ? $to : $cwd.'/'.$to;
// or traveling the tree via `..`
// attempt to resolve path, or assume it's fine if it doesn't exist
$from = realpath($from) ?: $from;
$to = realpath($to) ?: $to;
}
$from = $this->dirname($from);
$to = $this->dirname($to);
$from = $this->normalize($from);
$to = $this->normalize($to);
$this->from = $from;
$this->to = $to;
}
/**
* Normalize path.
*
* @param string $path
*
* @return string
*/
protected function normalize($path)
{
// deal with different operating systems' directory structure
$path = rtrim(str_replace(DIRECTORY_SEPARATOR, '/', $path), '/');
/*
* Example:
* /home/forkcms/frontend/cache/compiled_templates/../../core/layout/css/../images/img.gif
* to
* /home/forkcms/frontend/core/layout/images/img.gif
*/
do {
$path = preg_replace('/[^\/]+(?<!\.\.)\/\.\.\//', '', $path, -1, $count);
} while ($count);
return $path;
}
/**
* Figure out the shared path of 2 locations.
*
* Example:
* /home/forkcms/frontend/core/layout/images/img.gif
* and
* /home/forkcms/frontend/cache/minified_css
* share
* /home/forkcms/frontend
*
* @param string $path1
* @param string $path2
*
* @return string
*/
protected function shared($path1, $path2)
{
// $path could theoretically be empty (e.g. no path is given), in which
// case it shouldn't expand to array(''), which would compare to one's
// root /
$path1 = $path1 ? explode('/', $path1) : array();
$path2 = $path2 ? explode('/', $path2) : array();
$shared = array();
// compare paths & strip identical ancestors
foreach ($path1 as $i => $chunk) {
if (isset($path2[$i]) && $path1[$i] == $path2[$i]) {
$shared[] = $chunk;
} else {
break;
}
}
return implode('/', $shared);
}
/**
* Convert paths relative from 1 file to another.
*
* E.g.
* ../images/img.gif relative to /home/forkcms/frontend/core/layout/css
* should become:
* ../../core/layout/images/img.gif relative to
* /home/forkcms/frontend/cache/minified_css
*
* @param string $path The relative path that needs to be converted
*
* @return string The new relative path
*/
public function convert($path)
{
// quit early if conversion makes no sense
if ($this->from === $this->to) {
return $path;
}
$path = $this->normalize($path);
// if we're not dealing with a relative path, just return absolute
if (strpos($path, '/') === 0) {
return $path;
}
// normalize paths
$path = $this->normalize($this->from.'/'.$path);
// strip shared ancestor paths
$shared = $this->shared($path, $this->to);
$path = mb_substr($path, mb_strlen($shared));
$to = mb_substr($this->to, mb_strlen($shared));
// add .. for every directory that needs to be traversed to new path
$to = str_repeat('../', mb_substr_count($to, '/'));
return $to.ltrim($path, '/');
}
/**
* Attempt to get the directory name from a path.
*
* @param string $path
*
* @return string
*/
protected function dirname($path)
{
if (is_file($path)) {
return dirname($path);
}
if (is_dir($path)) {
return rtrim($path, '/');
}
// no known file/dir, start making assumptions
// ends in / = dir
if (mb_substr($path, -1) === '/') {
return rtrim($path, '/');
}
// has a dot in the name, likely a file
if (preg_match('/.*\..*$/', basename($path)) !== 0) {
return dirname($path);
}
// you're on your own here!
return $path;
}
}
<?php
namespace MatthiasMullie\PathConverter;
/**
* Convert file paths.
*
* Please report bugs on https://github.com/matthiasmullie/path-converter/issues
*
* @author Matthias Mullie <pathconverter@mullie.eu>
* @copyright Copyright (c) 2015, Matthias Mullie. All rights reserved
* @license MIT License
*/
interface ConverterInterface
{
/**
* Convert file paths.
*
* @param string $path The path to be converted
*
* @return string The new path
*/
public function convert($path);
}
<?php
namespace MatthiasMullie\PathConverter;
/**
* Don't convert paths.
*
* Please report bugs on https://github.com/matthiasmullie/path-converter/issues
*
* @author Matthias Mullie <pathconverter@mullie.eu>
* @copyright Copyright (c) 2015, Matthias Mullie. All rights reserved
* @license MIT License
*/
class NoConverter implements ConverterInterface
{
/**
* {@inheritdoc}
*/
public function convert($path)
{
return $path;
}
}
in
public
extends
private
protected
implements
instanceof
\ No newline at end of file
do
in
let
new
var
case
else
enum
void
with
class
const
yield
delete
export
import
public
static
typeof
extends
package
private
function
protected
implements
instanceof
\ No newline at end of file
do
if
in
for
let
new
try
var
case
else
enum
eval
null
this
true
void
with
break
catch
class
const
false
super
throw
while
yield
delete
export
import
public
return
static
switch
typeof
default
extends
finally
package
private
continue
debugger
function
arguments
interface
protected
implements
instanceof
abstract
boolean
byte
char
double
final
float
goto
int
long
native
short
synchronized
throws
transient
volatile
\ No newline at end of file
+
-
*
/
%
=
+=
-=
*=
/=
%=
<<=
>>=
>>>=
&=
^=
|=
&
|
^
~
<<
>>
>>>
==
===
!=
!==
>
<
>=
<=
&&
||
!
.
[
]
?
:
,
;
(
)
{
}
\ No newline at end of file
+
-
*
/
%
=
+=
-=
*=
/=
%=
<<=
>>=
>>>=
&=
^=
|=
&
|
^
~
<<
>>
>>>
==
===
!=
!==
>
<
>=
<=
&&
||
.
[
]
?
:
,
;
(
)
}
\ No newline at end of file
+
-
*
/
%
=
+=
-=
*=
/=
%=
<<=
>>=
>>>=
&=
^=
|=
&
|
^
~
<<
>>
>>>
==
===
!=
!==
>
<
>=
<=
&&
||
!
.
[
?
:
,
;
(
{
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment