/** * Merges a collection of files togheter and returns array of paths to the files. * js /css content is returned as string if packlevel is 0 and you use a js/ css generator. * $fileArray can also be array of array of files, like array( 'file.js', 'file2.js', array( 'file5.js' ) ) * The name of the cached file is a md5 hash consistant of the file paths * of the valid files in $file_array and the packlevel. * The whole argument is used instead of file path on js/ css generators in the cache hash. * * @param array|string $fileArray Either array of file paths, or string with file path * @param string $subPath In witch sub path of design folder to look for files. * @param string $fileExtension File extension name (for use on cache file) * @param int $packLevel Level of packing, values: 0-3 * @param bool $wwwInCacheHash To add www path in cahce hash or not * @return array List of css files */ static function packFiles($fileArray, $subPath = '', $fileExtension = '.js', $packLevel = 2, $wwwInCacheHash = false) { if (!$fileArray) { return array(); } else { if (!is_array($fileArray)) { $fileArray = array($fileArray); } } $cacheName = ''; $lastmodified = 0; $httpFiles = array(); $validFiles = array(); $validWWWFiles = array(); $bases = eZTemplateDesignResource::allDesignBases(); // Only pack files if Packer is enabled and if not set DevelopmentMode is disabled $ezjscINI = eZINI::instance('ezjscore.ini'); if ($ezjscINI->hasVariable('eZJSCore', 'Packer')) { $packerIniValue = $ezjscINI->variable('eZJSCore', 'Packer'); if ($packerIniValue === 'disabled') { $packLevel = 0; } else { if (is_numeric($packerIniValue)) { $packLevel = (int) $packerIniValue; } } } else { $ini = eZINI::instance(); if ($ini->variable('TemplateSettings', 'DevelopmentMode') === 'enabled') { $packLevel = 0; } } $packerInfo = array('file_extension' => $fileExtension, 'pack_level' => $packLevel, 'sub_path' => $subPath, 'cache_dir' => self::getCacheDir(), 'www_dir' => self::getWwwDir()); // needed for image includes to work on ezp installs with mixed access methods (virtualhost + url based setup) if ($wwwInCacheHash) { $cacheName = $packerInfo['www_dir']; } while (count($fileArray) > 0) { $file = array_shift($fileArray); // if $file is array, concat it to the file array and continue if ($file && is_array($file)) { $fileArray = array_merge($file, $fileArray); continue; } else { if (!$file) { continue; } else { if (strpos($file, '::') !== false) { $server = self::serverCallHelper(explode('::', $file)); $fileTime = $server->getCacheTime($packerInfo); // generate content straight away if packing is disabled if ($packLevel === 0) { $validWWWFiles[] = $server->call($fileArray); } else { if ($fileTime === -1) { $validFiles[] = $server->call($fileArray); } else { $validFiles[] = $server; $cacheName .= $file . '_'; } } $lastmodified = max($lastmodified, $fileTime); continue; } else { if (strpos($file, 'http://') === 0 || strpos($file, 'https://') === 0) { $httpFiles[] = $file; continue; } else { if (strpos($file, 'var/') === 0) { if (substr($file, 0, 2) === '//' || preg_match("#^[a-zA-Z0-9]+:#", $file)) { $file = '/'; } else { if (strlen($file) > 0 && $file[0] !== '/') { $file = '/' . $file; } } eZURI::transformURI($file, true, 'relative'); // get file time and continue if it return false $file = str_replace('//' . $packerInfo['www_dir'], '', '//' . $file); $fileTime = file_exists($file) ? filemtime($file) : false; $wwwFile = $packerInfo['www_dir'] . $file; } else { // allow path to be outside subpath if it starts with '/' if ($file[0] === '/') { $file = ltrim($file, '/'); } else { $file = $subPath . $file; } $triedFiles = array(); $match = eZTemplateDesignResource::fileMatch($bases, '', $file, $triedFiles); if ($match === false) { eZDebug::writeWarning("Could not find: {$file}", __METHOD__); continue; } $file = htmlspecialchars($match['path']); $fileTime = file_exists($file) ? filemtime($file) : false; $wwwFile = $packerInfo['www_dir'] . $file; } } } } } if ($fileTime === false) { eZDebug::writeWarning("Could not get modified time of file: {$file}", __METHOD__); continue; } // calculate last modified time and store in arrays $lastmodified = max($lastmodified, $fileTime); $validFiles[] = $file; $validWWWFiles[] = $wwwFile; // STEVO $cacheName .= $file . '_' . $fileTime . '_'; } // if packing is disabled, return the valid paths / content we have generated if ($packLevel === 0) { return array_merge($httpFiles, $validWWWFiles); } if (!$validFiles) { eZDebug::writeWarning("Could not find any files: " . var_export($fileArray, true), __METHOD__); return array(); } // generate cache file name and path $cacheName = md5($cacheName . $packLevel) . $fileExtension; $cachePath = $packerInfo['cache_dir'] . $subPath; if (file_exists($cachePath . $cacheName)) { // check last modified time and return path to cache file if valid if ($lastmodified <= filemtime($cachePath . $cacheName)) { $httpFiles[] = $packerInfo['www_dir'] . $cachePath . $cacheName; return $httpFiles; } } // Merge file content and create new cache file $content = ''; foreach ($validFiles as $file) { // if this is a js / css generator, call to get content if ($file instanceof ezjscServerRouter) { $content .= $file->call($validFiles); continue; } else { if (!$file) { continue; } } // else, get content of normal file $fileContent = file_get_contents($file); if (!trim($fileContent)) { $content .= "/* empty: {$file} */\r\n"; continue; } // we need to fix relative background image paths if this is a css file if (strpos($fileExtension, '.css') !== false) { $fileContent = ezjscPacker::fixImgPaths($fileContent, $file); } $content .= "/* start: {$file} */\r\n"; $content .= $fileContent; $content .= "\r\n/* end: {$file} */\r\n\r\n"; } // Pack the file to save bandwidth if ($packLevel > 1) { if (strpos($fileExtension, '.css') !== false) { $content = ezjscPacker::optimizeCSS($content, $packLevel); } else { $content = ezjscPacker::optimizeScript($content, $packLevel); } } // save file and return path if sucsessfull if (eZFile::create($cacheName, $cachePath, $content)) { $httpFiles[] = $packerInfo['www_dir'] . $cachePath . $cacheName; return $httpFiles; } return array(); }
/** * Merges a collection of files togheter and returns array of paths to the files. * js /css content is returned as string if packlevel is 0 and you use a js/ css generator. * $fileArray can also be array of array of files, like array( 'file.js', 'file2.js', array( 'file5.js' ) ) * The name of the cached file is a md5 hash consistant of the file paths * of the valid files in $file_array and the packlevel. * The whole argument is used instead of file path on js/ css generators in the cache hash. * * @param array|string $fileArray Either array of file paths, or string with file path * @param string $subPath In witch sub path of design folder to look for files. * @param string $fileExtension File extension name (for use on cache file) * @param int $packLevel Level of packing, values: 0-3 * @param bool $indexDirInCacheHash To add index path in cache hash or not * @param string $filePostName Extra file name part, example "_screen" in case of medai use for css * * @return array List of css files */ static function packFiles($fileArray, $subPath = '', $fileExtension = '.js', $packLevel = 2, $indexDirInCacheHash = false, $filePostName = '') { if (!$fileArray) { return array(); } else { if (!is_array($fileArray)) { $fileArray = array($fileArray); } } $ezjscINI = eZINI::instance('ezjscore.ini'); $bases = eZTemplateDesignResource::allDesignBases(); $customHosts = $ezjscINI->variable('Packer', 'CustomHosts'); $data = array('http' => array(), 'www' => array(), 'locale' => array(), 'cache_name' => '', 'cache_hash' => '', 'cache_path' => '', 'last_modified' => 0, 'file_extension' => $fileExtension, 'file_post_name' => $filePostName, 'pack_level' => $packLevel, 'sub_path' => $subPath, 'cache_dir' => self::getCacheDir(), 'www_dir' => htmlspecialchars(self::getWwwDir(), ENT_COMPAT, 'UTF-8'), 'index_dir' => self::getIndexDir(), 'custom_host' => isset($customHosts[$fileExtension]) ? $customHosts[$fileExtension] : ''); // Only pack files if Packer is enabled and if not set DevelopmentMode is disabled if ($ezjscINI->hasVariable('eZJSCore', 'Packer')) { $packerIniValue = $ezjscINI->variable('eZJSCore', 'Packer'); if ($packerIniValue === 'disabled') { $data['pack_level'] = 0; } else { if (is_numeric($packerIniValue)) { $data['pack_level'] = (int) $packerIniValue; } } } else { if (eZINI::instance()->variable('TemplateSettings', 'DevelopmentMode') === 'enabled') { $data['pack_level'] = 0; } } // Needed for image includes to work on ezp installs with mixed access methods (virtualhost + url based setup) if ($indexDirInCacheHash) { $data['cache_name'] = $data['index_dir']; } $originalFileArray = $fileArray; while (!empty($fileArray)) { $file = array_shift($fileArray); // if $file is array, concat it to the file array and continue if ($file && is_array($file)) { $fileArray = array_merge($file, $fileArray); continue; } else { if (!$file) { continue; } else { if (strpos($file, '::') !== false) { $server = self::serverCallHelper(explode('::', $file)); if (!$server instanceof ezjscServerRouter) { continue; } $fileTime = $server->getCacheTime($data); // Generate content straight away if packing is disabled if ($data['pack_level'] === 0) { $data['www'][] = $server->call($fileArray); } else { if ($fileTime === -1) { $data['http'][] = $server->call($fileArray); } else { $data['locale'][] = $server; $data['cache_name'] .= $file . '_'; } } $data['last_modified'] = max($data['last_modified'], $fileTime); continue; } else { if (strpos($file, 'http://') === 0 || strpos($file, 'https://') === 0) { $data['http'][] = $file; continue; } else { if (strpos($file, '://') === 0) { if (!isset($protocol)) { $protocol = eZSys::serverProtocol(); } $data['http'][] = $protocol . $file; continue; } else { if (strpos($file, 'var/') === 0) { if (substr($file, 0, 2) === '//' || preg_match("#^[a-zA-Z0-9]+:#", $file)) { $file = '/'; } else { if (strlen($file) > 0 && $file[0] !== '/') { $file = '/' . $file; } } eZURI::transformURI($file, true, 'relative'); // Get file time and continue if it return false $file = str_replace('//' . $data['www_dir'], '', '//' . $file); $fileTime = file_exists($file) ? filemtime($file) : false; $wwwFile = $data['www_dir'] . $file; } else { // Allow path to be outside subpath if it starts with '/' if ($file[0] === '/') { $file = ltrim($file, '/'); } else { $file = $subPath . $file; } $triedFiles = array(); $match = eZTemplateDesignResource::fileMatch($bases, '', $file, $triedFiles); if ($match === false) { eZDebug::writeWarning("Could not find: {$file}", __METHOD__); continue; } $file = htmlspecialchars($match['path']); $fileTime = file_exists($file) ? filemtime($file) : false; $wwwFile = $data['www_dir'] . $file; } } } } } } if ($fileTime === false) { eZDebug::writeWarning("Could not get modified time of file: {$file}", __METHOD__); continue; } // Calculate last modified time and store in arrays $data['last_modified'] = max($data['last_modified'], $fileTime); $data['locale'][] = $file; $data['www'][] = $wwwFile; $data['cache_name'] .= $file . '_'; } if ($data['pack_level'] === 0) { self::$log[] = $data; // if packing is disabled, return the valid paths / content we have generated return array_merge($data['http'], $data['www']); } else { if (empty($data['locale']) && !empty($data['http'])) { self::$log[] = $data; // return if there are only external scripts and no local files to cache return array_merge($data['http'], $data['www']); } else { if (empty($data['locale'])) { eZDebug::writeWarning("Could not find any files: " . var_export($originalFileArray, true), __METHOD__); return array(); } } } // See if cahe file exists and if it has expired (only if time is not part of name) if ($ezjscINI->variable('Packer', 'AppendLastModifiedTime') === 'enabled') { $data['cache_hash'] = md5($data['cache_name'] . $data['pack_level']) . '_' . $data['last_modified'] . $data['file_post_name'] . $data['file_extension']; $data['cache_path'] = $data['cache_dir'] . $subPath . $data['cache_hash']; $clusterFileHandler = eZClusterFileHandler::instance($data['cache_path']); if ($clusterFileHandler->fileExists($data['cache_path'])) { $data['http'][] = $data['custom_host'] . $data['www_dir'] . $data['cache_path']; self::$log[] = $data; return $data['http']; } } else { $data['cache_hash'] = md5($data['cache_name'] . $data['pack_level']) . $data['file_post_name'] . $data['file_extension']; $data['cache_path'] = $data['cache_dir'] . $subPath . $data['cache_hash']; $clusterFileHandler = eZClusterFileHandler::instance($data['cache_path']); // Check last modified time and return path to cache file if valid if ($clusterFileHandler->fileExists($data['cache_path']) && $data['last_modified'] <= $clusterFileHandler->mtime($data['cache_path'])) { $data['http'][] = $data['custom_host'] . $data['www_dir'] . $data['cache_path']; self::$log[] = $data; return $data['http']; } } // Merge file content and create new cache file $content = ''; $isCSS = $data['file_extension'] === '.css'; foreach ($data['locale'] as $i => $file) { // if this is a js / css generator, call to get content if ($file instanceof ezjscServerRouter) { $content .= $file->call($data['locale']); continue; } else { if (!$file) { continue; } } // else, get content of normal file $fileContent = file_get_contents($file); if (!trim($fileContent)) { $content .= "/* empty: {$file} */\r\n"; continue; } if ($isCSS) { // We need to fix relative background image paths if this is a css file $fileContent = ezjscPacker::fixImgPaths($fileContent, $file); // Remove @charset if this is not the first file (some browsers will ignore css after a second occurance of this) if ($i) { $fileContent = preg_replace('/^@charset[^;]+;/i', '', $fileContent); } } $content .= "/* start: {$file} */\r\n"; $content .= $fileContent; $content .= "\r\n/* end: {$file} */\r\n\r\n"; } // Pack all files to save bandwidth if ($data['pack_level'] > 1) { foreach ($ezjscINI->variable('eZJSCore', $isCSS ? 'CssOptimizer' : 'JavaScriptOptimizer') as $optimizer) { if (is_callable(array($optimizer, 'optimize'))) { $content = call_user_func(array($optimizer, 'optimize'), $content, $data['pack_level']); } else { eZDebug::writeWarning("Could not call optimizer '{$optimizer}'", __METHOD__); } } } // Save cache file and return path $clusterFileHandler->fileStoreContents($data['cache_path'], $content, 'ezjscore', $isCSS ? 'text/css' : 'text/javascript'); $data['http'][] = $data['custom_host'] . $data['www_dir'] . $data['cache_path']; self::$log[] = $data; return $data['http']; }
/** * generateTag * @param array $files * @return string $html */ private function generateTag($files) { eZDebug::writeDebug($files, 'ezLessOperator::generateTag'); $html = $cssContent = ''; $ini = eZINI::instance('ezless.ini'); $compileMethod = trim($ini->variable('ezlessconfig', 'CompileMethod')); $useOneFile = $ini->variable('ezlessconfig', 'useOneFile'); // ToDo: siteaccess as parameter $bases = eZTemplateDesignResource::allDesignBases(); $triedFiles = array(); $importsTried = array(); if ($compileMethod === 'javascript') { foreach ($files as $file) { $match = eZTemplateDesignResource::fileMatch($bases, '', 'stylesheets/' . $file, $triedFiles); if ($match) { $path = "/{$match['path']}"; $html .= "<link rel=\"stylesheet/less\" type=\"text/css\" href=\"{$path}\">" . PHP_EOL; } } $lessJSFilename = $ini->variable('ezlessconfig', 'LessJSFile'); $lookForLessJS = eZTemplateDesignResource::fileMatch($bases, '', 'javascript/' . $lessJSFilename, $triedFiles); if (!$lookForLessJS) { eZDebug::writeDebug("Using LessJS mode but unable to find less.js (LessJSFile={$lessJSFilename}).\nTried files : " . implode("\n", $triedFiles), __CLASS__ . "::" . __FUNCTION__); } else { $path = "/{$lookForLessJS['path']}"; $html .= "<script src=\"{$path}\" type=\"text/javascript\" ></script>" . PHP_EOL; } return $html; } elseif ($compileMethod === 'lessphp') { $sys = eZSys::instance(); $path = $sys->cacheDirectory() . '/public/stylesheets'; require_once dirname(__FILE__) . '/../lib/lessphp/lessc.inc.php'; $packerLevel = $this->getPackerLevel(); $less = new lessc(); foreach ($bases as $base) { $less->importDir[] = $base . DIRECTORY_SEPARATOR . 'stylesheets'; } $importContent = ""; $importCss = ""; if (count(self::$imports) > 0) { foreach (self::$imports as $import) { $match = eZTemplateDesignResource::fileMatch($bases, '', 'stylesheets/' . $import, $importsTried); $importCss = file_get_contents($match['path']); $importContent .= $importCss; } } foreach ($files as $file) { $match = eZTemplateDesignResource::fileMatch($bases, '', 'stylesheets/' . $file, $triedFiles); $content = file_get_contents($match['path']); $content = ezjscPacker::fixImgPaths($content, $match['path']); if ($useOneFile == "true") { $cssContent .= $content; } else { try { $parsedContent = $less->parse($importContent . $content); if ($packerLevel > 1) { $parsedContent = $this->optimizeCSS($parsedContent, $packerLevel); } // $file = md5(uniqid(mt_rand(), true)) . ".css"; $file = substr($file, 0, -4) . 'css'; // we wan't to know what's the name of the less file on the browser $file = $path . '/' . $file; $clusterFile = eZClusterFileHandler::instance($file); $clusterFile->storeContents($parsedContent, 'ezless', 'text/css'); eZURI::transformURI($file, true); $html .= '<link rel="stylesheet" type="text/css" href="' . $file . '" />' . PHP_EOL; } catch (Exception $e) { eZDebug::writeError($e->getMessage(), 'ezLessOperator for ' . $match['path']); } } } if ($useOneFile == "true") { $file = md5(uniqid(mt_rand(), true)) . ".css"; try { $parsedContent = $less->parse($cssContent); if ($packerLevel > 1) { $parsedContent = $this->optimizeCSS($parsedContent, $packerLevel); } $file = $path . '/' . $file; $clusterFile = eZClusterFileHandler::instance($file); $clusterFile->storeContents($parsedContent, 'ezless', 'text/css'); eZURI::transformURI($file, true); $html = '<link rel="stylesheet" type="text/css" href="' . $file . '" />' . PHP_EOL; } catch (Exception $e) { eZDebug::writeError($e->getMessage(), 'ezLessOperator parsing error'); } } return $html; } else { eZDebug::writeError("Unknown compile method : '{$compileMethod}'", __CLASS__ . "::" . __FUNCTION__); } }