advagg.missing.inc

  1. cis7 sites/all/modules/ulmus/advagg/advagg.missing.inc
  2. cle7 sites/all/modules/ulmus/advagg/advagg.missing.inc
  3. ecd7 sites/all/modules/ulmus/advagg/advagg.missing.inc
  4. elmsmedia7 sites/all/modules/ulmus/advagg/advagg.missing.inc
  5. harmony7 sites/all/modules/ulmus/advagg/advagg.missing.inc
  6. icor7 sites/all/modules/ulmus/advagg/advagg.missing.inc
  7. meedjum_blog7 sites/all/modules/ulmus/advagg/advagg.missing.inc
  8. mooc7 sites/all/modules/ulmus/advagg/advagg.missing.inc

Advanced CSS/JS aggregation module.

Functions used to generate a file given the filename.

Functions

Namesort descending Description
advagg_get_atime Read the atime value for the given aggregate.
advagg_get_css_aggregate_contents Given a list of files, grab their contents and glue it into one big string.
advagg_get_files_from_hashes Get the files that belong inside of this aggregate.
advagg_get_hashes_from_filename Given a filename return the type and 2 hashes.
advagg_get_js_aggregate_contents Given a list of files, grab their contents and glue it into one big string.
advagg_htaccess_check_generate Generate .htaccess rules and place them in advagg dir.
advagg_missing_aggregate Menu Callback; generates a missing CSS/JS file.
advagg_missing_create_file Given a filename create that file.
advagg_missing_fast404 Send out a fast 404 and exit.
advagg_missing_generate Generates a missing CSS/JS file and send it to client.
advagg_missing_send_saved_file Send the css/js file to the client.
advagg_missing_set_farfuture_headers Set various headers so the browser will cache the file for a long time.
advagg_parse_media_blocks Split up as CSS string by @media queries.
advagg_save_aggregate Save an aggregate given a filename, the files included in it, and the type.
advagg_save_data Save data to a file.

File

sites/all/modules/ulmus/advagg/advagg.missing.inc
View source
  1. <?php
  2. /**
  3. * @file
  4. * Advanced CSS/JS aggregation module.
  5. *
  6. * Functions used to generate a file given the filename.
  7. */
  8. /**
  9. * Menu Callback; generates a missing CSS/JS file.
  10. */
  11. function advagg_missing_aggregate() {
  12. // Do not stop processing this request.
  13. ignore_user_abort(TRUE);
  14. // Generate missing file.
  15. $msg = advagg_missing_generate();
  16. // If here send out fast 404.
  17. advagg_missing_fast404($msg);
  18. }
  19. /**
  20. * Generates a missing CSS/JS file and send it to client.
  21. *
  22. * @return string
  23. * text if bundle couldn't be generated.
  24. */
  25. function advagg_missing_generate() {
  26. // Make sure we are not in a redirect loop.
  27. $redirect_counter = isset($_GET['redirect_counter']) ? intval($_GET['redirect_counter']) : 0;
  28. if ($redirect_counter > 5) {
  29. watchdog('advagg', 'This request could not generate correctly. Loop detected. Request data: %info', array('%info' => $_GET['q']));
  30. return t('In a loop.');
  31. }
  32. // Get filename from request.
  33. $arg = arg();
  34. $filename = array_pop($arg);
  35. $filename = explode('?', $filename);
  36. $filename = array_shift($filename);
  37. // Quit ASAP if filename does not match the AdvAgg pattern.
  38. $data = advagg_get_hashes_from_filename($filename);
  39. if (is_string($data) || !is_array($data)) {
  40. return $data;
  41. }
  42. // Get lock so only one process will do the work.
  43. $lock_name = 'advagg_' . $filename;
  44. $uri = $GLOBALS['base_path'] . $_GET['q'];
  45. $created = FALSE;
  46. $files_to_save = array();
  47. if (lock_acquire($lock_name, 10) || $redirect_counter > 4) {
  48. if ($redirect_counter > 4) {
  49. $return = advagg_missing_create_file($filename, TRUE, $data);
  50. }
  51. else {
  52. $return = advagg_missing_create_file($filename, FALSE, $data);
  53. }
  54. lock_release($lock_name);
  55. if (!is_array($return)) {
  56. return $return;
  57. }
  58. else {
  59. list($files_to_save, $type) = $return;
  60. $created = TRUE;
  61. }
  62. }
  63. else {
  64. // Wait for another request that is already doing this work.
  65. // We choose to block here since otherwise the router item may not
  66. // be available in menu_execute_active_handler() resulting in a 404.
  67. lock_wait($lock_name, 10);
  68. if (file_exists($uri) && filesize($uri) > 0) {
  69. $type = $data[0];
  70. $created = TRUE;
  71. }
  72. }
  73. // Redirect and try again on failure.
  74. if (empty($created)) {
  75. ++$redirect_counter;
  76. $uri .= '?redirect_counter=' . $redirect_counter;
  77. header('Location: ' . $uri, TRUE, 307);
  78. exit();
  79. }
  80. if ($redirect_counter > 4) {
  81. watchdog('advagg', 'One of the alter hooks failed when generating this file: %uri. Thus this file was created without any alter hooks.', array('%uri' => $uri), WATCHDOG_CRITICAL);
  82. }
  83. // Output file's contents if creating the file was successful.
  84. // This function will call exit.
  85. advagg_missing_send_saved_file($files_to_save, $uri, $created, $type, $redirect_counter);
  86. }
  87. /**
  88. * Send the css/js file to the client.
  89. *
  90. * @param array $files_to_save
  91. * Array of filenames and data.
  92. * @param string $uri
  93. * Requested filename.
  94. * @param bool $created
  95. * If file was created in a different thread.
  96. * @param string $type
  97. * css or js
  98. */
  99. function advagg_missing_send_saved_file($files_to_save, $uri, $created, $type, $redirect_counter) {
  100. // Negotiate whether to use gzip compression.
  101. $return_compressed = isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE;
  102. header('Vary: Accept-Encoding', FALSE);
  103. if (!empty($created)) {
  104. if ($return_compressed && file_exists($uri . '.gz') && filesize($uri . '.gz') > 0) {
  105. $uri .= '.gz';
  106. }
  107. if (!isset($files_to_save[$uri]) && file_exists($uri) && filesize($uri)) {
  108. $files_to_save[$uri] = file_get_contents($uri);
  109. }
  110. }
  111. // Make sure zlib.output_compression does not compress the output.
  112. ini_set('zlib.output_compression', '0');
  113. header('Vary: Accept-Encoding', FALSE);
  114. // Clear the output buffer.
  115. if (ob_get_level()) {
  116. ob_end_clean();
  117. }
  118. // Set generic far future headers.
  119. advagg_missing_set_farfuture_headers();
  120. // Return compressed content if we can.
  121. if ($return_compressed) {
  122. foreach ($files_to_save as $uri => $data) {
  123. // See if this uri contains .gz near the end of it.
  124. $pos = strripos($uri, '.gz', 91 + strlen(ADVAGG_SPACE) * 3);
  125. if (!empty($pos)) {
  126. $len = strlen($uri);
  127. if ($pos == $len - 3) {
  128. // .gz file exists, send it out.
  129. header('Content-Encoding: gzip');
  130. break;
  131. }
  132. }
  133. }
  134. }
  135. else {
  136. $data = trim(reset($files_to_save));
  137. }
  138. // Output file and exit.
  139. if (!empty($data)) {
  140. // Send out a 200 OK status.
  141. header($_SERVER['SERVER_PROTOCOL'] . " 200 OK");
  142. // Insure the Last-Modified header is set so 304's work correctly.
  143. // @ignore druplart_andor_assignment
  144. if (file_exists($uri) && $filemtime = @filemtime($uri)) {
  145. header('Last-Modified: ' . gmdate('D, d M Y H:i:s \G\M\T', $filemtime));
  146. }
  147. if ($type == 'css') {
  148. header("Content-Type: text/css");
  149. }
  150. elseif ($type == 'js') {
  151. header("Content-Type: application/javascript; charset=UTF-8");
  152. }
  153. header('X-AdvAgg: Generated file at ' . REQUEST_TIME);
  154. print $data;
  155. exit();
  156. }
  157. else {
  158. // Redirect and try again on failure.
  159. ++$redirect_counter;
  160. $uri .= '?redirect_counter=' . $redirect_counter;
  161. header('Location: ' . $uri, TRUE, 307);
  162. exit();
  163. }
  164. }
  165. /**
  166. * Set various headers so the browser will cache the file for a long time.
  167. */
  168. function advagg_missing_set_farfuture_headers() {
  169. // Hat tip to the CDN module for the far future headers.
  170. // @ignore sniffer_commenting_inlinecomment_spacingafter
  171. // Browsers that implement the W3C Access Control specification might refuse
  172. // to use certain resources such as fonts if those resources violate the
  173. // same-origin policy. Send a header to explicitly allow cross-domain use of
  174. // those resources. (This is called Cross-Origin Resource Sharing, or CORS.)
  175. header("Access-Control-Allow-Origin: *");
  176. // Remove all previously set Cache-Control headers, because we're going to
  177. // override it. Since multiple Cache-Control headers might have been set,
  178. // simply setting a new, overriding header isn't enough: that would only
  179. // override the *last* Cache-Control header. Yay for PHP!
  180. if (function_exists('header_remove')) {
  181. header_remove('Cache-Control');
  182. header_remove('ETag');
  183. header_remove('Set-Cookie');
  184. }
  185. else {
  186. header('Cache-Control:');
  187. header('Cache-Control:');
  188. header('ETag:');
  189. header('ETag:');
  190. header('Set-Cookie:');
  191. header('Set-Cookie:');
  192. }
  193. // Set a far future Cache-Control header (52 weeks), which prevents
  194. // intermediate caches from transforming the data and allows any
  195. // intermediate cache to cache it, since it's marked as a public resource.
  196. header('Cache-Control: max-age=31449600, no-transform, public');
  197. }
  198. /**
  199. * Given a filename create that file.
  200. *
  201. * @param string $filename
  202. * Just the filename no path information.
  203. * @param bool $no_alters
  204. * (optional) Set to TRUE to do the bare amount of processing on the file.
  205. * @param array $data
  206. * (optional) Output from advagg_get_hashes_from_filename().
  207. *
  208. * @return mixed
  209. * On failure a string saying why it failed.
  210. * On success the $files_to_save array.
  211. */
  212. function advagg_missing_create_file($filename, $no_alters = FALSE, $data = array()) {
  213. if (empty($data)) {
  214. $data = advagg_get_hashes_from_filename($filename);
  215. }
  216. if (is_array($data)) {
  217. list($type, $aggregate_filenames_hash, $aggregate_contents_hash, $aggregate_settings) = $data;
  218. }
  219. else {
  220. return $data;
  221. }
  222. // Set no alters if this is the last chance of generating the aggregate.
  223. if ($no_alters) {
  224. $aggregate_settings['settings']['no_alters'] = TRUE;
  225. }
  226. // Get a list of files.
  227. $files = advagg_get_files_from_hashes($type, $aggregate_filenames_hash, $aggregate_contents_hash);
  228. if (empty($files)) {
  229. return t('Hashes do not match database.');
  230. }
  231. // Save aggregate file.
  232. $files_to_save = advagg_save_aggregate($filename, $files, $type, $aggregate_settings);
  233. // Update atime.
  234. advagg_update_atime($aggregate_filenames_hash, $aggregate_contents_hash);
  235. // Make sure .htaccess file exists in the advagg dir.
  236. if (variable_get('advagg_htaccess_check_generate', ADVAGG_HTACCESS_CHECK_GENERATE)) {
  237. advagg_htaccess_check_generate($files_to_save, $type);
  238. }
  239. // Return data.
  240. return array(
  241. $files_to_save,
  242. $type,
  243. $aggregate_filenames_hash,
  244. $aggregate_contents_hash,
  245. $aggregate_settings,
  246. $files,
  247. );
  248. }
  249. /**
  250. * Generate .htaccess rules and place them in advagg dir.
  251. *
  252. * @param array $files_to_save
  253. * array of files that where saved.
  254. * @param string $type
  255. * css or js
  256. * @param array $force
  257. * (optional) force recreate the .htaccess file.
  258. */
  259. function advagg_htaccess_check_generate($files_to_save, $type, $force = FALSE) {
  260. $content_type = $type;
  261. if ($content_type == 'js') {
  262. $content_type = 'javascript';
  263. }
  264. // @ignore style_lowercase_html:45
  265. $data = "\n";
  266. $data .= "<FilesMatch \"^${type}" . ADVAGG_SPACE . "[A-Za-z0-9-_]{43}" . ADVAGG_SPACE . "[A-Za-z0-9-_]{43}" . ADVAGG_SPACE . "[A-Za-z0-9-_]{43}.${type}(\.gz)?\">\n";
  267. $data .= " # No mod_headers\n";
  268. $data .= " <IfModule !mod_headers.c>\n";
  269. $data .= " # No mod_expires\n";
  270. $data .= " <IfModule !mod_expires.c>\n";
  271. $data .= " # Use ETags.\n";
  272. $data .= " FileETag MTime Size\n";
  273. $data .= " </IfModule>\n";
  274. $data .= " </IfModule>\n";
  275. $data .= "\n";
  276. $data .= " # Use Expires Directive.\n";
  277. $data .= " <IfModule mod_expires.c>\n";
  278. $data .= " # Do not use ETags.\n";
  279. $data .= " FileETag None\n";
  280. $data .= " # Enable expirations.\n";
  281. $data .= " ExpiresActive On\n";
  282. $data .= " # Cache all aggregated ${type} files for 52 weeks after access (A).\n";
  283. $data .= " ExpiresDefault A31449600\n";
  284. $data .= " </IfModule>\n";
  285. $data .= "\n";
  286. $data .= " <IfModule mod_headers.c>\n";
  287. $data .= " # Do not use etags for cache validation.\n";
  288. $data .= " Header unset ETag\n";
  289. $data .= " # Serve correct content type.\n";
  290. if ($type == 'css') {
  291. $data .= " Header set Content-Type text/${content_type}\n";
  292. }
  293. else {
  294. $data .= " Header set Content-Type application/${content_type}\n";
  295. }
  296. $data .= " <IfModule !mod_expires.c>\n";
  297. $data .= " # Set a far future Cache-Control header to 52 weeks.\n";
  298. $data .= " Header set Cache-Control \"max-age=31449600, no-transform, public\"\n";
  299. $data .= " </IfModule>\n";
  300. $data .= " <IfModule mod_expires.c>\n";
  301. $data .= " Header append Cache-Control \"no-transform, public\"\n";
  302. $data .= " </IfModule>\n";
  303. $data .= " </IfModule>\n";
  304. if ($type == 'css') {
  305. $data .= " ForceType text/${content_type}\n";
  306. }
  307. else {
  308. $data .= " ForceType application/${content_type}\n";
  309. }
  310. $data .= "</FilesMatch>\n";
  311. foreach (array_keys($files_to_save) as $uri) {
  312. $dir = dirname($uri);
  313. $htaccess_file = $dir . '/.htaccess';
  314. if (!$force && file_exists($htaccess_file)) {
  315. continue;
  316. }
  317. advagg_save_data($htaccess_file, $data, $force);
  318. }
  319. }
  320. // Lookup functions.
  321. /**
  322. * Given a filename return the type and 2 hashes.
  323. *
  324. * @param string $filename
  325. * Just the filename no path information.
  326. *
  327. * @return mixed
  328. * On failure a string saying why it failed.
  329. * On success array($ext, $aggregate_hash, $files_hash).
  330. */
  331. function advagg_get_hashes_from_filename($filename) {
  332. // Verify requested filename has the correct pattern.
  333. if (!preg_match('/^(j|cs)s' . ADVAGG_SPACE . '[A-Za-z0-9-_]{43}' . ADVAGG_SPACE . '[A-Za-z0-9-_]{43}' . ADVAGG_SPACE . '[A-Za-z0-9-_]{43}\.(j|cs)s$/', $filename)) {
  334. return t('Wrong pattern.');
  335. }
  336. // Get the extension.
  337. $ext = substr($filename, strpos($filename, '.', 131 + strlen(ADVAGG_SPACE) * 3) + 1);
  338. // Set extraction points.
  339. if ($ext == 'css') {
  340. $aggregate_filenames_start = 3 + strlen(ADVAGG_SPACE);
  341. $aggregate_contents_start = 46 + strlen(ADVAGG_SPACE) * 2;
  342. $hooks_hashes_start = 89 + strlen(ADVAGG_SPACE) * 3;
  343. }
  344. elseif ($ext == 'js') {
  345. $aggregate_filenames_start = 2 + strlen(ADVAGG_SPACE);
  346. $aggregate_contents_start = 45 + strlen(ADVAGG_SPACE) * 2;
  347. $hooks_hashes_start = 88 + strlen(ADVAGG_SPACE) * 3;
  348. }
  349. else {
  350. return t('Wrong file type.');
  351. }
  352. // Extract info from wanted filename.
  353. $aggregate_filenames_hash = substr($filename, $aggregate_filenames_start, 43);
  354. $aggregate_contents_hash = substr($filename, $aggregate_contents_start, 43);
  355. $hooks_hashes_value = substr($filename, $hooks_hashes_start, 43);
  356. // Verify that the hooks hashes is valid.
  357. $aggregate_settings = advagg_get_hash_settings($hooks_hashes_value);
  358. if (empty($aggregate_settings)) {
  359. return t('Bad hooks hashes value.');
  360. }
  361. return array(
  362. $ext,
  363. $aggregate_filenames_hash,
  364. $aggregate_contents_hash,
  365. $aggregate_settings,
  366. );
  367. }
  368. /**
  369. * Get the files that belong inside of this aggregate.
  370. *
  371. * @param string $type
  372. * css or js.
  373. * @param string $aggregate_filenames_hash
  374. * Hash of the groupings of files.
  375. * @param string $aggregate_contents_hash
  376. * Hash of the files contents.
  377. *
  378. * @return array
  379. * List of files in the order they should be included.
  380. */
  381. function advagg_get_files_from_hashes($type, $aggregate_filenames_hash, $aggregate_contents_hash) {
  382. // Create join query for the advagg_files table.
  383. $subquery_files = db_select('advagg_files', 'af')
  384. ->fields('af')
  385. ->condition('af.filetype', $type);
  386. // Create join query for the advagg_aggregates table.
  387. $subquery_aggregates = db_select('advagg_aggregates', 'aa')
  388. ->condition('aa.aggregate_filenames_hash', $aggregate_filenames_hash)
  389. ->fields('aa');
  390. // Create main query for the advagg_aggregates_versions table.
  391. $query = db_select('advagg_aggregates_versions', 'aav')
  392. ->condition('aav.aggregate_filenames_hash', $aggregate_filenames_hash)
  393. ->condition('aav.aggregate_contents_hash', $aggregate_contents_hash);
  394. $query->join($subquery_aggregates, 'aa', 'aa.aggregate_filenames_hash=aav.aggregate_filenames_hash');
  395. $query->join($subquery_files, 'af', 'af.filename_hash=aa.filename_hash');
  396. $query->comment('Query called from ' . __FUNCTION__ . '()');
  397. $query = $query->fields('af', array('filename'))
  398. ->fields('aa', array('settings'))
  399. ->orderBy('porder', 'ASC')
  400. ->execute();
  401. // Add in files that are included in this aggregate.
  402. $files = array();
  403. foreach ($query as $value) {
  404. $files[$value->filename] = unserialize($value->settings);
  405. }
  406. return $files;
  407. }
  408. // Read CSS/JS files.
  409. /**
  410. * Given a list of files, grab their contents and glue it into one big string.
  411. *
  412. * @param array $files
  413. * array of filenames.
  414. * @param array $aggregate_settings
  415. * array of settings.
  416. *
  417. * @return string
  418. * string containing all the files.
  419. */
  420. function advagg_get_css_aggregate_contents($files, $aggregate_settings) {
  421. // Check if CSS compression is enabled.
  422. $optimize = TRUE;
  423. if (!empty($aggregate_settings['settings']['no_alters'])) {
  424. $optimize = FALSE;
  425. }
  426. $data = '';
  427. if (!empty($files)) {
  428. $media_changes = FALSE;
  429. $last_media = NULL;
  430. foreach ($files as $file => $settings) {
  431. if (!isset($settings['media'])) {
  432. continue;
  433. }
  434. if (is_null($last_media)) {
  435. $last_media = $settings['media'];
  436. continue;
  437. }
  438. if ($settings['media'] != $last_media) {
  439. $media_changes = TRUE;
  440. break;
  441. }
  442. }
  443. $last_media = NULL;
  444. $import_statements = array();
  445. module_load_include('inc', 'advagg', 'advagg');
  446. foreach ($files as $file => $settings) {
  447. $contents = advagg_load_css_stylesheet($file, $optimize, $aggregate_settings);
  448. // Allow other modules to modify this files contents.
  449. // Call hook_advagg_get_css_file_contents_alter().
  450. if (empty($aggregate_settings['settings']['no_alters'])) {
  451. drupal_alter('advagg_get_css_file_contents', $contents, $file, $aggregate_settings);
  452. }
  453. if ($media_changes) {
  454. $media_blocks = advagg_parse_media_blocks($contents);
  455. $contents = '';
  456. foreach ($media_blocks as $css_rules) {
  457. if (strpos($css_rules, '@media') !== FALSE) {
  458. // Get start and end of the rules for this media query block.
  459. $start = strpos($css_rules, '{');
  460. if ($start === FALSE) {
  461. continue;
  462. }
  463. $end = strrpos($css_rules, '}');
  464. if ($end === FALSE) {
  465. continue;
  466. }
  467. // Get current media queries for this media block.
  468. $media_rules = substr($css_rules, 6, $start - 6);
  469. // Get everything else besides top level media query.
  470. $css_selectors_rules = substr($css_rules, $start + 1, $end - ($start + 1));
  471. // Add in main media rule if needed.
  472. if (strpos($media_rules, $settings['media']) === FALSE) {
  473. $media_rules = $settings['media'] . ' ' . $media_rules;
  474. }
  475. }
  476. else {
  477. $media_rules = $settings['media'];
  478. $css_selectors_rules = $css_rules;
  479. }
  480. // Remove the all rule.
  481. $media_rules = str_replace('all', '', $media_rules);
  482. $media_rules = trim($media_rules);
  483. $css_selectors_rules = trim($css_selectors_rules);
  484. // Start of stylesheet.
  485. if (is_null($last_media)) {
  486. if (!empty($media_rules)) {
  487. $output = '@media ' . $media_rules . ' {' . $css_selectors_rules;
  488. }
  489. else {
  490. $output = $css_selectors_rules;
  491. }
  492. }
  493. elseif ($media_rules != $last_media) {
  494. if (!empty($media_rules)) {
  495. if (!empty($last_media)) {
  496. $output = "} \n@media " . $media_rules . ' {' . $css_selectors_rules;
  497. }
  498. else {
  499. $output = "\n@media " . $media_rules . ' {' . $css_selectors_rules;
  500. }
  501. }
  502. else {
  503. $output = "} \n " . $css_selectors_rules;
  504. }
  505. }
  506. else {
  507. $output = ' ' . $css_selectors_rules;
  508. }
  509. $last_media = $media_rules;
  510. $contents .= $output;
  511. }
  512. }
  513. // Per the W3C specification at
  514. // http://www.w3.org/TR/REC-CSS2/cascade.html#at-import, @import rules
  515. // must proceed any other style, so we move those to the top.
  516. $regexp = '/@import[^;]+;/i';
  517. preg_match_all($regexp, $contents, $matches);
  518. $contents = preg_replace($regexp, '', $contents);
  519. $import_statements[] = array($last_media, $matches[0]);
  520. // Close any open comment blocks.
  521. $contents .= '/**/';
  522. $data .= $contents;
  523. }
  524. // Close the final media bracket.
  525. if ($media_changes && !empty($last_media)) {
  526. $data .= '}';
  527. }
  528. // Add import statements to the top of the stylesheet.
  529. $import_string = '';
  530. foreach ($import_statements as $values) {
  531. if ($media_changes) {
  532. foreach ($values[1] as $statement) {
  533. $import_string .= str_replace(';', $values[0] . ';', $statement);
  534. }
  535. }
  536. else {
  537. $import_string .= implode('', $values[1]);
  538. }
  539. }
  540. $data = $import_string . $data;
  541. }
  542. // Allow other modules to modify this aggregates contents.
  543. // Call hook_advagg_get_css_aggregate_contents_alter().
  544. if (empty($aggregate_settings['settings']['no_alters'])) {
  545. drupal_alter('advagg_get_css_aggregate_contents', $data, $files, $aggregate_settings);
  546. }
  547. return $data;
  548. }
  549. /**
  550. * Given a list of files, grab their contents and glue it into one big string.
  551. *
  552. * @param array $files
  553. * array of filenames.
  554. * @param array $aggregate_settings
  555. * array of settings.
  556. *
  557. * @return string
  558. * string containing all the files.
  559. */
  560. function advagg_get_js_aggregate_contents($files, $aggregate_settings) {
  561. $data = '';
  562. if (!empty($files)) {
  563. // Build aggregate JS file.
  564. foreach ($files as $filename => $settings) {
  565. $contents = '';
  566. // Append a ';' and a newline after each JS file to prevent them from
  567. // running together. Also close any comment blocks.
  568. if (file_exists($filename)) {
  569. $contents .= file_get_contents($filename) . ";/**/\n";
  570. }
  571. // Allow other modules to modify this files contents.
  572. // Call hook_advagg_get_js_file_contents_alter().
  573. if (empty($aggregate_settings['settings']['no_alters'])) {
  574. drupal_alter('advagg_get_js_file_contents', $contents, $filename, $aggregate_settings);
  575. }
  576. $data .= $contents;
  577. }
  578. }
  579. // Allow other modules to modify this aggregates contents.
  580. // Call hook_advagg_get_js_aggregate_contents_alter().
  581. if (empty($aggregate_settings['settings']['no_alters'])) {
  582. drupal_alter('advagg_get_js_aggregate_contents', $data, $files, $aggregate_settings);
  583. }
  584. return $data;
  585. }
  586. // File save functions.
  587. /**
  588. * Save an aggregate given a filename, the files included in it, and the type.
  589. *
  590. * @param string $filename
  591. * Just the filename no path information.
  592. * @param array $files
  593. * array of filenames.
  594. * @param string $type
  595. * css or js
  596. * @param array $aggregate_settings
  597. * array of settings.
  598. *
  599. * @return array
  600. * $files_to_save array.
  601. */
  602. function advagg_save_aggregate($filename, $files, $type, $aggregate_settings) {
  603. list($css_path, $js_path) = advagg_get_root_files_dir();
  604. // Build the aggregates contents.
  605. $contents = '';
  606. if ($type == 'css') {
  607. $contents = advagg_get_css_aggregate_contents($files, $aggregate_settings);
  608. $uri = $css_path[0] . '/' . $filename;
  609. }
  610. elseif ($type == 'js') {
  611. $contents = advagg_get_js_aggregate_contents($files, $aggregate_settings);
  612. $uri = $js_path[0] . '/' . $filename;
  613. }
  614. // List of files to save.
  615. $files_to_save = array(
  616. $uri => $contents,
  617. );
  618. // Allow other modules to alter the contents and add new files to save (.gz).
  619. // Call hook_advagg_save_aggregate_alter().
  620. $other_parameters = array($files, $type);
  621. if (empty($aggregate_settings['settings']['no_alters'])) {
  622. drupal_alter('advagg_save_aggregate', $files_to_save, $aggregate_settings, $other_parameters);
  623. }
  624. foreach ($files_to_save as $uri => $data) {
  625. advagg_save_data($uri, $data);
  626. if (!file_exists($uri) || filesize($uri) == 0) {
  627. if ($type == 'css') {
  628. $full_dir = DRUPAL_ROOT . '/' . $css_path[1];
  629. }
  630. elseif ($type == 'js') {
  631. $full_dir = DRUPAL_ROOT . '/' . $js_path[1];
  632. }
  633. $free_space = @disk_free_space($full_dir);
  634. if ($free_space !== FALSE && strlen($data) > $free_space) {
  635. watchdog('advagg', 'Write to file system failed. Disk is full. %uri', array('%uri' => $uri), WATCHDOG_ALERT);
  636. }
  637. elseif (!is_writable($full_dir)) {
  638. watchdog('advagg', 'Write to file system failed. Check directory permissions. %uri', array('%uri' => $uri), WATCHDOG_ERROR);
  639. }
  640. else {
  641. watchdog('advagg', 'Write to file system failed. %uri', array('%uri' => $uri), WATCHDOG_ERROR);
  642. }
  643. // If the file is empty, remove it. Serving via drupal is better than an
  644. // empty aggregate being served.
  645. if (file_exists($uri) && filesize($uri) == 0) {
  646. @unlink($uri);
  647. }
  648. }
  649. }
  650. return $files_to_save;
  651. }
  652. /**
  653. * Save data to a file.
  654. *
  655. * This will use the rename operation ensuring atomic file operations.
  656. *
  657. * @param string $uri
  658. * A string containing the destination location. This must be a stream wrapper
  659. * URI.
  660. * @param string $data
  661. * A string containing the contents of the file.
  662. * @param bool $overwrite
  663. * (optional) Bool, set to TRUE to overwrite a file.
  664. */
  665. function advagg_save_data($uri, $data, $overwrite = FALSE) {
  666. // File already exists.
  667. if (!$overwrite && file_exists($uri) && filesize($uri) > 0) {
  668. return;
  669. }
  670. // If data is empty, write a space.
  671. if (empty($data)) {
  672. $data = ' ';
  673. }
  674. // Perform the replace operation. Since there could be multiple processes
  675. // writing to the same file, the best option is to create a temporary file in
  676. // the same directory and then rename it to the destination. A temporary file
  677. // is needed if the directory is mounted on a separate machine; thus ensuring
  678. // the rename command stays local.
  679. // @ignore sniffer_commenting_inlinecomment_spacingafter
  680. // Get a temporary filename in the destination directory.
  681. $dir = drupal_dirname($uri) . '/';
  682. $temporary_file = drupal_tempnam($dir, 'file_advagg_');
  683. $temporary_file_copy = $temporary_file;
  684. // Get the extension of the original filename and append it to the temp file
  685. // name. Preserves the mime type in different stream wrapper implementations.
  686. $parts = pathinfo($uri);
  687. $extension = '.' . $parts['extension'];
  688. if ($extension == '.gz') {
  689. $parts = pathinfo($parts['filename']);
  690. $extension = '.' . $parts['extension'] . $extension;
  691. }
  692. // Also move temp file into the advagg dir.
  693. $temporary_file = str_replace(substr($temporary_file, 0, strpos($temporary_file, 'file_advagg_')), $dir, $temporary_file) . $extension;
  694. // Remove if rename failed.
  695. if (!rename($temporary_file_copy, $temporary_file)) {
  696. @unlink($temporary_file_copy);
  697. }
  698. // Save to temporary filename in the destination directory.
  699. $filepath = file_unmanaged_save_data($data, $temporary_file, FILE_EXISTS_REPLACE);
  700. // Perform the rename operation.
  701. $result = @rename($temporary_file, $uri);
  702. if (!$result) {
  703. // Unlink and try again for windows. Rename on windows does not replace
  704. // the file if it already exists.
  705. @unlink($uri);
  706. $result = @rename($temporary_file, $uri);
  707. // Remove temporary_file if rename failed.
  708. if (!$result) {
  709. @unlink($temporary_file);
  710. }
  711. }
  712. }
  713. // Helper functions.
  714. /**
  715. * Send out a fast 404 and exit.
  716. *
  717. * @param string $msg
  718. * (optional) Small message reporting why the file didn't get created.
  719. */
  720. function advagg_missing_fast404($msg = '') {
  721. // Strip new lines and limit header message to 512 characters.
  722. $msg = substr(str_replace(array("\n", "\r"), '', $msg), 0, 512);
  723. // Add in headers if possible.
  724. if (!headers_sent()) {
  725. header($_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found');
  726. header('X-AdvAgg: Failed validation. ' . $msg);
  727. }
  728. // Output fast 404 message and exit.
  729. print '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">' . "\n";
  730. print '<html xmlns="http://www.w3.org/1999/xhtml">';
  731. print '<head><title>404 Not Found</title><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /></head>';
  732. print '<body><h1>Not Found</h1>';
  733. print '<p>The requested URL was not found on this server.</p>';
  734. print '<p><a href="' . $GLOBALS['base_path'] . '">Home</a></p>';
  735. print '<!-- advagg_missing_fast404 -->';
  736. print '</body></html>';
  737. exit();
  738. }
  739. /**
  740. * Read the atime value for the given aggregate.
  741. *
  742. * @param string $aggregate_filenames_hash
  743. * Hash of the groupings of files.
  744. * @param string $aggregate_contents_hash
  745. * Hash of the files contents.
  746. * @param string $uri
  747. * URI pointing to the aggregate file.
  748. *
  749. * @return mixed
  750. * File atime or FALSE if not found.
  751. */
  752. function advagg_get_atime($aggregate_filenames_hash, $aggregate_contents_hash, $uri) {
  753. // Try to use the cache to avoid hitting the database with a select query.
  754. $cache_id = 'advagg:db:' . $aggregate_filenames_hash . ADVAGG_SPACE . $aggregate_contents_hash;
  755. $cache = cache_get($cache_id, 'cache_advagg_info');
  756. if ($cache) {
  757. // If the atime in the cache is less than 12 hours old, use that.
  758. if (!empty($cache->data['atime']) && $cache->data['atime'] > REQUEST_TIME - (12 * 60 * 60)) {
  759. return $cache->data['atime'];
  760. }
  761. }
  762. // Try to get the atime from the DB.
  763. $atime = db_select('advagg_aggregates_versions', 'aav')
  764. ->fields('aav', array('atime'))
  765. ->condition('aav.aggregate_filenames_hash', $aggregate_filenames_hash)
  766. ->condition('aav.aggregate_contents_hash', $aggregate_contents_hash)
  767. ->execute()
  768. ->fetchField();
  769. if (!empty($atime)) {
  770. return $atime;
  771. }
  772. // Return the atime from disk as a last resort.
  773. if (file_exists($uri)) {
  774. return fileatime($uri);
  775. }
  776. // No atime was found, return FALSE.
  777. return FALSE;
  778. }
  779. /**
  780. * Split up as CSS string by @media queries.
  781. *
  782. * @see http://stackoverflow.com/questions/14145620/regular-expression-for-media-queries-in-css
  783. *
  784. * @param string $css
  785. * String of CSS.
  786. *
  787. * @return array
  788. * array of css with only media queries.
  789. */
  790. function advagg_parse_media_blocks($css) {
  791. $media_blocks = array();
  792. $start = 0;
  793. $last_start = 0;
  794. // Using the string as an array throughout this function.
  795. // http://php.net/types.string#language.types.string.substr
  796. while (($start = strpos($css, "@media", $start)) !== FALSE) {
  797. // Stack to manage brackets.
  798. $s = array();
  799. // Get the first opening bracket.
  800. $i = strpos($css, "{", $start);
  801. // If $i is false, then there is probably a css syntax error.
  802. if ($i === FALSE) {
  803. continue;
  804. }
  805. // Push bracket onto stack.
  806. array_push($s, $css[$i]);
  807. // Move past first bracket.
  808. ++$i;
  809. // Find the closing bracket for the @media statement.
  810. while (!empty($s)) {
  811. // If the character is an opening bracket, push it onto the stack,
  812. // otherwise pop the stack.
  813. if ($css[$i] == "{") {
  814. array_push($s, "{");
  815. }
  816. elseif ($css[$i] == "}") {
  817. array_pop($s);
  818. }
  819. ++$i;
  820. }
  821. // Get CSS before @media and store it.
  822. if ($last_start != $start) {
  823. $insert = trim(substr($css, $last_start, $start - $last_start));
  824. if (!empty($insert)) {
  825. $media_blocks[] = $insert;
  826. }
  827. }
  828. // Cut @media block out of the css and store.
  829. $media_blocks[] = trim(substr($css, $start, $i - $start));
  830. // Set the new $start to the end of the block.
  831. $start = $i;
  832. $last_start = $start;
  833. }
  834. // Add in any remaining css rules after the last @media statement.
  835. if (strlen($css) > $last_start) {
  836. $insert = trim(substr($css, $last_start));
  837. if (!empty($insert)) {
  838. $media_blocks[] = $insert;
  839. }
  840. }
  841. return $media_blocks;
  842. }
Error | ELMSLN API

Error

×

Error message

  • Warning: Cannot modify header information - headers already sent by (output started at /var/www/html/elmsln_community/api.elmsln.org/includes/common.inc:2791) in drupal_send_headers() (line 1499 of /var/www/html/elmsln_community/api.elmsln.org/includes/bootstrap.inc).
  • Error: Call to undefined function apc_delete() in DrupalAPCCache->clear() (line 289 of /var/www/html/elmsln_community/api.elmsln.org/sites/all/modules/apc/drupal_apc_cache.inc).
The website encountered an unexpected error. Please try again later.