1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10:
11:
12: namespace Xoops\Core;
13:
14: use Assetic\AssetManager;
15: use Assetic\FilterManager;
16: use Assetic\Filter;
17: use Assetic\Factory\AssetFactory;
18: use Assetic\Factory\Worker\CacheBustingWorker;
19: use Assetic\AssetWriter;
20: use Assetic\Asset\AssetCollection;
21: use Assetic\Asset\FileAsset;
22: use Assetic\Asset\GlobAsset;
23:
24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35:
36: class Assets
37: {
38: 39: 40:
41: private $debug = false;
42:
43: 44: 45:
46: private $default_filters = array(
47: 'css' => 'cssimport,cssembed,?cssmin',
48: 'js' => '?jsqueeze',
49: );
50:
51: 52: 53:
54: private $default_output = array(
55: 'css' => 'css/*.css',
56: 'js' => 'js/*.js',
57: );
58:
59: 60: 61:
62: private $default_asset_refs = array(
63: array(
64: 'name' => 'jquery',
65: 'assets' => array('media/jquery/jquery.js'),
66: 'filters' => null,
67: ),
68: array(
69: 'name' => 'jqueryui',
70: 'assets' => array('media/jquery/ui/jquery-ui.js'),
71: 'filters' => null,
72: ),
73: array(
74: 'name' => 'jgrowl',
75: 'assets' => array('media/jquery/plugins/jquery.jgrowl.js'),
76: 'filters' => null,
77: ),
78: );
79:
80: 81: 82:
83: private $assetManager = null;
84:
85: 86: 87:
88: private $assetsPrefsFilename = 'var/configs/system_assets_prefs.yml';
89:
90: 91: 92:
93: private $assetsPrefsCacheKey = 'system/assets/prefs';
94:
95: 96: 97:
98:
99: private $filterInterface = '\Assetic\Filter\FilterInterface';
100: 101: 102:
103: public function __construct()
104: {
105: $this->assetManager = new AssetManager();
106: if (isset($_REQUEST['ASSET_DEBUG'])) {
107: $this->setDebug();
108: }
109: $this->readAssetsPrefs();
110:
111: foreach ($this->default_asset_refs as $ref) {
112: $this->registerAssetReference($ref['name'], $ref['assets'], $ref['filters']);
113: }
114: }
115:
116: 117: 118: 119: 120:
121: protected function readAssetsPrefs()
122: {
123: $xoops = \Xoops::getInstance();
124:
125: $assetsPrefs = array();
126:
127: try {
128: $assetsPrefs = $xoops->cache()->read($this->assetsPrefsCacheKey);
129: $file = $xoops->path($this->assetsPrefsFilename);
130: $mtime = filemtime($file);
131: if ($assetsPrefs===false || !isset($assetsPrefs['mtime']) || !$mtime
132: || (isset($assetsPrefs['mtime']) && $assetsPrefs['mtime']<$mtime)) {
133: if ($mtime) {
134: $assetsPrefs = Yaml::read($file);
135: if (!is_array($assetsPrefs)) {
136: $xoops->logger()->error("Invalid config in system_assets_prefs.yml");
137: $assetsPrefs = array();
138: } else {
139: $assetsPrefs['mtime']=$mtime;
140: $xoops->cache()->write($this->assetsPrefsCacheKey, $assetsPrefs);
141: }
142: } else {
143:
144: $assetsPrefs = array(
145: 'default_filters' => $this->default_filters,
146: 'default_asset_refs' => $this->default_asset_refs,
147: 'mtime' => time(),
148: );
149: $this->saveAssetsPrefs($assetsPrefs);
150: }
151: }
152: if (!empty($assetsPrefs['default_filters']) && is_array($assetsPrefs['default_filters'])) {
153: $this->default_filters = $assetsPrefs['default_filters'];
154: }
155: if (!empty($assetsPrefs['default_asset_refs']) && is_array($assetsPrefs['default_asset_refs'])) {
156: $this->default_asset_refs = $assetsPrefs['default_asset_refs'];
157: }
158: } catch (\Exception $e) {
159: $xoops->events()->triggerEvent('core.exception', $e);
160: $assetsPrefs = array();
161: }
162: return $assetsPrefs;
163: }
164:
165: 166: 167: 168: 169: 170: 171: 172:
173: protected function saveAssetsPrefs($assets_prefs)
174: {
175: if (is_array($assets_prefs)) {
176: $xoops = \Xoops::getInstance();
177: try {
178: Yaml::save($assets_prefs, $xoops->path($this->assetsPrefsFilename));
179: $xoops->cache()->write($this->assetsPrefsCacheKey, $assets_prefs);
180: } catch (\Exception $e) {
181: $xoops->events()->triggerEvent('core.exception', $e);
182: }
183: }
184: }
185:
186:
187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199:
200: public function getUrlToAssets($type, $assets, $filters = 'default', $target = null)
201: {
202: if (is_scalar($assets)) {
203: $assets = array($assets);
204: }
205:
206: if ($filters==='default') {
207: if (isset($this->default_filters[$type])) {
208: $filters = $this->default_filters[$type];
209: } else {
210: $filters = '';
211: }
212: }
213:
214: if (!is_array($filters)) {
215: if (empty($filters)) {
216: $filters = array();
217: } else {
218: $filters = explode(',', str_replace(' ', '', $filters));
219: }
220: }
221:
222: if (isset($this->default_output[$type])) {
223: $output = $this->default_output[$type];
224: } else {
225: $output = '';
226: }
227:
228: $xoops = \Xoops::getInstance();
229:
230: if (isset($target)) {
231: $target_path = $target;
232: } else {
233: $target_path = $xoops->path('assets');
234: }
235:
236: try {
237: $am = $this->assetManager;
238: $fm = new FilterManager();
239:
240: foreach ($filters as $filter) {
241: if (is_object($filter) && $filter instanceof $this->filterInterface) {
242: $filterArray[] = $filter;
243: } else {
244: switch (ltrim($filter, '?')) {
245: case 'cssembed':
246: $fm->set('cssembed', new Filter\PhpCssEmbedFilter());
247: break;
248: case 'cssmin':
249: $fm->set('cssmin', new Filter\CssMinFilter());
250: break;
251: case 'cssimport':
252: $fm->set('cssimport', new Filter\CssImportFilter());
253: break;
254: case 'cssrewrite':
255: $fm->set('cssrewrite', new Filter\CssRewriteFilter());
256: break;
257: case 'lessphp':
258: $fm->set('lessphp', new Filter\LessphpFilter());
259: break;
260: case 'scssphp':
261: $fm->set('scssphp', new Filter\ScssphpFilter());
262: break;
263: case 'jsmin':
264: $fm->set('jsmin', new Filter\JSMinFilter());
265: break;
266: case 'jsqueeze':
267: $fm->set('jsqueeze', new Filter\JSqueezeFilter());
268: break;
269: default:
270: throw new \Exception(sprintf('%s filter not implemented.', $filter));
271: break;
272: }
273: }
274: }
275:
276:
277: $factory = new AssetFactory($target_path);
278: $factory->setAssetManager($am);
279: $factory->setFilterManager($fm);
280: $factory->setDefaultOutput($output);
281: $factory->setDebug($this->debug);
282: $factory->addWorker(new CacheBustingWorker());
283:
284:
285: $writer = new AssetWriter($target_path);
286:
287:
288: $translated_assets = array();
289: foreach ($assets as $k => $v) {
290:
291: if (0 == preg_match("/^\\/|^\\\\|^[a-zA-Z]:|^@/", $v)) {
292: $v = $xoops->path($v);
293: }
294: if (!in_array($v, $translated_assets)) {
295: $translated_assets[] = $v;
296: }
297: }
298:
299:
300: $asset = $factory->createAsset(
301: $translated_assets,
302: $filters
303: );
304: $asset_path = $asset->getTargetPath();
305: if (!is_readable($target_path . $asset_path)) {
306: $assetKey = 'Asset '.$asset_path;
307: $xoops->events()->triggerEvent('debug.timer.start', $assetKey);
308: $oldumask = umask(0002);
309: $writer->writeAsset($asset);
310: umask($oldumask);
311: $xoops->events()->triggerEvent('debug.timer.stop', $assetKey);
312: }
313:
314: return $xoops->url('assets/' . $asset_path);
315:
316: } catch (\Exception $e) {
317: $xoops->events()->triggerEvent('core.exception', $e);
318: return null;
319: }
320: }
321:
322:
323: 324: 325: 326: 327:
328: public function setDebug()
329: {
330: $this->debug = true;
331: return true;
332: }
333:
334: 335: 336: 337: 338: 339: 340: 341: 342: 343: 344:
345: public function registerAssetReference($name, $assets, $filters = null)
346: {
347: $xoops = \Xoops::getInstance();
348:
349: $assetArray = array();
350: $filterArray = array();
351:
352: try {
353: if (is_scalar($assets)) {
354: $assets = array($assets);
355: }
356: foreach ($assets as $a) {
357:
358: if ((substr_compare($a, '@', 0, 1) != 0)
359: && (substr_compare($a, '/', 0, 1) != 0)) {
360: $a = $xoops->path($a);
361: }
362: if (false===strpos($a, '*')) {
363: $assetArray[] = new FileAsset($a);
364: } else {
365: $assetArray[] = new GlobAsset($a);
366: }
367: }
368:
369: if (!is_array($filters)) {
370: if (empty($filters)) {
371: $filters = array();
372: } else {
373: $filters = explode(',', str_replace(' ', '', $filters));
374: }
375: }
376: foreach ($filters as $filter) {
377: if (is_object($filter) && $filter instanceof $this->filterInterface) {
378: $filterArray[] = $filter;
379: } else {
380: switch (ltrim($filter, '?')) {
381: case 'cssembed':
382: $filterArray[] = new Filter\PhpCssEmbedFilter();
383: break;
384: case 'cssmin':
385: $filterArray[] = new Filter\CssMinFilter();
386: break;
387: case 'cssimport':
388: $filterArray[] = new Filter\CssImportFilter();
389: break;
390: case 'cssrewrite':
391: $filterArray[] = new Filter\CssRewriteFilter();
392: break;
393: case 'lessphp':
394: $filterArray[] = new Filter\LessphpFilter();
395: break;
396: case 'scssphp':
397: $filterArray[] = new Filter\ScssphpFilter();
398: break;
399: case 'jsmin':
400: $filterArray[] = new Filter\JSMinFilter();
401: break;
402: case 'jsqueeze':
403: $filterArray[] = new Filter\JSqueezeFilter();
404: break;
405: default:
406: throw new \Exception(sprintf('%s filter not implemented.', $filter));
407: break;
408: }
409: }
410: }
411:
412: $collection = new AssetCollection($assetArray, $filterArray);
413: $this->assetManager->set($name, $collection);
414:
415: return true;
416: } catch (\Exception $e) {
417: $xoops->events()->triggerEvent('core.exception', $e);
418: return false;
419: }
420: }
421:
422: 423: 424: 425: 426: 427: 428: 429: 430: 431: 432: 433: 434:
435: public function copyFileAssets($from_path, $pattern, $output)
436: {
437: $xoops = \Xoops::getInstance();
438:
439: $to_path = $xoops->path('assets') . '/' . $output . '/';
440: $from = glob($from_path . '/' . $pattern);
441: $xoops->events()->triggerEvent('debug.log', $from);
442:
443: if (!is_dir($to_path)) {
444: $oldUmask = umask(0);
445: mkdir($to_path, 0775, true);
446: umask($oldUmask);
447: }
448:
449: if (!is_writable($to_path)) {
450: $xoops->logger()->warning('Asset directory is not writable. ' . $output);
451: return false;
452: } else {
453: $count = 0;
454: $oldUmask = umask(0002);
455: foreach ($from as $filepath) {
456: $filename = basename($filepath);
457: $status=copy($filepath, $to_path.$filename);
458: if (false) {
459: $xoops->logger()->warning('Failed to copy asset '.$filename);
460: } else {
461: $xoops->logger()->debug('Copied asset '.$filename);
462: ++$count;
463: }
464: }
465: umask($oldUmask);
466: return $count;
467: }
468: }
469: }
470: