/** * Produire un fichier statique a partir d'un squelette dynamique * Permet ensuite a apache de le servir en statique sans repasser * par spip.php a chaque hit sur le fichier * si le format (css ou js) est passe dans contexte['format'], on l'utilise * sinon on regarde si le fond finit par .css ou .js * sinon on utilie "html" * * @param string $fond * @param array $contexte * @param array $options * @param string $connect * @return string */ function produire_fond_statique($fond, $contexte = array(), $options = array(), $connect = '') { if (isset($contexte['format'])) { $extension = $contexte['format']; unset($contexte['format']); } else { $extension = "html"; if (preg_match(',[.](css|js|json)$,', $fond, $m)) { $extension = $m[1]; } } // recuperer le contenu produit par le squelette $options['raw'] = true; $cache = recuperer_fond($fond, $contexte, $options, $connect); // calculer le nom de la css $dir_var = sous_repertoire(_DIR_VAR, 'cache-' . $extension); $nom_safe = preg_replace(",\\W,", '_', str_replace('.', '_', $fond)); $filename = $dir_var . $extension . "dyn-{$nom_safe}-" . substr(md5($fond . serialize($contexte) . $connect), 0, 8) . ".{$extension}"; // mettre a jour le fichier si il n'existe pas // ou trop ancien // le dernier fichier produit est toujours suffixe par .last // et recopie sur le fichier cible uniquement si il change if (!file_exists($filename) or !file_exists($filename . ".last") or isset($cache['lastmodified']) and $cache['lastmodified'] and filemtime($filename . ".last") < $cache['lastmodified'] or defined('_VAR_MODE') and _VAR_MODE == 'recalcul') { $contenu = $cache['texte']; // passer les urls en absolu si c'est une css if ($extension == "css") { $contenu = urls_absolues_css($contenu, test_espace_prive() ? generer_url_ecrire('accueil') : generer_url_public($fond)); } // ne pas insérer de commentaire si c'est du json if ($extension != "json") { $comment = "/* #PRODUIRE{fond={$fond}"; foreach ($contexte as $k => $v) { $comment .= ",{$k}={$v}"; } // pas de date dans le commentaire car sinon ca invalide le md5 et force la maj // mais on peut mettre un md5 du contenu, ce qui donne un aperu rapide si la feuille a change ou non $comment .= "}\n md5:" . md5($contenu) . " */\n"; } // et ecrire le fichier ecrire_fichier($filename . ".last", $comment . $contenu); // regarder si on recopie if (!file_exists($filename) or md5_file($filename) !== md5_file($filename . ".last")) { @copy($filename . ".last", $filename); spip_clearstatcache(true, $filename); // eviter que PHP ne reserve le vieux timestamp } } return $filename; }
/** * Invalidates a PHP file from any active opcode caches. * * If the opcode cache does not support the invalidation of individual files, * the entire cache will be flushed. * kudo : http://cgit.drupalcode.org/drupal/commit/?id=be97f50 * * @param string $filepath * The absolute path of the PHP file to invalidate. */ function spip_clear_opcode_cache($filepath) { spip_clearstatcache(true, $filepath); // Zend OPcache if (function_exists('opcache_invalidate')) { opcache_invalidate($filepath, true); } // APC. if (function_exists('apc_delete_file')) { // apc_delete_file() throws a PHP warning in case the specified file was // not compiled yet. // @see http://php.net/apc-delete-file @apc_delete_file($filepath); } }
/** * Concaténer en un seul une liste de fichier, * avec appels de callback sur chaque fichier, * puis sur le fichier final * * Gestion d'un cache : le fichier concaténé n'est produit que si il n'existe pas * pour la liste de fichiers fournis en entrée * * * @param array $files * Liste des fichiers à concatener, chaque entrée sour la forme html=>fichier * - string $key : html d'insertion du fichier dans la page * - string|array $fichier : chemin du fichier, ou tableau (page,argument) si c'est un squelette * @param string $format * js ou css utilisé pour l'extension du fichier de sortie * @param array $callbacks * Tableau de fonctions à appeler : * - each_pre : fonction de préparation à appeler sur le contenu de chaque fichier * - each_min : fonction de minification à appeler sur le contenu de chaque fichier * - all_min : fonction de minification à appeler sur le contenu concatene complet, en fin de traitement * @return array * Tableau a 2 entrées retournant le nom du fichier et des commentaires HTML à insérer dans la page initiale */ function concatener_fichiers($files, $format = 'js', $callbacks = array()) { $nom = ""; if (!is_array($files) && $files) { $files = array($files); } if (count($files)) { $callback_min = isset($callbacks['each_min']) ? $callbacks['each_min'] : 'concatener_callback_identite'; $callback_pre = isset($callbacks['each_pre']) ? $callbacks['each_pre'] : ''; $url_base = self('&'); // on trie la liste de files pour calculer le nom // necessaire pour retomber sur le meme fichier // si on renome une url a la volee pour enlever le var_mode=recalcul // mais attention, il faut garder l'ordre initial pour la minification elle meme ! $dir = sous_repertoire(_DIR_VAR, 'cache-' . $format); $nom = $dir . md5(serialize($files) . serialize($callbacks)) . ".{$format}"; if (defined('_VAR_MODE') and _VAR_MODE == 'recalcul' or !file_exists($nom)) { $fichier = ""; $comms = array(); $total = 0; $files2 = false; foreach ($files as $key => $file) { if (!is_array($file)) { // c'est un fichier $comm = $file; // enlever le timestamp si besoin $file = preg_replace(",[?].+\$,", '', $file); // preparer le fichier si necessaire if ($callback_pre) { $file = $callback_pre($file); } lire_fichier($file, $contenu); } else { // c'est un squelette if (!isset($file[1])) { $file[1] = ''; } $comm = _SPIP_PAGE . "={$file['0']}" . (strlen($file[1]) ? "({$file['1']})" : ''); parse_str($file[1], $contexte); $contenu = recuperer_fond($file[0], $contexte); // preparer le contenu si necessaire if ($callback_pre) { $contenu = $callback_pre($contenu, url_absolue(_DIR_RESTREINT ? generer_url_public($file[0], $file[1]) : $url_base)); } // enlever le var_mode si present pour retrouver la css minifiee standard if (strpos($file[1], 'var_mode') !== false) { if (!$files2) { $files2 = $files; } $old_key = $key; $key = preg_replace(',(&(amp;)?)?var_mode=[^&\'"]*,', '', $key); $file[1] = preg_replace(',&?var_mode=[^&\'"]*,', '', $file[1]); if (!strlen($file[1])) { unset($file[1]); } $files2 = array_replace_key($files2, $old_key, $key, $file); } } // passer la balise html initiale en second argument $fichier .= "/* {$comm} */\n" . $callback_min($contenu, $key) . "\n\n"; $comms[] = $comm; $total += strlen($contenu); } // calcul du % de compactage $pc = intval(1000 * strlen($fichier) / $total) / 10; $comms = "compact [\n\t" . join("\n\t", $comms) . "\n] {$pc}%"; $fichier = "/* {$comms} */\n\n" . $fichier; // si on a nettoye des &var_mode=recalcul : mettre a jour le nom // on ecrit pas dans le nom initial, qui est de toute facon recherche qu'en cas de recalcul // donc jamais utile if ($files2) { $files = $files2; $nom = $dir . md5(serialize($files) . serialize($callbacks)) . ".{$format}"; } $nom_tmp = $nom; $final_callback = isset($callbacks['all_min']) ? $callbacks['all_min'] : false; if ($final_callback) { unset($callbacks['all_min']); $nom_tmp = $dir . md5(serialize($files) . serialize($callbacks)) . ".{$format}"; } // ecrire ecrire_fichier($nom_tmp, $fichier, true); spip_clearstatcache(true, $nom_tmp); // ecrire une version .gz pour content-negociation par apache, cf. [11539] ecrire_fichier("{$nom_tmp}.gz", $fichier, true); if ($final_callback) { // closure compiler ou autre super-compresseurs // a appliquer sur le fichier final $encore = $final_callback($nom_tmp, $nom); // si echec, on se contente de la compression sans cette callback if ($encore !== $nom) { // ecrire ecrire_fichier($nom, $fichier, true); spip_clearstatcache(true, $nom); // ecrire une version .gz pour content-negociation par apache, cf. [11539] ecrire_fichier("{$nom}.gz", $fichier, true); } } } } // Le commentaire detaille n'apparait qu'au recalcul, pour debug return array($nom, (isset($comms) and $comms) ? "<!-- {$comms} -->\n" : ''); }
/** * Minification additionnelle de JS * * Compacter du javascript plus intensivement * grâce au google closure compiler * * @param string $content * Contenu JS à compresser * @param bool $file * Indique si $content est ou non un fichier, et retourne un fichier dans ce dernier cas * Si $file est une chaîne, c'est un nom de ficher sous lequel on écrit aussi le fichier destination * @return string * Contenu JS compressé */ function minifier_encore_js($content, $file = false) { # Closure Compiler n'accepte pas des POST plus gros que 200 000 octets # au-dela il faut stocker dans un fichier, et envoyer l'url du fichier # dans code_url ; en localhost ca ne marche evidemment pas if ($file) { $nom = $content; lire_fichier($nom, $content); $dest = dirname($nom) . '/' . md5($content . $file) . '.js'; if (file_exists($dest) and (!is_string($file) or file_exists($file))) { if (filesize($dest)) { return is_string($file) ? $file : $dest; } else { spip_log("minifier_encore_js: Fichier {$dest} vide", _LOG_INFO); return $nom; } } } if (!$file and strlen($content) > 200000) { return $content; } include_spip('inc/distant'); $datas = array('output_format' => 'text', 'output_info' => 'compiled_code', 'compilation_level' => 'SIMPLE_OPTIMIZATIONS'); if (!$file or strlen($content) < 200000) { $datas['js_code'] = $content; } else { $datas['url_code'] = url_absolue($nom); } $cc = recuperer_page('http://closure-compiler.appspot.com/compile', $trans = false, $get_headers = false, $taille_max = null, $datas, $boundary = -1); if ($cc and !preg_match(',^\\s*Error,', $cc)) { spip_log('Closure Compiler: success'); $cc = "/* {$nom} + Closure Compiler */\n" . $cc; if ($file) { ecrire_fichier($dest, $cc, true); ecrire_fichier("{$dest}.gz", $cc, true); $content = $dest; if (is_string($file)) { ecrire_fichier($file, $cc, true); spip_clearstatcache(true, $file); ecrire_fichier("{$file}.gz", $cc, true); $content = $file; } } else { $content =& $cc; } } else { if ($file) { spip_log("minifier_encore_js:Echec appel Closure Compiler. Ecriture fichier {$dest} vide", _LOG_INFO_IMPORTANTE); ecrire_fichier($dest, '', true); } } return $content; }