PageRenderTime 61ms CodeModel.GetById 11ms RepoModel.GetById 1ms app.codeStats 0ms

/views/helpers/resource.php

https://github.com/asecondwill/syrup
PHP | 416 lines | 255 code | 89 blank | 72 comment | 68 complexity | bda5479f742052fa36ba6c23223d6c47 MD5 | raw file
  1. <?php
  2. App::import('Helper', 'Html');
  3. class ResourceHelper extends AppHelper {
  4. /**
  5. * Helpers used by this helper
  6. *
  7. * @var array
  8. */
  9. public $helpers = array('Html', 'Javascript');
  10. /**
  11. * Included Javascripts
  12. *
  13. * @var array
  14. */
  15. private static $__javascripts = array();
  16. /**
  17. * Included Stylesheets
  18. *
  19. * @var array
  20. */
  21. private static $__stylesheets = array();
  22. /**
  23. * Before rendering a view check for automatic files
  24. */
  25. public function beforeRender() {
  26. parent::beforeRender();
  27. if (!empty($this->params['controller']) && !empty($this->params['action'])) {
  28. $candidates = array();
  29. $view = ClassRegistry::getObject('view');
  30. if (!empty($view)) {
  31. $candidates[] = $view->layout;
  32. }
  33. $candidates = array_merge($candidates, array(
  34. $this->params['controller'],
  35. $this->params['controller'] . '/' . $this->params['action']
  36. ));
  37. foreach($candidates as $candidate) {
  38. if (is_readable(CSS . DS . str_replace('/', DS, $candidate) . '.css')) {
  39. self::$__stylesheets[] = $candidate;
  40. }
  41. }
  42. foreach($candidates as $candidate) {
  43. if (is_readable(JS . DS . str_replace('/', DS, $candidate) . '.js')) {
  44. self::$__javascripts[] = $candidate . '.js';
  45. }
  46. }
  47. }
  48. }
  49. /**
  50. * Get tags for adding dependencies.
  51. *
  52. * @param string $type Type of dependency (css / javascript), or leave empty for all
  53. * @param View if specified, use View::addScript to include elements
  54. * @return string Added tags.
  55. */
  56. public function resources($type = null, $view = null) {
  57. $out = '';
  58. if (empty($type)) {
  59. $out .= $this->resources('css');
  60. $out .= $this->resources('javascript');
  61. return $out;
  62. }
  63. $type = strtolower($type);
  64. $resources = null;
  65. switch($type) {
  66. case 'css':
  67. $resources = array_filter(self::$__stylesheets);
  68. break;
  69. case 'js':
  70. case 'javascript':
  71. $resources = array_filter(self::$__javascripts);
  72. break;
  73. }
  74. if (!empty($resources)) {
  75. return $this->resource($resources, $type, $view);
  76. }
  77. return $out;
  78. }
  79. /**
  80. * Include specified set of javascripts to header.
  81. *
  82. * @return string Included javascripts
  83. */
  84. public function javascript() {
  85. $javascripts = func_get_args();
  86. $top = false;
  87. foreach($javascripts as $index => $javascript) {
  88. if (is_array($javascript)) {
  89. if (!empty($javascript)) {
  90. $javascripts = array_merge($javascripts, $javascript);
  91. }
  92. unset($javascripts[$index]);
  93. } elseif (is_bool($javascript)) {
  94. $top = $javascript;
  95. unset($javascripts[$index]);
  96. }
  97. }
  98. if ($top) {
  99. $javascripts = array_merge($javascripts, self::$__javascripts);
  100. } else {
  101. $javascripts = array_merge(self::$__javascripts, $javascripts);
  102. }
  103. self::$__javascripts = array_unique($javascripts);
  104. }
  105. /**
  106. * Include specified set of stylesheets to header.
  107. *
  108. * @return string Included stylesheets
  109. */
  110. public function css() {
  111. $stylesheets = func_get_args();
  112. $top = false;
  113. foreach($stylesheets as $index => $stylesheet) {
  114. if (is_array($stylesheet)) {
  115. if (!empty($stylesheet)) {
  116. $stylesheets = array_merge($stylesheets, $stylesheet);
  117. }
  118. unset($stylesheets[$index]);
  119. } elseif (is_bool($stylesheet)) {
  120. $top = $stylesheet;
  121. unset($stylesheets[$index]);
  122. }
  123. }
  124. if ($top) {
  125. $stylesheets = array_merge($stylesheets, self::$__stylesheets);
  126. } else {
  127. $stylesheets = array_merge(self::$__stylesheets, $stylesheets);
  128. }
  129. self::$__stylesheets = array_unique($stylesheets);
  130. }
  131. /**
  132. * Add resources (CSS / JS) to current layout, caching and embedding all of them
  133. * in one file.
  134. *
  135. * @param array $dependencies Set of dependencies (file locations)
  136. * @param string $type Type of dependency being added (css / javascript)
  137. * @param View if specified, use View::addScript to include elements
  138. * @param array $settings Settings to use
  139. * @return string Set of tags to add resources
  140. */
  141. public function resource($dependencies, $type = 'css', $view = null, $settings = array()) {
  142. if (strtolower($type) == 'javascript') {
  143. $type = 'js';
  144. }
  145. if (Configure::read('Cache.resources') !== null) {
  146. $settings = array_merge(Configure::read('Cache.resources'), $settings);
  147. }
  148. $defaults = array(
  149. 'cache' => false,
  150. 'force' => false,
  151. 'cachePath' => CACHE . 'views',
  152. 'cacheDir' => '/',
  153. 'cacheFileAppend' => '-203-20070814-1415.cache',
  154. 'minimize' => true
  155. );
  156. switch($type) {
  157. case 'css':
  158. $defaults['path'] = CSS;
  159. break;
  160. case 'js':
  161. $defaults['path'] = JS;
  162. break;
  163. }
  164. $settings = array_merge($defaults, $settings);
  165. // Not using cache and unifier
  166. if (!$settings['cache'] || !is_writable($settings['cachePath'])) {
  167. $out = '';
  168. foreach($dependencies as $dependency) {
  169. $tag = $this->__dependencyTag($type, $dependency);
  170. if (is_object($view)) {
  171. $view->addScript($tag);
  172. }
  173. $out .= $tag;
  174. }
  175. return $out;
  176. }
  177. // Calculate signature and name cache file
  178. $cacheName = Security::hash(serialize($dependencies)) . $settings['cacheFileAppend'] . '.' . $type;
  179. $cacheFile = $settings['cachePath'] . DS . $cacheName;
  180. // Look for cached version of signature
  181. if (!file_exists($cacheFile) || $settings['force']) {
  182. // Build full body
  183. $body = '';
  184. foreach($dependencies as $dependency) {
  185. $dependency = str_replace('/', DS, $dependency);
  186. $file = ($dependency[0] != DS ? str_replace('/', DS, $settings['path']) : substr(WWW_ROOT, 0, -1)) . $dependency;
  187. if (!preg_match('/\.' . $type . '$/i', $file)) {
  188. $file .= '.' . $type;
  189. }
  190. // Get the contents
  191. if (!file_exists($file)) {
  192. continue;
  193. }
  194. $contents = file_get_contents($file);
  195. // If we have relative paths on stylesheet, make sure we set them right
  196. if ($type == 'css') {
  197. // Calculate indirections from this dependency to root
  198. $relativeFile = str_replace(($dependency[0] != DS ? $settings['path'] : substr(WWW_ROOT, 0, -1)), '', $file);
  199. $indirections = 0;
  200. for ($i=0, $limiti=strlen($relativeFile); $i < $limiti; $i++) {
  201. $indirections += ($relativeFile[$i] == DS ? 1 : 0);
  202. }
  203. $relativePath = '';
  204. if ($indirections > 0) {
  205. $relativePath = str_replace(DS . basename($relativeFile), '', $relativeFile);
  206. }
  207. $rootIndirections = 0;
  208. for ($i=0, $limiti=strlen($settings['cacheDir']); $i < $limiti && $limiti > 1; $i++) {
  209. $rootIndirections += ($settings['cacheDir'] == '/' ? 1 : 0);
  210. }
  211. // Now look for relative paths
  212. if (preg_match_all('/url\(("|\')?([^\)]+)\)/i', $contents, $matches)) {
  213. $replaces = array();
  214. foreach($matches[0] as $index => $original) {
  215. $startQuote = $matches[1][$index];
  216. $endQuote = $startQuote;
  217. $currentPath = $matches[2][$index];
  218. if (!empty($startQuote) && $currentPath[strlen($currentPath) - 1] == $startQuote) {
  219. $currentPath = substr($currentPath, 0, -1);
  220. }
  221. // Look for non absolute path
  222. if (stripos($currentPath, 'http:') === false && stripos($currentPath, 'https:') === false) {
  223. $newPath = $currentPath;
  224. if ($currentPath[0] == '/') {
  225. // Refers to root, so just add indirection (from cache to root)
  226. $newPath = str_repeat('../', $rootIndirections) . substr($currentPath, 1);
  227. } else {
  228. // Refers to relative path, so calculate path from root
  229. $convertPath = str_replace(DS, '/', $relativePath . '/' . $currentPath);
  230. // Remove mutually exclusive relative URLs
  231. $convertPath = preg_replace('/[^\/]+\/\.\.\//', '', $convertPath);
  232. // Get real path from relative path
  233. $convertPath = explode('/', $convertPath);
  234. $elements = array();
  235. for ($i=0, $limiti=count($convertPath); $i < $limiti; $i++) {
  236. if ($convertPath[$i] == '' || $convertPath[$i] == '.') {
  237. continue;
  238. }
  239. if ($convertPath[$i] == '..' && $i > 0 && isset($elements[$limiti - 1]) && $elements[$limiti - 1] != '..') {
  240. array_pop($elements);
  241. continue;
  242. }
  243. array_push($elements, $convertPath[$i]);
  244. }
  245. $newPath = str_repeat('../', $rootIndirections) . implode('/', $elements);
  246. if ($dependency[0] == DS) {
  247. $newPath = '/' . $newPath;
  248. }
  249. }
  250. if ($newPath != $currentPath) {
  251. $replaces[] = array(
  252. 'original' => $original
  253. , 'replace' => 'url(' . $startQuote . $newPath . $endQuote . ')'
  254. );
  255. }
  256. }
  257. }
  258. if (!empty($replaces)) {
  259. $contents = str_replace(
  260. Set::extract($replaces, '{n}.original')
  261. , Set::extract($replaces, '{n}.replace')
  262. , $contents
  263. );
  264. }
  265. }
  266. }
  267. $body .= (!empty($body) ? "\n\n" : '') . $contents;
  268. }
  269. if ($type == 'js') {
  270. // Add a global variable to indicate which JS file is being used
  271. $body = '__PackagedJavaScriptFile = \'' . $settings['cacheDir'] . $cacheName . '\';' . "\n\n" . $body;
  272. if ($settings['minimize'] !== false) {
  273. // Normalize new lines
  274. $body = str_replace("\\\r\n", "\\n", $body);
  275. $body = str_replace("\\\n", "\\n", $body);
  276. $body = str_replace("\\\r", "\\n", $body);
  277. // Remove extra spaces
  278. $body = preg_replace("/\\n\s*\\n*/s", "\n", $body);
  279. }
  280. } elseif ($type == 'css') {
  281. // Make sure all import tags are at the beggining
  282. $imports = array();
  283. if (preg_match_all('/@import\s+url\(([^\)]+)\)\s*;?/i', $body, $matches)) {
  284. foreach($matches[0] as $match) {
  285. $imports[] = $match;
  286. }
  287. }
  288. if (!empty($imports)) {
  289. $body = implode("\n", $imports) . "\n\n" . str_replace($imports, '', $body);
  290. }
  291. }
  292. // Create cache file
  293. file_put_contents($cacheFile, $body);
  294. }
  295. // Strip out extension from cached file
  296. $cacheName = preg_replace('/\.' . $type . '$/i', '', $cacheName);
  297. // Add cached file as a view dependency
  298. $out = '';
  299. $tag = $this->__dependencyTag($type, ($settings['cacheDir'] != '/' ? $settings['cacheDir'] : '') . $cacheName);
  300. if (is_object($view)) {
  301. $view->addScript($tag);
  302. }
  303. $out .= $tag;
  304. return $out;
  305. }
  306. /**
  307. * Build dependency tag
  308. *
  309. * @param string $type Type of dependency (css / js)
  310. * @param string $dependency Dependency location
  311. * @return string Tag
  312. */
  313. private function __dependencyTag($type, $dependency) {
  314. $line = null;
  315. switch($type) {
  316. case 'css':
  317. $line = $this->Html->css($dependency);
  318. break;
  319. case 'js':
  320. case 'javascript':
  321. $line = $this->Javascript->link($dependency);
  322. break;
  323. }
  324. return $line;
  325. }
  326. }
  327. ?>