1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25:
26:
27:
28: class Mage_Core_Model_Design_Package
29: {
30: const DEFAULT_AREA = 'frontend';
31: const DEFAULT_PACKAGE = 'default';
32: const DEFAULT_THEME = 'default';
33: const BASE_PACKAGE = 'base';
34:
35: 36: 37:
38: const FALLBACK_THEME = 'default';
39:
40: private static $_regexMatchCache = array();
41: private static $_customThemeTypeCache = array();
42:
43: 44: 45: 46: 47:
48: protected $_store = null;
49:
50: 51: 52: 53: 54:
55: protected $_area;
56:
57: 58: 59: 60: 61:
62: protected $_name;
63:
64: 65: 66: 67: 68:
69: protected $_theme;
70:
71: 72: 73: 74: 75:
76: protected $_rootDir;
77:
78: 79: 80: 81: 82:
83: protected $_callbackFileDir;
84:
85: protected $_config = null;
86:
87: 88: 89: 90: 91:
92: protected $_shouldFallback = true;
93:
94: 95: 96: 97: 98: 99:
100: public function setStore($store)
101: {
102: $this->_store = $store;
103: return $this;
104: }
105:
106: 107: 108: 109: 110:
111: public function getStore()
112: {
113: if ($this->_store === null) {
114: return Mage::app()->getStore();
115: }
116: return $this->_store;
117: }
118:
119: 120: 121: 122: 123: 124:
125: public function setArea($area)
126: {
127: $this->_area = $area;
128: return $this;
129: }
130:
131: 132: 133: 134: 135:
136: public function getArea()
137: {
138: if (is_null($this->_area)) {
139: $this->_area = self::DEFAULT_AREA;
140: }
141: return $this->_area;
142: }
143:
144: 145: 146: 147: 148: 149: 150:
151: public function setPackageName($name = '')
152: {
153: if (empty($name)) {
154:
155: $customPackage = $this->_checkUserAgentAgainstRegexps('design/package/ua_regexp');
156: if ($customPackage) {
157: $this->_name = $customPackage;
158: }
159: else {
160: $this->_name = Mage::getStoreConfig('design/package/name', $this->getStore());
161: }
162: }
163: else {
164: $this->_name = $name;
165: }
166:
167: if (!$this->designPackageExists($this->_name, $this->getArea())) {
168: $this->_name = self::DEFAULT_PACKAGE;
169: }
170: return $this;
171: }
172:
173: 174: 175: 176: 177: 178: 179: 180: 181:
182: public function setAllGetOld($storePackageArea)
183: {
184: $oldValues = array();
185: if (array_key_exists('store', $storePackageArea)) {
186: $oldValues['store'] = $this->getStore();
187: $this->setStore($storePackageArea['store']);
188: }
189: if (array_key_exists('area', $storePackageArea)) {
190: $oldValues['area'] = $this->getArea();
191: $this->setArea($storePackageArea['area']);
192: }
193: if (array_key_exists('package', $storePackageArea)) {
194: $oldValues['package'] = $this->getPackageName();
195: $this->setPackageName($storePackageArea['package']);
196: }
197: return $oldValues;
198: }
199:
200: 201: 202: 203: 204:
205: public function getPackageName()
206: {
207: if (null === $this->_name) {
208: $this->setPackageName();
209: }
210: return $this->_name;
211: }
212:
213: public function designPackageExists($packageName, $area = self::DEFAULT_AREA)
214: {
215: return is_dir(Mage::getBaseDir('design') . DS . $area . DS . $packageName);
216: }
217:
218: 219: 220: 221: 222: 223: 224: 225:
226: public function setTheme()
227: {
228: switch (func_num_args()) {
229: case 1:
230: foreach (array('layout', 'template', 'skin', 'locale') as $type) {
231: $this->_theme[$type] = func_get_arg(0);
232: }
233: break;
234:
235: case 2:
236: $this->_theme[func_get_arg(0)] = func_get_arg(1);
237: break;
238:
239: default:
240: throw Mage::exception(Mage::helper('core')->__('Wrong number of arguments for %s', __METHOD__));
241: }
242: return $this;
243: }
244:
245: public function getTheme($type)
246: {
247: if (empty($this->_theme[$type])) {
248: $this->_theme[$type] = Mage::getStoreConfig('design/theme/'.$type, $this->getStore());
249: if ($type!=='default' && empty($this->_theme[$type])) {
250: $this->_theme[$type] = $this->getTheme('default');
251: if (empty($this->_theme[$type])) {
252: $this->_theme[$type] = self::DEFAULT_THEME;
253: }
254:
255:
256: }
257: }
258:
259:
260:
261:
262: $customThemeType = $this->_checkUserAgentAgainstRegexps("design/theme/{$type}_ua_regexp");
263: if ($customThemeType) {
264: $this->_theme[$type] = $customThemeType;
265: }
266:
267: return $this->_theme[$type];
268: }
269:
270: public function getDefaultTheme()
271: {
272: return self::DEFAULT_THEME;
273: }
274:
275: public function updateParamDefaults(array &$params)
276: {
277: if ($this->getStore()) {
278: $params['_store'] = $this->getStore();
279: }
280: if (empty($params['_area'])) {
281: $params['_area'] = $this->getArea();
282: }
283: if (empty($params['_package'])) {
284: $params['_package'] = $this->getPackageName();
285: }
286: if (empty($params['_theme'])) {
287: $params['_theme'] = $this->getTheme( (isset($params['_type'])) ? $params['_type'] : '' );
288: }
289: if (empty($params['_default'])) {
290: $params['_default'] = false;
291: }
292: return $this;
293: }
294:
295: public function getBaseDir(array $params)
296: {
297: $this->updateParamDefaults($params);
298: $baseDir = (empty($params['_relative']) ? Mage::getBaseDir('design').DS : '').
299: $params['_area'].DS.$params['_package'].DS.$params['_theme'].DS.$params['_type'];
300: return $baseDir;
301: }
302:
303: public function getSkinBaseDir(array $params=array())
304: {
305: $params['_type'] = 'skin';
306: $this->updateParamDefaults($params);
307: $baseDir = (empty($params['_relative']) ? Mage::getBaseDir('skin').DS : '').
308: $params['_area'].DS.$params['_package'].DS.$params['_theme'];
309: return $baseDir;
310: }
311:
312: public function getLocaleBaseDir(array $params=array())
313: {
314: $params['_type'] = 'locale';
315: $this->updateParamDefaults($params);
316: $baseDir = (empty($params['_relative']) ? Mage::getBaseDir('design').DS : '').
317: $params['_area'].DS.$params['_package'].DS.$params['_theme'] . DS . 'locale' . DS .
318: Mage::app()->getLocale()->getLocaleCode();
319: return $baseDir;
320: }
321:
322: public function getSkinBaseUrl(array $params=array())
323: {
324: $params['_type'] = 'skin';
325: $this->updateParamDefaults($params);
326: $baseUrl = Mage::getBaseUrl('skin', isset($params['_secure'])?(bool)$params['_secure']:null)
327: .$params['_area'].'/'.$params['_package'].'/'.$params['_theme'].'/';
328: return $baseUrl;
329: }
330:
331: 332: 333: 334: 335: 336: 337: 338: 339: 340: 341: 342: 343: 344:
345: public function validateFile($file, array $params)
346: {
347: $fileName = $this->_renderFilename($file, $params);
348: $testFile = (empty($params['_relative']) ? '' : Mage::getBaseDir('design') . DS) . $fileName;
349: if (!file_exists($testFile)) {
350: return false;
351: }
352: return $fileName;
353: }
354:
355: 356: 357: 358: 359: 360: 361:
362: protected function _renderFilename($file, array $params)
363: {
364: switch ($params['_type']) {
365: case 'skin':
366: $dir = $this->getSkinBaseDir($params);
367: break;
368:
369: case 'locale':
370: $dir = $this->getLocaleBasedir($params);
371: break;
372:
373: default:
374: $dir = $this->getBaseDir($params);
375: break;
376: }
377: return $dir . DS . $file;
378: }
379:
380: 381: 382: 383: 384: 385: 386: 387: 388: 389: 390: 391:
392: protected function _fallback($file, array &$params, array $fallbackScheme = array(array()))
393: {
394: if ($this->_shouldFallback) {
395: foreach ($fallbackScheme as $try) {
396: $params = array_merge($params, $try);
397: $filename = $this->validateFile($file, $params);
398: if ($filename) {
399: return $filename;
400: }
401: }
402: $params['_package'] = self::BASE_PACKAGE;
403: $params['_theme'] = self::DEFAULT_THEME;
404: }
405: return $this->_renderFilename($file, $params);
406: }
407:
408: 409: 410: 411: 412: 413: 414: 415: 416:
417: public function getFilename($file, array $params)
418: {
419: Varien_Profiler::start(__METHOD__);
420: $this->updateParamDefaults($params);
421: $result = $this->_fallback($file, $params, array(
422: array(),
423: array('_theme' => $this->getFallbackTheme()),
424: array('_theme' => self::DEFAULT_THEME),
425: ));
426: Varien_Profiler::stop(__METHOD__);
427: return $result;
428: }
429:
430: 431: 432: 433:
434: public function getFallbackTheme()
435: {
436: return Mage::getStoreConfig('design/theme/default', $this->getStore());
437: }
438:
439: public function getLayoutFilename($file, array $params=array())
440: {
441: $params['_type'] = 'layout';
442: return $this->getFilename($file, $params);
443: }
444:
445: public function getTemplateFilename($file, array $params=array())
446: {
447: $params['_type'] = 'template';
448: return $this->getFilename($file, $params);
449: }
450:
451: public function getLocaleFileName($file, array $params=array())
452: {
453: $params['_type'] = 'locale';
454: return $this->getFilename($file, $params);
455: }
456:
457: 458: 459: 460: 461: 462: 463:
464: public function getSkinUrl($file = null, array $params = array())
465: {
466: Varien_Profiler::start(__METHOD__);
467: if (empty($params['_type'])) {
468: $params['_type'] = 'skin';
469: }
470: if (empty($params['_default'])) {
471: $params['_default'] = false;
472: }
473: $this->updateParamDefaults($params);
474: if (!empty($file)) {
475: $result = $this->_fallback($file, $params, array(
476: array(),
477: array('_theme' => $this->getFallbackTheme()),
478: array('_theme' => self::DEFAULT_THEME),
479: ));
480: }
481: $result = $this->getSkinBaseUrl($params) . (empty($file) ? '' : $file);
482: Varien_Profiler::stop(__METHOD__);
483: return $result;
484: }
485:
486: 487: 488: 489:
490: public function getPackageList()
491: {
492: $directory = Mage::getBaseDir('design') . DS . 'frontend';
493: return $this->_listDirectories($directory);
494: }
495:
496: 497: 498: 499: 500:
501: public function getThemeList($package = null)
502: {
503: $result = array();
504:
505: if (is_null($package)){
506: foreach ($this->getPackageList() as $package){
507: $result[$package] = $this->getThemeList($package);
508: }
509: } else {
510: $directory = Mage::getBaseDir('design') . DS . 'frontend' . DS . $package;
511: $result = $this->_listDirectories($directory);
512: }
513:
514: return $result;
515: }
516:
517: 518: 519: 520: 521: 522: 523:
524: private function _listDirectories($path, $fullPath = false)
525: {
526: $result = array();
527: $dir = opendir($path);
528: if ($dir) {
529: while ($entry = readdir($dir)) {
530: if (substr($entry, 0, 1) == '.' || !is_dir($path . DS . $entry)){
531: continue;
532: }
533: if ($fullPath) {
534: $entry = $path . DS . $entry;
535: }
536: $result[] = $entry;
537: }
538: unset($entry);
539: closedir($dir);
540: }
541:
542: return $result;
543: }
544:
545: 546: 547: 548: 549: 550: 551: 552: 553:
554: protected function _checkUserAgentAgainstRegexps($regexpsConfigPath)
555: {
556: if (empty($_SERVER['HTTP_USER_AGENT'])) {
557: return false;
558: }
559:
560: if (!empty(self::$_customThemeTypeCache[$regexpsConfigPath])) {
561: return self::$_customThemeTypeCache[$regexpsConfigPath];
562: }
563:
564: $configValueSerialized = Mage::getStoreConfig($regexpsConfigPath, $this->getStore());
565:
566: if (!$configValueSerialized) {
567: return false;
568: }
569:
570: $regexps = @unserialize($configValueSerialized);
571:
572: if (empty($regexps)) {
573: return false;
574: }
575:
576: return self::getPackageByUserAgent($regexps, $regexpsConfigPath);
577: }
578:
579: 580: 581: 582: 583: 584: 585:
586: public static function getPackageByUserAgent(array $rules, $regexpsConfigPath = 'path_mock')
587: {
588: foreach ($rules as $rule) {
589: if (!empty(self::$_regexMatchCache[$rule['regexp']][$_SERVER['HTTP_USER_AGENT']])) {
590: self::$_customThemeTypeCache[$regexpsConfigPath] = $rule['value'];
591: return $rule['value'];
592: }
593:
594: $regexp = '/' . trim($rule['regexp'], '/') . '/';
595:
596: if (@preg_match($regexp, $_SERVER['HTTP_USER_AGENT'])) {
597: self::$_regexMatchCache[$rule['regexp']][$_SERVER['HTTP_USER_AGENT']] = true;
598: self::$_customThemeTypeCache[$regexpsConfigPath] = $rule['value'];
599: return $rule['value'];
600: }
601: }
602:
603: return false;
604: }
605:
606: 607: 608: 609: 610: 611:
612: public function getMergedJsUrl($files)
613: {
614: $targetFilename = md5(implode(',', $files)) . '.js';
615: $targetDir = $this->_initMergerDir('js');
616: if (!$targetDir) {
617: return '';
618: }
619: if ($this->_mergeFiles($files, $targetDir . DS . $targetFilename, false, null, 'js')) {
620: return Mage::getBaseUrl('media', Mage::app()->getRequest()->isSecure()) . 'js/' . $targetFilename;
621: }
622: return '';
623: }
624:
625: 626: 627: 628: 629: 630:
631: public function getMergedCssUrl($files)
632: {
633:
634: $isSecure = Mage::app()->getRequest()->isSecure();
635: $mergerDir = $isSecure ? 'css_secure' : 'css';
636: $targetDir = $this->_initMergerDir($mergerDir);
637: if (!$targetDir) {
638: return '';
639: }
640:
641:
642: $baseMediaUrl = Mage::getBaseUrl('media', $isSecure);
643: $hostname = parse_url($baseMediaUrl, PHP_URL_HOST);
644: $port = parse_url($baseMediaUrl, PHP_URL_PORT);
645: if (false === $port) {
646: $port = $isSecure ? 443 : 80;
647: }
648:
649:
650: $targetFilename = md5(implode(',', $files) . "|{$hostname}|{$port}") . '.css';
651: $mergeFilesResult = $this->_mergeFiles(
652: $files, $targetDir . DS . $targetFilename,
653: false,
654: array($this, 'beforeMergeCss'),
655: 'css'
656: );
657: if ($mergeFilesResult) {
658: return $baseMediaUrl . $mergerDir . '/' . $targetFilename;
659: }
660: return '';
661: }
662:
663: 664: 665: 666: 667: 668: 669: 670: 671: 672: 673:
674: protected function _mergeFiles(array $srcFiles, $targetFile = false,
675: $mustMerge = false, $beforeMergeCallback = null, $extensionsFilter = array())
676: {
677: if (Mage::helper('core/file_storage_database')->checkDbUsage()) {
678: if (!file_exists($targetFile)) {
679: Mage::helper('core/file_storage_database')->saveFileToFilesystem($targetFile);
680: }
681: if (file_exists($targetFile)) {
682: $filemtime = filemtime($targetFile);
683: } else {
684: $filemtime = null;
685: }
686: $result = Mage::helper('core')->mergeFiles(
687: $srcFiles,
688: $targetFile,
689: $mustMerge,
690: $beforeMergeCallback,
691: $extensionsFilter
692: );
693: if ($result && (filemtime($targetFile) > $filemtime)) {
694: Mage::helper('core/file_storage_database')->saveFile($targetFile);
695: }
696: return $result;
697:
698: } else {
699: return Mage::helper('core')->mergeFiles(
700: $srcFiles,
701: $targetFile,
702: $mustMerge,
703: $beforeMergeCallback,
704: $extensionsFilter
705: );
706: }
707: }
708:
709: 710: 711: 712: 713:
714: public function cleanMergedJsCss()
715: {
716: $result = (bool)$this->_initMergerDir('js', true);
717: $result = (bool)$this->_initMergerDir('css', true) && $result;
718: return (bool)$this->_initMergerDir('css_secure', true) && $result;
719: }
720:
721: 722: 723: 724: 725: 726: 727: 728:
729: protected function _initMergerDir($dirRelativeName, $cleanup = false)
730: {
731: $mediaDir = Mage::getBaseDir('media');
732: try {
733: $dir = Mage::getBaseDir('media') . DS . $dirRelativeName;
734: if ($cleanup) {
735: Varien_Io_File::rmdirRecursive($dir);
736: Mage::helper('core/file_storage_database')->deleteFolder($dir);
737: }
738: if (!is_dir($dir)) {
739: mkdir($dir);
740: }
741: return is_writeable($dir) ? $dir : false;
742: } catch (Exception $e) {
743: Mage::logException($e);
744: }
745: return false;
746: }
747:
748: 749: 750: 751: 752: 753: 754:
755: public function beforeMergeCss($file, $contents)
756: {
757: $this->_setCallbackFileDir($file);
758:
759: $cssImport = '/@import\\s+([\'"])(.*?)[\'"]/';
760: $contents = preg_replace_callback($cssImport, array($this, '_cssMergerImportCallback'), $contents);
761:
762: $cssUrl = '/url\\(\\s*(?!data:)([^\\)\\s]+)\\s*\\)?/';
763: $contents = preg_replace_callback($cssUrl, array($this, '_cssMergerUrlCallback'), $contents);
764:
765: return $contents;
766: }
767:
768: 769: 770: 771: 772:
773: protected function _setCallbackFileDir($file)
774: {
775: $file = str_replace(Mage::getBaseDir().DS, '', $file);
776: $this->_callbackFileDir = dirname($file);
777: }
778:
779: 780: 781: 782: 783: 784:
785: protected function _cssMergerImportCallback($match)
786: {
787: $quote = $match[1];
788: $uri = $this->_prepareUrl($match[2]);
789:
790: return "@import {$quote}{$uri}{$quote}";
791: }
792:
793: 794: 795: 796: 797: 798:
799: protected function _cssMergerUrlCallback($match)
800: {
801: $quote = ($match[1][0] == "'" || $match[1][0] == '"') ? $match[1][0] : '';
802: $uri = ($quote == '') ? $match[1] : substr($match[1], 1, strlen($match[1]) - 2);
803: $uri = $this->_prepareUrl($uri);
804:
805: return "url({$quote}{$uri}{$quote})";
806: }
807:
808: 809: 810: 811: 812: 813:
814: protected function _prepareUrl($uri)
815: {
816:
817: if (!preg_match('/^https?:/i', $uri) && !preg_match('/^\//i', $uri)) {
818: $fileDir = '';
819: $pathParts = explode(DS, $uri);
820: $fileDirParts = explode(DS, $this->_callbackFileDir);
821: $store = $this->getStore();
822: if ($store->isAdmin()) {
823: $secure = $store->isAdminUrlSecure();
824: } else {
825: $secure = $store->isFrontUrlSecure() && Mage::app()->getRequest()->isSecure();
826: }
827:
828: if ('skin' == $fileDirParts[0]) {
829: $baseUrl = Mage::getBaseUrl('skin', $secure);
830: $fileDirParts = array_slice($fileDirParts, 1);
831: } elseif ('media' == $fileDirParts[0]) {
832: $baseUrl = Mage::getBaseUrl('media', $secure);
833: $fileDirParts = array_slice($fileDirParts, 1);
834: } else {
835: $baseUrl = Mage::getBaseUrl('web', $secure);
836: }
837:
838: foreach ($pathParts as $key=>$part) {
839: if ($part == '.' || $part == '..') {
840: unset($pathParts[$key]);
841: }
842: if ($part == '..' && count($fileDirParts)) {
843: $fileDirParts = array_slice($fileDirParts, 0, count($fileDirParts) - 1);
844: }
845: }
846:
847: if (count($fileDirParts)) {
848: $fileDir = implode('/', $fileDirParts).'/';
849: }
850:
851: $uri = $baseUrl.$fileDir.implode('/', $pathParts);
852: }
853: return $uri;
854: }
855: }
856: