all-in-one-event-calendar/lib/theme/compiler.php
2019-07-25 14:11:00 +02:00

225 lines
6.9 KiB
PHP

<?php
/**
* Loads files for admin and frontend.
*
* @author Time.ly Network Inc.
* @since 2.0
*
* @package AI1EC
* @subpackage AI1EC.Theme
*/
class Ai1ec_Theme_Compiler extends Ai1ec_Base {
/**
* Register filters early on.
*
* @param Ai1ec_Registry_Object $registry Instance to use.
*
* @return void
*/
public function __construct( Ai1ec_Registry_Object $registry ) {
parent::__construct( $registry );
add_filter(
'ai1ec_twig_environment',
array( $this, 'ai1ec_twig_environment' )
);
add_filter(
'ai1ec_twig_add_debug',
'__return_false'
);
}
/**
* Perform actual templates (re-)compilation.
*
* @return void
*/
public function generate() {
$loader = $this->_registry->get( 'theme.loader' );
header( 'Content-Type: text/plain; charset=utf-8' );
$start = microtime( true );
if ( ! $this->clean_and_check_dir( AI1EC_TWIG_CACHE_PATH ) ) {
throw new Ai1ec_Bootstrap_Exception(
'Failed to create cache directory: ' . AI1EC_TWIG_CACHE_PATH
);
}
foreach ( array( true, false ) as $for_admin ) {
$twig = $loader->get_twig_instance( $for_admin, true );
$files = $this->get_files( $twig );
$this->compile( $twig, $files );
}
echo 'Re-compiled in ' . ( microtime( true ) - $start ) . "\n";
exit( 0 );
}
/**
* Extract files locatable within provided Twig Environment.
*
* @param Twig_Environment $twig Instance to check.
*
* @return array Map of files => Twig templates.
*/
public function get_files( Twig_Environment $twig ) {
$files = array();
try {
$paths = $twig->getLoader()->getPaths();
foreach ( $paths as $path ) {
$files += $this->read_files( $path, strlen( $path ) + 1 );
}
} catch ( Exception $excpt ) { }
return $files;
}
/**
* Actually compile templates to cache directory.
*
* @param Twig_Environment $twig Instance to use for compilation.
* @param array $file_list Map of files located previously.
*
* @return void
*/
public function compile( Twig_Environment $twig, array $file_list ) {
foreach ( $file_list as $file => $template ) {
$twig->loadTemplate( $template );
echo 'Compiled: ', $template, ' (', $file, ')', "\n";
}
}
/**
* Read file system searching for twig files.
*
* @param string $path Directory to search in.
* @param int $trim_length Number of characters to omit for templates.
*
* @return array Map of files => Twig templates.
*/
public function read_files( $path, $trim_length ) {
$handle = opendir( $path );
$files = array();
if ( false === $handle ) {
return $files;
}
while ( false !== ( $file = readdir( $handle ) ) ) {
if ( '.' === $file{0} ) {
continue;
}
$new_path = $path . DIRECTORY_SEPARATOR . $file;
if ( is_dir( $new_path ) ) {
$files += $this->read_files( $new_path, $trim_length );
} else if (
is_file( $new_path ) &&
'.twig' === strrchr( $new_path, '.' )
) {
$files[$new_path] = substr( $new_path, $trim_length );
}
}
closedir( $handle );
return $files;
}
/**
* Adjust Twig environment for compilation.
*
* @param array $environment Initial environment arguments.
*
* @return
*/
public function ai1ec_twig_environment( array $environment ) {
$environment['debug'] = false;
$environment['cache'] = AI1EC_TWIG_CACHE_PATH;
$environment['auto_reload'] = true;
return $environment;
}
/**
* Ensure cache directory pre-conditions.
*
* Before compilation starts cache directory must be empty but existing.
* NOTE: it attempts to preserve `.gitignore` file in cache/ directory.
*
* @param string $cache_dir Directory to check.
*
* @return bool Validity.
*/
public function clean_and_check_dir( $cache_dir ) {
try {
// PROD-3918 - Check if we are cleaning the TWIG cache dir
// This validation will fail if a custom directory is used to store twig cache
if ( empty( $cache_dir ) || strpos( $cache_dir, 'cache/twig' ) === false ) {
return false;
}
$parent = realpath( $cache_dir );
// $parent will return empty if the directory doesn't exist
// Only clean directory if directory exists
if ( ! empty( $parent ) && ! $this->_prune_dir( $parent ) ) {
return false;
}
if (
is_dir( $cache_dir ) && chmod( $cache_dir, 0754 )
|| mkdir( $cache_dir, 0754, true )
) {
return true;
}
return false;
} catch ( Exception $exc ) {
return false;
}
}
/**
* Remove directory and all it's contents.
*
* @param string $cache_dir Absolute path to remove.
*
* @return bool Success.
*/
protected function _prune_dir( $cache_dir ) {
if ( ! file_exists( $cache_dir ) ) {
return true;
}
$handle = opendir( $cache_dir );
if ( ! $handle ) {
return false;
}
while ( false !== ( $file = readdir( $handle ) ) ) {
if ( '.' === $file{0} ) {
continue;
}
$basename = basename( $file, '.php' );
// continue deleting only if:
// - it's 60 characters length (filename w/o '.php')
// - OR it's 2 characters length (directory)
// - AND (with two above) it's hex encoded string
if (
! (
( isset( $basename{59} ) && ! isset( $basename{60} ) ) ||
( isset( $basename{1} ) && ! isset( $basename{2} ) ) &&
ctype_xdigit( $basename )
)
) {
continue;
}
$path = $cache_dir . DIRECTORY_SEPARATOR . $file;
if ( is_file( $path ) ) {
if ( ! unlink( $path ) ) {
return false;
}
} else {
if ( ! $this->_prune_dir( $path ) ) {
return false;
}
}
}
closedir( $handle );
if ( is_file( $cache_dir . DIRECTORY_SEPARATOR . 'EMPTY' ) ) {
return true; // ignore, this directory is intentionally here
}
return rmdir( $cache_dir );
}
}