@@ -0,0 +1,225 @@
|
||||
<?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 );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
/**
|
||||
* Abstract class for a file.
|
||||
*
|
||||
* @author Time.ly Network Inc.
|
||||
* @since 2.0
|
||||
*
|
||||
* @package AI1EC
|
||||
* @subpackage AI1EC.Theme.File
|
||||
*/
|
||||
abstract class Ai1ec_File_Abstract extends Ai1ec_Base {
|
||||
|
||||
/**
|
||||
* @var array The paths where to look for the file.
|
||||
*/
|
||||
protected $_paths;
|
||||
|
||||
/**
|
||||
* @var string The name of the file.
|
||||
*/
|
||||
protected $_name;
|
||||
|
||||
/**
|
||||
* @var mixed The content of the file.
|
||||
* Usually it's a string but for some edge cases it might be a PHP type like an array
|
||||
* The only case now is user_variables.php for Less
|
||||
*/
|
||||
protected $_content;
|
||||
|
||||
/**
|
||||
* Locates the file and parses its content. Populates $this->_content.
|
||||
*
|
||||
* @return boolean Returns true if the file is found, false otheriwse.
|
||||
*/
|
||||
abstract public function process_file();
|
||||
|
||||
/**
|
||||
* Standard constructor for basic files.
|
||||
*
|
||||
* @param Ai1ec_Registry_Object $registry
|
||||
* @param string $name
|
||||
* @param array $paths
|
||||
*/
|
||||
public function __construct(
|
||||
Ai1ec_Registry_Object $registry,
|
||||
$name,
|
||||
array $paths
|
||||
) {
|
||||
parent::__construct( $registry );
|
||||
$this->_paths = $paths;
|
||||
$this->_name = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the content of the file to the screen.
|
||||
*/
|
||||
public function render() {
|
||||
echo $this->_content;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param bool $mute_output used for compatibility reason with old code.
|
||||
*
|
||||
* @return mixed the parsed content of the file.
|
||||
*/
|
||||
public function get_content( $mute_output = false ) {
|
||||
if ( true === $mute_output ) {
|
||||
return '';
|
||||
}
|
||||
return $this->_content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Just in case you want to echo the object.
|
||||
*/
|
||||
public function __toString() {
|
||||
return $this->_content;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
/**
|
||||
* Exception thrown when a file/extension is not found.
|
||||
*
|
||||
* @author Time.ly Network Inc.
|
||||
* @since 2.0
|
||||
*
|
||||
* @package AI1EC
|
||||
* @subpackage AI1EC.Theme.File
|
||||
*/
|
||||
class Ai1ec_File_Exception extends Ai1ec_Exception {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Handle finding and parsing an image file.
|
||||
*
|
||||
* @author Time.ly Network Inc.
|
||||
* @since 2.0
|
||||
* @instantiator new
|
||||
* @package AI1EC
|
||||
* @subpackage AI1EC.Theme.File
|
||||
*/
|
||||
class Ai1ec_File_Image extends Ai1ec_File_Abstract {
|
||||
|
||||
/**
|
||||
* @var string The url of the image file.
|
||||
*/
|
||||
protected $_url;
|
||||
|
||||
/**
|
||||
* Get the URL to the image file.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_url() {
|
||||
return $this->_url;
|
||||
}
|
||||
|
||||
/* (non-PHPdoc)
|
||||
* @see Ai1ec_File_Abstract::process_file()
|
||||
*/
|
||||
public function process_file() {
|
||||
$files_to_check = array();
|
||||
foreach ( array_keys( $this->_paths ) as $path ) {
|
||||
$files_to_check[$path] =
|
||||
$path . 'img' . DIRECTORY_SEPARATOR . $this->_name;
|
||||
}
|
||||
foreach ( $files_to_check as $path => $file ) {
|
||||
if ( file_exists( $file ) ) {
|
||||
// Construct URL based on base URL available in $this->_paths array.
|
||||
$this->_url = $this->_paths[$path] . '/img/' . $this->_name;
|
||||
$this->_content = $file;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Handle finding CSS/LESS files.
|
||||
*
|
||||
* @author Time.ly Network Inc.
|
||||
* @since 2.0
|
||||
* @instantiator new
|
||||
* @package AI1EC
|
||||
* @subpackage AI1EC.Theme.File
|
||||
*/
|
||||
class Ai1ec_File_Less extends Ai1ec_File_Abstract {
|
||||
|
||||
/**
|
||||
* @var string The default CSS folder.
|
||||
*/
|
||||
const THEME_CSS_FOLDER = 'css';
|
||||
|
||||
/**
|
||||
* @var string The default less folder.
|
||||
*/
|
||||
const THEME_LESS_FOLDER = 'less';
|
||||
|
||||
/**
|
||||
* Returns the name of the file.
|
||||
* @return string
|
||||
*/
|
||||
public function get_name() {
|
||||
return $this->_name;
|
||||
}
|
||||
|
||||
/* (non-PHPdoc)
|
||||
* @see Ai1ec_File_Abstract::process_file()
|
||||
*/
|
||||
public function process_file() {
|
||||
// 1. Look for a CSS file in the directory of the current theme.
|
||||
// 2. Look for a LESS version in the directory of the current theme.
|
||||
// 3. Look for a LESS file into the default theme folder.
|
||||
$name = $this->_name;
|
||||
$css_file = $name . '.css';
|
||||
$less_file = $name . '.less';
|
||||
$files_to_check = array();
|
||||
foreach ( $this->_paths as $path ) {
|
||||
$files_to_check[] =
|
||||
$path . self::THEME_LESS_FOLDER . DIRECTORY_SEPARATOR . $less_file;
|
||||
$files_to_check[] =
|
||||
$path . self::THEME_CSS_FOLDER . DIRECTORY_SEPARATOR . $css_file;
|
||||
if ( '..' . DIRECTORY_SEPARATOR . 'style' === $name ) {
|
||||
$files_to_check[] =
|
||||
$path . self::THEME_LESS_FOLDER . DIRECTORY_SEPARATOR . $css_file;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( $files_to_check as $file_to_check ) {
|
||||
if ( file_exists( $file_to_check ) ) {
|
||||
$this->_content = file_get_contents( $file_to_check );
|
||||
$this->_name = $file_to_check;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
/**
|
||||
* Handle finding and parsing a PHP file.
|
||||
*
|
||||
* @author Time.ly Network Inc.
|
||||
* @since 2.0
|
||||
* @instantiator new
|
||||
* @package AI1EC
|
||||
* @subpackage AI1EC.Theme.File
|
||||
*/
|
||||
class Ai1ec_File_Php extends Ai1ec_File_Abstract {
|
||||
|
||||
/**
|
||||
* @var string filename with the variables
|
||||
*/
|
||||
const USER_VARIABLES_FILE = 'user_variables';
|
||||
|
||||
/**
|
||||
* @var array the arguments used by the PHP template.
|
||||
*/
|
||||
private $_args;
|
||||
|
||||
/**
|
||||
* Initialize class specific variables.
|
||||
*
|
||||
* @parma Ai1ec_Registry_Object $registry
|
||||
* @param string $name
|
||||
* @param array $paths
|
||||
* @param array $args
|
||||
*/
|
||||
public function __construct(
|
||||
Ai1ec_Registry_Object $registry,
|
||||
$name,
|
||||
array $paths,
|
||||
array $args
|
||||
) {
|
||||
parent::__construct( $registry, $name, $paths );
|
||||
$this->_args = $args;
|
||||
}
|
||||
|
||||
/* (non-PHPdoc)
|
||||
* @see Ai1ec_File_Abstract::locate_file()
|
||||
*/
|
||||
public function process_file() {
|
||||
// if the file was already processed just return.
|
||||
if ( isset( $this->_content ) ) {
|
||||
return true;
|
||||
}
|
||||
$files_to_check = array();
|
||||
foreach ( array_values( $this->_paths ) as $path ) {
|
||||
$files_to_check[] = $path . $this->_name;
|
||||
}
|
||||
foreach ( $files_to_check as $file ) {
|
||||
if ( is_file( $file ) ) {
|
||||
// Check if file is custom LESS variable definitions.
|
||||
$user_variables_pattern = Ai1ec_File_Less::THEME_LESS_FOLDER .
|
||||
'/' . self::USER_VARIABLES_FILE;
|
||||
|
||||
if ( strpos( $this->_name, $user_variables_pattern ) === 0 ) {
|
||||
// It's a user variables file. We must handle the fact that it might
|
||||
// be legacy.
|
||||
if ( true === $this->_args['is_legacy_theme'] ) {
|
||||
$content = ( require $file );
|
||||
if ( isset( $less_user_variables ) ) {
|
||||
$content = $less_user_variables;
|
||||
}
|
||||
$this->_content = $content;
|
||||
} else {
|
||||
$this->_content = require $file;
|
||||
}
|
||||
} else {
|
||||
$this->_registry->get( 'compatibility.ob' )->start();
|
||||
extract( $this->_args );
|
||||
require $file;
|
||||
$this->_content = $this->_registry
|
||||
->get( 'compatibility.ob' )->get_clean();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy function to keep conpatibility with 1.x themes.
|
||||
*
|
||||
* @param string $file
|
||||
*/
|
||||
public function get_theme_img_url( $file ) {
|
||||
return $this->_registry->get( 'theme.loader' )
|
||||
->get_file( $file, array(), false )->get_url();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
/**
|
||||
* Handle finding and parsing a twig template.
|
||||
*
|
||||
* @author Time.ly Network Inc.
|
||||
* @since 2.0
|
||||
* @instantiator new
|
||||
* @package AI1EC
|
||||
* @subpackage AI1EC.Theme.File
|
||||
*/
|
||||
class Ai1ec_File_Twig extends Ai1ec_File_Abstract {
|
||||
|
||||
/**
|
||||
* @var Twig_Environment Twig environment for this helper.
|
||||
*/
|
||||
protected $_twig;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $_args;
|
||||
|
||||
/**
|
||||
* @param string $name The name of the template.
|
||||
* @param array $args The arguments needed to render the template.
|
||||
* @param Twig_Environment $twig An instance of the Twig environment
|
||||
*/
|
||||
public function __construct( $name, array $args, $twig ) {
|
||||
$this->_args = $args;
|
||||
$this->_name = $name;
|
||||
$this->_twig = $twig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given search path to the end of the list (low priority).
|
||||
*
|
||||
* @param string $search_path Path to add to end of list
|
||||
*/
|
||||
public function appendPath( $search_path ) {
|
||||
$loader = $this->_twig->getLoader();
|
||||
$loader->addPath( $search_path );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given search path to the front of the list (high priority).
|
||||
*
|
||||
* @param string $search_path Path to add to front of list
|
||||
*/
|
||||
public function prepend_path( $search_path ) {
|
||||
$loader = $this->_twig->getLoader();
|
||||
$loader->prependPath( $search_path );
|
||||
}
|
||||
|
||||
/* (non-PHPdoc)
|
||||
* @see Ai1ec_File::locate_file()
|
||||
*/
|
||||
public function process_file() {
|
||||
$loader = $this->_twig->getLoader();
|
||||
if ( $loader->exists( $this->_name ) ) {
|
||||
|
||||
$this->_content = $this->_twig->render( $this->_name, $this->_args );
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
385
wp-content/plugins/all-in-one-event-calendar/lib/theme/list.php
Normal file
385
wp-content/plugins/all-in-one-event-calendar/lib/theme/list.php
Normal file
@@ -0,0 +1,385 @@
|
||||
<?php
|
||||
|
||||
if ( ! class_exists( 'WP_List_Table' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
|
||||
}
|
||||
/**
|
||||
* Extends WP_List_Table to list our calerndar themes.
|
||||
*
|
||||
* @author Time.ly Network Inc.
|
||||
* @since 2.0
|
||||
*
|
||||
* @package AI1EC
|
||||
* @subpackage AI1EC.Theme
|
||||
*/
|
||||
class Ai1ec_Theme_List extends WP_List_Table {
|
||||
|
||||
/**
|
||||
* @var array List of search terms
|
||||
*/
|
||||
public $search = array();
|
||||
|
||||
/**
|
||||
* @var array List of features
|
||||
*/
|
||||
public $features = array();
|
||||
|
||||
/**
|
||||
* @var Ai1ec_Registry_Object
|
||||
*/
|
||||
protected $_registry;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* Overriding constructor to allow inhibiting parents startup sequence.
|
||||
* If in some wild case you need to inhibit startup sequence of parent
|
||||
* class - pass `array( 'inhibit' => true )` as argument to this one.
|
||||
*
|
||||
* @param array $args Options to pass to parent constructor
|
||||
*
|
||||
* @return void Constructor does not return
|
||||
*/
|
||||
public function __construct(
|
||||
Ai1ec_Registry_Object $registry,
|
||||
$args = array()
|
||||
) {
|
||||
$this->_registry = $registry;
|
||||
if ( ! isset( $args['inhibit'] ) ) {
|
||||
parent::__construct( $args );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* prepare_items function
|
||||
*
|
||||
* Prepares themes for display, applies search filters if available
|
||||
*
|
||||
* @return void
|
||||
**/
|
||||
public function prepare_items() {
|
||||
global $ct;
|
||||
|
||||
// setting wp_themes to null in case
|
||||
// other plugins have changed its value
|
||||
unset( $GLOBALS['wp_themes'] );
|
||||
|
||||
// get available themes
|
||||
$ct = $this->current_theme_info();
|
||||
|
||||
$themes = $this->_registry->get( 'theme.search' )
|
||||
->filter_themes();
|
||||
|
||||
if ( isset( $ct->name ) && isset( $themes[$ct->name] ) ) {
|
||||
unset( $themes[$ct->name] );
|
||||
}
|
||||
|
||||
// sort themes using strnatcasecmp function
|
||||
uksort( $themes, 'strnatcasecmp' );
|
||||
|
||||
// themes per page
|
||||
$per_page = 24;
|
||||
|
||||
// get current page
|
||||
$page = $this->get_pagenum();
|
||||
$start = ( $page - 1 ) * $per_page;
|
||||
|
||||
$this->items = array_slice( $themes, $start, $per_page );
|
||||
|
||||
// set total themes and themes per page
|
||||
$this->set_pagination_args( array(
|
||||
'total_items' => count( $themes ),
|
||||
'per_page' => $per_page,
|
||||
) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns html display of themes table
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function display() {
|
||||
$this->tablenav( 'top' );
|
||||
echo '<div id="availablethemes">',
|
||||
$this->display_rows_or_placeholder(),
|
||||
'</div>';
|
||||
$this->tablenav( 'bottom' );
|
||||
}
|
||||
|
||||
/**
|
||||
* tablenav function
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function tablenav( $which = 'top' ) {
|
||||
if ( $this->get_pagination_arg( 'total_pages' ) <= 1 ) {
|
||||
return '';
|
||||
}
|
||||
?>
|
||||
<div class="tablenav themes <?php echo $which; ?>">
|
||||
<?php $this->pagination( $which ); ?>
|
||||
<img src="<?php echo esc_url( ai1ec_admin_url( 'images/wpspin_light.gif' ) ); ?>"
|
||||
class="ajax-loading list-ajax-loading"
|
||||
alt="" />
|
||||
<br class="clear" />
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* ajax_user_can function
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function ajax_user_can() {
|
||||
// Do not check edit_theme_options here.
|
||||
// AJAX calls for available themes require switch_themes.
|
||||
return current_user_can( 'switch_themes' );
|
||||
}
|
||||
|
||||
/**
|
||||
* no_items function
|
||||
*
|
||||
* @return void
|
||||
**/
|
||||
public function no_items() {
|
||||
if ( is_multisite() ) {
|
||||
if (
|
||||
current_user_can( 'install_themes' ) &&
|
||||
current_user_can( 'manage_network_themes' )
|
||||
) {
|
||||
printf(
|
||||
Ai1ec_I18n::__(
|
||||
'You only have one theme enabled for this site right now. Visit the Network Admin to <a href="%1$s">enable</a> or <a href="%2$s">install</a> more themes.'
|
||||
),
|
||||
ai1ec_network_admin_url(
|
||||
'site-themes.php?id=' . $GLOBALS['blog_id']
|
||||
),
|
||||
ai1ec_network_admin_url( 'theme-install.php' )
|
||||
);
|
||||
|
||||
return;
|
||||
} elseif ( current_user_can( 'manage_network_themes' ) ) {
|
||||
printf(
|
||||
Ai1ec_I18n::__(
|
||||
'You only have one theme enabled for this site right now. Visit the Network Admin to <a href="%1$s">enable</a> more themes.'
|
||||
),
|
||||
ai1ec_network_admin_url(
|
||||
'site-themes.php?id=' . $GLOBALS['blog_id']
|
||||
)
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
// else, fallthrough. install_themes doesn't help if you
|
||||
// can't enable it.
|
||||
} else {
|
||||
if ( current_user_can( 'install_themes' ) ) {
|
||||
printf(
|
||||
Ai1ec_I18n::__(
|
||||
'You only have one theme installed right now. You can choose from many free themes in the Timely Theme Directory at any time: just click on the <a href="%s">Install Themes</a> tab above.'
|
||||
),
|
||||
ai1ec_admin_url( AI1EC_THEME_SELECTION_BASE_URL )
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Fallthrough.
|
||||
printf(
|
||||
Ai1ec_I18n::__(
|
||||
'Only the active theme is available to you. Contact the <em>%s</em> administrator to add more themes.'
|
||||
),
|
||||
get_site_option( 'site_name' )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* get_columns function
|
||||
*
|
||||
* @return array
|
||||
**/
|
||||
public function get_columns() {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* display_rows function
|
||||
*
|
||||
* @return void
|
||||
**/
|
||||
function display_rows() {
|
||||
$themes = $this->items;
|
||||
$theme_names = array_keys( $themes );
|
||||
natcasesort( $theme_names );
|
||||
|
||||
foreach ( $theme_names as $theme_name ) {
|
||||
$class = array( 'available-theme' );
|
||||
?>
|
||||
<div class="<?php echo join( ' ', $class ); ?>">
|
||||
<?php
|
||||
if ( !empty( $theme_name ) ) :
|
||||
$template = $themes[$theme_name]['Template'];
|
||||
$stylesheet = $themes[$theme_name]['Stylesheet'];
|
||||
$title = $themes[$theme_name]['Title'];
|
||||
$version = $themes[$theme_name]['Version'];
|
||||
$description = $themes[$theme_name]['Description'];
|
||||
$author = $themes[$theme_name]['Author'];
|
||||
$screenshot = $themes[$theme_name]['Screenshot'];
|
||||
$stylesheet_dir = $themes[$theme_name]['Stylesheet Dir'];
|
||||
$template_dir = $themes[$theme_name]['Template Dir'];
|
||||
$parent_theme = $themes[$theme_name]['Parent Theme'];
|
||||
$theme_root = $themes[$theme_name]['Theme Root'];
|
||||
$theme_dir = $themes[$theme_name]->get_stylesheet_directory();
|
||||
$theme_root_uri = esc_url( $themes[$theme_name]['Theme Root URI'] );
|
||||
$tags = $themes[$theme_name]['Tags'];
|
||||
|
||||
// Generate theme activation link.
|
||||
$activate_link = ai1ec_admin_url( AI1EC_THEME_SELECTION_BASE_URL );
|
||||
$activate_link = add_query_arg(
|
||||
array(
|
||||
'ai1ec_action' => 'activate_theme',
|
||||
'ai1ec_theme_dir' => $theme_dir,
|
||||
'ai1ec_legacy' => false, // hardcoded for 2.2
|
||||
'ai1ec_stylesheet' => $stylesheet,
|
||||
'ai1ec_theme_root' => $theme_root,
|
||||
'ai1ec_theme_url' => $theme_root_uri . '/' . $stylesheet,
|
||||
),
|
||||
$activate_link
|
||||
);
|
||||
$activate_link = wp_nonce_url(
|
||||
$activate_link,
|
||||
'switch-ai1ec_theme_' . $template
|
||||
);
|
||||
|
||||
$activate_text = esc_attr(
|
||||
sprintf(
|
||||
Ai1ec_I18n::__( 'Activate “%s”' ),
|
||||
$title
|
||||
)
|
||||
);
|
||||
$actions = array();
|
||||
$actions[] = '<a href="' . $activate_link .
|
||||
'" class="activatelink" title="' . $activate_text . '">' .
|
||||
Ai1ec_I18n::__( 'Activate' ) . '</a>';
|
||||
|
||||
$actions = apply_filters(
|
||||
'theme_action_links',
|
||||
$actions,
|
||||
$themes[$theme_name]
|
||||
);
|
||||
|
||||
$actions = implode ( ' | ', $actions );
|
||||
?>
|
||||
<?php if ( $screenshot ) : ?>
|
||||
<img src="<?php echo $theme_root_uri . '/' . $stylesheet . '/' . $screenshot; ?>" alt="" />
|
||||
<?php endif; ?>
|
||||
<h3>
|
||||
<?php
|
||||
/* translators: 1: theme title, 2: theme version, 3: theme author */
|
||||
printf(
|
||||
Ai1ec_I18n::__( '%1$s %2$s by %3$s' ),
|
||||
$title,
|
||||
$version,
|
||||
$author
|
||||
); ?></h3>
|
||||
<p class="description"><?php echo $description; ?></p>
|
||||
<span class='action-links'><?php echo $actions; ?></span>
|
||||
<?php if ( current_user_can( 'edit_themes' ) && $parent_theme ) {
|
||||
/* translators: 1: theme title, 2: template dir, 3: stylesheet_dir, 4: theme title, 5: parent_theme */ ?>
|
||||
<p>
|
||||
<?php
|
||||
printf(
|
||||
Ai1ec_I18n::__(
|
||||
'The template files are located in <code>%2$s</code>. The stylesheet files are located in <code>%3$s</code>. <strong>%4$s</strong> uses templates from <strong>%5$s</strong>. Changes made to the templates will affect both themes.'
|
||||
),
|
||||
$title,
|
||||
str_replace( WP_CONTENT_DIR, '', $template_dir ),
|
||||
str_replace( WP_CONTENT_DIR, '', $stylesheet_dir ),
|
||||
$title,
|
||||
$parent_theme
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
<?php } else { ?>
|
||||
<p>
|
||||
<?php
|
||||
printf(
|
||||
Ai1ec_I18n::__(
|
||||
'All of this theme’s files are located in <code>%2$s</code>.'
|
||||
),
|
||||
$title,
|
||||
str_replace( WP_CONTENT_DIR, '', $template_dir ),
|
||||
str_replace( WP_CONTENT_DIR, '', $stylesheet_dir )
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
<?php } ?>
|
||||
<?php if ( $tags ) : ?>
|
||||
<p>
|
||||
<?php echo Ai1ec_I18n::__( 'Tags:' ); ?> <?php echo join( ', ', $tags ); ?>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
<?php endif; // end if not empty theme_name ?>
|
||||
</div>
|
||||
<?php
|
||||
} // end foreach $theme_names
|
||||
}
|
||||
|
||||
/**
|
||||
* {@internal Missing Short Description}}
|
||||
*
|
||||
* @since 2.0.0
|
||||
*
|
||||
* @return unknown
|
||||
*/
|
||||
function current_theme_info() {
|
||||
$themes = $this->_registry->get( 'theme.search' )
|
||||
->filter_themes();
|
||||
$current_theme = $this->get_current_ai1ec_theme();
|
||||
if ( ! $themes ) {
|
||||
$ct = new stdClass;
|
||||
$ct->name = $current_theme;
|
||||
return $ct;
|
||||
}
|
||||
|
||||
if ( ! isset( $themes[$current_theme] ) ) {
|
||||
delete_option( 'ai1ec_current_theme' );
|
||||
$current_theme = $this->get_current_ai1ec_theme();
|
||||
}
|
||||
|
||||
$ct = new stdClass;
|
||||
$ct->name = $current_theme;
|
||||
$ct->title = $themes[$current_theme]['Title'];
|
||||
$ct->version = $themes[$current_theme]['Version'];
|
||||
$ct->parent_theme = $themes[$current_theme]['Parent Theme'];
|
||||
$ct->template_dir = $themes[$current_theme]['Template Dir'];
|
||||
$ct->stylesheet_dir = $themes[$current_theme]['Stylesheet Dir'];
|
||||
$ct->template = $themes[$current_theme]['Template'];
|
||||
$ct->stylesheet = $themes[$current_theme]['Stylesheet'];
|
||||
$ct->screenshot = $themes[$current_theme]['Screenshot'];
|
||||
$ct->description = $themes[$current_theme]['Description'];
|
||||
$ct->author = $themes[$current_theme]['Author'];
|
||||
$ct->tags = $themes[$current_theme]['Tags'];
|
||||
$ct->theme_root = $themes[$current_theme]['Theme Root'];
|
||||
$ct->theme_root_uri = esc_url( $themes[$current_theme]['Theme Root URI'] );
|
||||
return $ct;
|
||||
}
|
||||
/**
|
||||
* Retrieve current theme display name.
|
||||
*
|
||||
* If the 'current_theme' option has already been set, then it will be returned
|
||||
* instead. If it is not set, then each theme will be iterated over until both
|
||||
* the current stylesheet and current template name.
|
||||
*
|
||||
* @since 1.5.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_current_ai1ec_theme() {
|
||||
$option = $this->_registry->get( 'model.option' );
|
||||
$theme = $option->get( 'ai1ec_current_theme', array() );
|
||||
return $theme['stylesheet'];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,656 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Loads files for admin and frontend.
|
||||
*
|
||||
* @author Time.ly Network Inc.
|
||||
* @since 2.0
|
||||
*
|
||||
* @package AI1EC
|
||||
* @subpackage AI1EC.Theme
|
||||
*/
|
||||
class Ai1ec_Theme_Loader {
|
||||
|
||||
/**
|
||||
* @const string Name of option which forces theme clean-up if set to true.
|
||||
*/
|
||||
const OPTION_FORCE_CLEAN = 'ai1ec_clean_twig_cache';
|
||||
|
||||
/**
|
||||
* @const string Prefix for theme arguments filter name.
|
||||
*/
|
||||
const ARGS_FILTER_PREFIX = 'ai1ec_theme_args_';
|
||||
|
||||
/**
|
||||
* @var array contains the admin and theme paths.
|
||||
*/
|
||||
protected $_paths = array(
|
||||
'admin' => array( AI1EC_ADMIN_PATH => AI1EC_ADMIN_URL ),
|
||||
'theme' => array(),
|
||||
);
|
||||
|
||||
/**
|
||||
* @var Ai1ec_Registry_Object The registry Object.
|
||||
*/
|
||||
protected $_registry;
|
||||
|
||||
/**
|
||||
* @var array Array of Twig environments.
|
||||
*/
|
||||
protected $_twig = array();
|
||||
|
||||
/**
|
||||
* @var bool Whether this theme uses .php templates instead of .twig
|
||||
*/
|
||||
protected $_legacy_theme = false;
|
||||
|
||||
/**
|
||||
* @var bool Whether this theme is a child of the default theme
|
||||
*/
|
||||
protected $_child_theme = false;
|
||||
|
||||
/**
|
||||
* @var bool Whether this theme is a core theme
|
||||
*/
|
||||
protected $_core_theme = false;
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_legacy_theme() {
|
||||
return $this->_legacy_theme;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param $registry Ai1ec_Registry_Object
|
||||
* The registry Object.
|
||||
*/
|
||||
public function __construct(
|
||||
Ai1ec_Registry_Object $registry
|
||||
) {
|
||||
$this->_registry = $registry;
|
||||
$option = $this->_registry->get( 'model.option' );
|
||||
$theme = $option->get( 'ai1ec_current_theme' );
|
||||
$this->_legacy_theme = (bool)$theme['legacy'];
|
||||
|
||||
// Find out if this is a core theme.
|
||||
$core_themes = explode( ',', AI1EC_CORE_THEMES );
|
||||
$this->_core_theme = in_array( $theme['stylesheet'], $core_themes );
|
||||
|
||||
// Default theme's path is always the last in the list of paths to check,
|
||||
// so add it first (path list is a stack).
|
||||
$this->add_path_theme(
|
||||
AI1EC_DEFAULT_THEME_PATH . DIRECTORY_SEPARATOR,
|
||||
AI1EC_THEMES_URL . '/' . AI1EC_DEFAULT_THEME_NAME . '/'
|
||||
);
|
||||
|
||||
// If using a child theme, set flag and push its path to top of stack.
|
||||
if ( AI1EC_DEFAULT_THEME_NAME !== $theme['stylesheet'] ) {
|
||||
$this->_child_theme = true;
|
||||
$this->add_path_theme(
|
||||
$theme['theme_dir'] . DIRECTORY_SEPARATOR,
|
||||
$theme['theme_url'] . '/'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the filter for the specified filename just once
|
||||
*
|
||||
* @param array $args
|
||||
* @param string $filename
|
||||
* @param boole $is_admin
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function apply_filters_to_args( array $args, $filename, $is_admin ) {
|
||||
return apply_filters(
|
||||
self::ARGS_FILTER_PREFIX . $filename,
|
||||
$args,
|
||||
$is_admin
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds file search path to list. If an extension is adding this path, and
|
||||
* this is a custom child theme, inserts its path at the second index of the
|
||||
* list. Else pushes it onto the top of the stack.
|
||||
*
|
||||
* @param string $target Name of path purpose, i.e. 'admin' or 'theme'.
|
||||
* @param string $path Absolute path to the directory to search.
|
||||
* @param string $url URL to the directory represented by $path.
|
||||
* @param string $is_extension Whether an extension is adding this page.
|
||||
*
|
||||
* @return bool Success.
|
||||
*/
|
||||
public function add_path( $target, $path, $url, $is_extension = false ) {
|
||||
if ( ! isset( $this->_paths[$target] ) ) {
|
||||
// Invalid target.
|
||||
return false;
|
||||
}
|
||||
|
||||
$path = apply_filters( 'ai1ec_theme_loader_add_path_file', $path, $url, $target, $is_extension );
|
||||
$url = apply_filters( 'ai1ec_theme_loader_add_path_http', $url, $path, $target, $is_extension );
|
||||
// New element to insert into associative array.
|
||||
$new = array( $path => $url );
|
||||
|
||||
if (
|
||||
true === $is_extension &&
|
||||
true === $this->_child_theme &&
|
||||
false === $this->_core_theme
|
||||
) {
|
||||
// Special case: extract first element into $head and insert $new after.
|
||||
$head = array_splice( $this->_paths[$target], 0, 1 );
|
||||
} else {
|
||||
// Normal case: $new gets pushed to the top of the array.
|
||||
$head = array();
|
||||
}
|
||||
|
||||
$this->_paths[$target] = $head + $new + $this->_paths[$target];
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add admin files search path.
|
||||
*
|
||||
* @param string $path Path to admin template files.
|
||||
* @param string $url URL to the directory represented by $path.
|
||||
*
|
||||
* @return bool Success.
|
||||
*/
|
||||
public function add_path_admin( $path, $url ) {
|
||||
return $this->add_path( 'admin', $path, $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add theme files search path.
|
||||
*
|
||||
* @param string $path Path to theme template files.
|
||||
* @param string $url URL to the directory represented by $path.
|
||||
* @param string $is_extension Whether an extension is adding this path.
|
||||
*
|
||||
* @return bool Success.
|
||||
*/
|
||||
public function add_path_theme( $path, $url, $is_extension = false ) {
|
||||
return $this->add_path( 'theme', $path, $url, $is_extension );
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension registration hook to automatically add file paths.
|
||||
*
|
||||
* NOTICE: extensions are expected to exactly replicate Core directories
|
||||
* structure. If different extension is to be developed at some point in
|
||||
* time - this will have to be changed.
|
||||
*
|
||||
* @param string $path Absolute path to extension's directory.
|
||||
* @param string $url URL to directory represented by $path.
|
||||
*
|
||||
* @return Ai1ec_Theme_Loader Instance of self for chaining.
|
||||
*/
|
||||
public function register_extension( $path, $url ) {
|
||||
$D = DIRECTORY_SEPARATOR; // For readability.
|
||||
|
||||
// Add extension's admin path.
|
||||
$this->add_path_admin(
|
||||
$path . $D .'public' . $D . 'admin' . $D,
|
||||
$url . '/public/admin/'
|
||||
);
|
||||
|
||||
// Add extension's theme path(s).
|
||||
$option = $this->_registry->get( 'model.option' );
|
||||
$theme = $option->get( 'ai1ec_current_theme' );
|
||||
|
||||
// Default theme's path is always later in the list of paths to check,
|
||||
// so add it first (path list is a stack).
|
||||
$this->add_path_theme(
|
||||
$path . $D . 'public' . $D . AI1EC_THEME_FOLDER . $D .
|
||||
AI1EC_DEFAULT_THEME_NAME . $D,
|
||||
$url . '/public/' . AI1EC_THEME_FOLDER . '/' . AI1EC_DEFAULT_THEME_NAME .
|
||||
'/',
|
||||
true
|
||||
);
|
||||
|
||||
// If using a core child theme, set flag and push its path to top of stack.
|
||||
if ( true === $this->_child_theme && true === $this->_core_theme ) {
|
||||
$this->add_path_theme(
|
||||
$path . $D . 'public' . $D . AI1EC_THEME_FOLDER . $D .
|
||||
$theme['stylesheet'] . $D,
|
||||
$url . '/public/' . AI1EC_THEME_FOLDER . '/' . $theme['stylesheet'] .
|
||||
'/',
|
||||
true
|
||||
);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the requested file from the filesystem.
|
||||
*
|
||||
* Get the requested file from the filesystem. The file is already parsed.
|
||||
*
|
||||
* @param string $filename Name of file to load.
|
||||
* @param array $args Map of variables to use in file.
|
||||
* @param bool $is_admin Set to true for admin-side views.
|
||||
* @param bool $throw_exception Set to true to throw exceptions on error.
|
||||
* @param array $paths For PHP & Twig files only: list of paths to use instead of default.
|
||||
*
|
||||
* @throws Ai1ec_Exception If File is not found or not possible to handle.
|
||||
*
|
||||
* @return Ai1ec_File_Abstract An instance of a file object with content parsed.
|
||||
*/
|
||||
public function get_file(
|
||||
$filename,
|
||||
$args = array(),
|
||||
$is_admin = false,
|
||||
$throw_exception = true,
|
||||
array $paths = null
|
||||
) {
|
||||
$dot_position = strrpos( $filename, '.' ) + 1;
|
||||
$ext = substr( $filename, $dot_position );
|
||||
$file = false;
|
||||
|
||||
switch ( $ext ) {
|
||||
case 'less':
|
||||
case 'css':
|
||||
$filename_base = substr( $filename, 0, $dot_position - 1);
|
||||
$file = $this->_registry->get(
|
||||
'theme.file.less',
|
||||
$filename_base,
|
||||
array_keys( $this->_paths['theme'] ) // Values (URLs) not used for CSS
|
||||
);
|
||||
break;
|
||||
|
||||
case 'png':
|
||||
case 'gif':
|
||||
case 'jpg':
|
||||
$paths = $is_admin ? $this->_paths['admin'] : $this->_paths['theme'];
|
||||
$file = $this->_registry->get(
|
||||
'theme.file.image',
|
||||
$filename,
|
||||
$paths // Paths => URLs needed for images
|
||||
);
|
||||
break;
|
||||
|
||||
case 'php':
|
||||
$args = apply_filters(
|
||||
self::ARGS_FILTER_PREFIX . $filename,
|
||||
$args,
|
||||
$is_admin
|
||||
);
|
||||
if ( null === $paths ) {
|
||||
$paths = $is_admin ? $this->_paths['admin'] : $this->_paths['theme'];
|
||||
$paths = array_keys( $paths ); // Values (URLs) not used for PHP
|
||||
}
|
||||
$args['is_legacy_theme'] = $this->_legacy_theme;
|
||||
$file = $this->_registry->get(
|
||||
'theme.file.php',
|
||||
$filename,
|
||||
$paths,
|
||||
$args
|
||||
);
|
||||
break;
|
||||
|
||||
case 'twig':
|
||||
$args = apply_filters(
|
||||
self::ARGS_FILTER_PREFIX . $filename,
|
||||
$args,
|
||||
$is_admin
|
||||
);
|
||||
|
||||
if ( null === $paths ) {
|
||||
$paths = $is_admin ? $this->_paths['admin'] : $this->_paths['theme'];
|
||||
$paths = array_keys( $paths ); // Values (URLs) not used for Twig
|
||||
}
|
||||
if ( true === $this->_legacy_theme && ! $is_admin ) {
|
||||
$filename = substr( $filename, 0, $dot_position - 1);
|
||||
$file = $this->_get_legacy_file(
|
||||
$filename,
|
||||
$args,
|
||||
$paths
|
||||
);
|
||||
} else {
|
||||
$file = $this->_registry->get(
|
||||
'theme.file.twig',
|
||||
$filename,
|
||||
$args,
|
||||
$this->_get_twig_instance( $paths, $is_admin )
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Ai1ec_Exception(
|
||||
sprintf(
|
||||
Ai1ec_I18n::__( "We couldn't find a suitable loader for filename with extension '%s'" ),
|
||||
$ext
|
||||
)
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
// here file is a concrete class otherwise the exception is thrown
|
||||
if ( ! $file->process_file() && true === $throw_exception ) {
|
||||
throw new Ai1ec_Exception(
|
||||
'The specified file "' . $filename . '" doesn\'t exist.'
|
||||
);
|
||||
}
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reuturns loader paths.
|
||||
*
|
||||
* @return array Loader paths.
|
||||
*/
|
||||
public function get_paths() {
|
||||
return $this->_paths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to load a PHP file from the theme. If not present, it falls back to
|
||||
* Twig.
|
||||
*
|
||||
* @param string $filename Filename to locate
|
||||
* @param array $args Args to pass to template
|
||||
* @param array $paths Array of paths to search
|
||||
*
|
||||
* @return Ai1ec_File_Abstract
|
||||
*/
|
||||
protected function _get_legacy_file( $filename, array $args, array $paths ) {
|
||||
$php_file = $filename . '.php';
|
||||
$php_file = $this->get_file( $php_file, $args, false, false, $paths );
|
||||
|
||||
if ( false === $php_file->process_file() ) {
|
||||
$twig_file = $this->_registry->get(
|
||||
'theme.file.twig',
|
||||
$filename . '.twig',
|
||||
$args,
|
||||
$this->_get_twig_instance( $paths, false )
|
||||
);
|
||||
|
||||
// here file is a concrete class otherwise the exception is thrown
|
||||
if ( ! $twig_file->process_file() ) {
|
||||
throw new Ai1ec_Exception(
|
||||
'The specified file "' . $filename . '" doesn\'t exist.'
|
||||
);
|
||||
}
|
||||
return $twig_file;
|
||||
}
|
||||
return $php_file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Twig instance.
|
||||
*
|
||||
* @param bool $is_admin Set to true for admin views.
|
||||
* @param bool $refresh Set to true to get fresh instance.
|
||||
*
|
||||
* @return Twig_Environment Configured Twig instance.
|
||||
*/
|
||||
public function get_twig_instance( $is_admin = false, $refresh = false ) {
|
||||
if ( $refresh ) {
|
||||
unset( $this->_twig );
|
||||
}
|
||||
$paths = $is_admin ? $this->_paths['admin'] : $this->_paths['theme'];
|
||||
$paths = array_keys( $paths ); // Values (URLs) not used for Twig
|
||||
return $this->_get_twig_instance( $paths, $is_admin );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache dir for Twig.
|
||||
*
|
||||
* @param bool $rescan Set to true to force rescan
|
||||
*
|
||||
* @return string|bool Cache directory or false
|
||||
*/
|
||||
public function get_cache_dir( $rescan = false ) {
|
||||
$settings = $this->_registry->get( 'model.settings' );
|
||||
$ai1ec_twig_cache = $settings->get( 'twig_cache' );
|
||||
if (
|
||||
! empty( $ai1ec_twig_cache ) &&
|
||||
false === $rescan
|
||||
) {
|
||||
return ( AI1EC_CACHE_UNAVAILABLE === $ai1ec_twig_cache )
|
||||
? false
|
||||
: $ai1ec_twig_cache;
|
||||
}
|
||||
$path = false;
|
||||
$scan_dirs = array( AI1EC_TWIG_CACHE_PATH );
|
||||
if ( apply_filters( 'ai1ec_check_static_dir', true ) ) {
|
||||
$filesystem = $this->_registry->get( 'filesystem.checker' );
|
||||
$upload_folder = $filesystem->get_ai1ec_static_dir_if_available();
|
||||
if ( '' !== $upload_folder ) {
|
||||
$scan_dirs[] = $upload_folder;
|
||||
}
|
||||
}
|
||||
foreach ( $scan_dirs as $dir ) {
|
||||
if ( $this->_is_dir_writable( $dir ) ) {
|
||||
$path = $dir;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$settings->set(
|
||||
'twig_cache',
|
||||
false === $path ? AI1EC_CACHE_UNAVAILABLE : $path
|
||||
);
|
||||
if ( false === $path ) {
|
||||
/* @TODO: move this to Settings -> Advanced -> Cache and provide a nice message */
|
||||
}
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* After upgrade clean cache if it's not default.
|
||||
*
|
||||
* @return void Method doesn't return
|
||||
*/
|
||||
public function clean_cache_on_upgrade() {
|
||||
if ( ! apply_filters( 'ai1ec_clean_cache_on_upgrade', true ) ) {
|
||||
return;
|
||||
}
|
||||
$model_option = $this->_registry->get( 'model.option' );
|
||||
if ( $model_option->get( self::OPTION_FORCE_CLEAN, false ) ) {
|
||||
$model_option->set( self::OPTION_FORCE_CLEAN, false );
|
||||
$cache = realpath( $this->get_cache_dir() );
|
||||
if ( 0 === strcmp( $cache, realpath( AI1EC_TWIG_CACHE_PATH ) ) ) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
! $this->_registry->get(
|
||||
'theme.compiler'
|
||||
)->clean_and_check_dir( $cache )
|
||||
) {
|
||||
$this->_registry->get( 'twig.cache' )->set_unavailable( $cache );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method whould be in a factory called by the object registry.
|
||||
* I leave it here for reference.
|
||||
*
|
||||
* @param array $paths Array of paths to search
|
||||
* @param bool $is_admin whether to use the admin or not admin Twig instance
|
||||
*
|
||||
* @return Twig_Environment
|
||||
*/
|
||||
protected function _get_twig_instance( array $paths, $is_admin ) {
|
||||
$instance = $is_admin ? 'admin' : 'front';
|
||||
if ( ! isset( $this->_twig[$instance] ) ) {
|
||||
|
||||
// Set up Twig environment.
|
||||
$loader_path = array();
|
||||
|
||||
foreach ( $paths as $path ) {
|
||||
if ( is_dir( $path . 'twig' . DIRECTORY_SEPARATOR ) ) {
|
||||
$loader_path[] = $path . 'twig' . DIRECTORY_SEPARATOR;
|
||||
}
|
||||
}
|
||||
|
||||
$loader = new Ai1ec_Twig_Loader_Filesystem( $loader_path );
|
||||
unset( $loader_path );
|
||||
// TODO: Add cache support.
|
||||
$environment = array(
|
||||
'cache' => $this->get_cache_dir(),
|
||||
'optimizations' => -1, // all
|
||||
'auto_reload' => true,
|
||||
);
|
||||
if ( AI1EC_DEBUG ) {
|
||||
$environment += array(
|
||||
'debug' => true, // produce node structure
|
||||
);
|
||||
// auto_reload never worked well
|
||||
$environment['cache'] = false;
|
||||
unset( $environment['auto_reload'] );
|
||||
}
|
||||
$environment = apply_filters(
|
||||
'ai1ec_twig_environment',
|
||||
$environment
|
||||
);
|
||||
|
||||
$ai1ec_twig_environment = new Ai1ec_Twig_Environment(
|
||||
$loader,
|
||||
$environment
|
||||
);
|
||||
$ai1ec_twig_environment->set_registry( $this->_registry );
|
||||
|
||||
$this->_twig[$instance] = $ai1ec_twig_environment;
|
||||
if ( apply_filters( 'ai1ec_twig_add_debug', AI1EC_DEBUG ) ) {
|
||||
$this->_twig[$instance]->addExtension( new Twig_Extension_Debug() );
|
||||
}
|
||||
|
||||
$extension = $this->_registry->get( 'twig.ai1ec-extension' );
|
||||
$extension->set_registry( $this->_registry );
|
||||
$this->_twig[$instance]->addExtension( $extension );
|
||||
}
|
||||
return $this->_twig[$instance];
|
||||
}
|
||||
|
||||
/**
|
||||
* Called during 'after_setup_theme' action. Runs theme's special
|
||||
* functions.php file, if present.
|
||||
*/
|
||||
public function execute_theme_functions() {
|
||||
$option = $this->_registry->get( 'model.option' );
|
||||
$theme = $option->get( 'ai1ec_current_theme' );
|
||||
$functions = $theme['theme_dir'] . DIRECTORY_SEPARATOR . 'functions.php';
|
||||
|
||||
if ( file_exists( $functions ) ) {
|
||||
include( $functions );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Safe checking for directory writeability.
|
||||
*
|
||||
* @param string $dir Path of likely directory.
|
||||
*
|
||||
* @return bool Writeability.
|
||||
*/
|
||||
private function _is_dir_writable( $dir ) {
|
||||
$stack = array(
|
||||
dirname( dirname( $dir ) ),
|
||||
dirname( $dir ),
|
||||
$dir,
|
||||
);
|
||||
foreach ( $stack as $element ) {
|
||||
if ( is_dir( $element ) ) {
|
||||
continue;
|
||||
}
|
||||
if ( ! is_writable( dirname( $element ) ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( ! mkdir( $dir, 0755, true ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch to the given calendar theme.
|
||||
*
|
||||
* @param array $theme The theme's settings array
|
||||
* @param bool $delete_variables If true, deletes user variables from DB.
|
||||
* Else replaces them with config file.
|
||||
*/
|
||||
public function switch_theme( array $theme, $delete_variables = true ) {
|
||||
/* @var $option Ai1ec_Option */
|
||||
$option = $this->_registry->get( 'model.option' );
|
||||
$option->set(
|
||||
'ai1ec_current_theme',
|
||||
$theme
|
||||
);
|
||||
$option->delete( 'ai1ec_fer_checked' );
|
||||
$lessphp = $this->_registry->get( 'less.lessphp' );
|
||||
// If requested, delete theme variables from DB.
|
||||
if ( $delete_variables ) {
|
||||
$option->delete( Ai1ec_Less_Lessphp::DB_KEY_FOR_LESS_VARIABLES );
|
||||
}
|
||||
// Else replace them with those loaded from config file.
|
||||
else {
|
||||
$option->set(
|
||||
Ai1ec_Less_Lessphp::DB_KEY_FOR_LESS_VARIABLES,
|
||||
$lessphp->get_less_variable_data_from_config_file()
|
||||
);
|
||||
}
|
||||
// Recompile CSS for new theme.
|
||||
$css_controller = $this->_registry->get( 'css.frontend' );
|
||||
$css_controller->invalidate_cache( null, false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Switches to default Vortex theme.
|
||||
*
|
||||
* @param bool $silent Whether notify admin or not.
|
||||
*
|
||||
* @return void Method does not return.
|
||||
*/
|
||||
public function switch_to_vortex( $silent = false ) {
|
||||
$current_theme = $this->get_current_theme();
|
||||
if (
|
||||
isset( $current_theme['stylesheet'] ) &&
|
||||
'vortex' === $current_theme['stylesheet']
|
||||
) {
|
||||
return $current_theme;
|
||||
}
|
||||
$root = AI1EC_PATH . DIRECTORY_SEPARATOR . 'public' .
|
||||
DIRECTORY_SEPARATOR . AI1EC_THEME_FOLDER;
|
||||
$theme = array(
|
||||
'theme_root' => $root,
|
||||
'theme_dir' => $root . DIRECTORY_SEPARATOR . 'vortex',
|
||||
'theme_url' => AI1EC_URL . '/public/' . AI1EC_THEME_FOLDER . '/vortex',
|
||||
'stylesheet' => 'vortex',
|
||||
'legacy' => false
|
||||
);
|
||||
$this->switch_theme( $theme );
|
||||
if ( ! $silent ) {
|
||||
$this->_registry->get( 'notification.admin' )->store(
|
||||
Ai1ec_I18n::__(
|
||||
"Your calendar theme has been switched to Vortex due to a rendering problem. For more information, please enable debug mode by adding this line to your WordPress <code>wp-config.php</code> file:<pre>define( 'AI1EC_DEBUG', true );</pre>"
|
||||
),
|
||||
'error',
|
||||
0,
|
||||
array( Ai1ec_Notification_Admin::RCPT_ADMIN ),
|
||||
true
|
||||
);
|
||||
}
|
||||
return $theme;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current calendar theme.
|
||||
*
|
||||
* @return mixed Theme array or null.
|
||||
*
|
||||
* @throws Ai1ec_Bootstrap_Exception
|
||||
*/
|
||||
public function get_current_theme() {
|
||||
return $this->_registry->get(
|
||||
'model.option'
|
||||
)->get( 'ai1ec_current_theme' );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Serach calendar themes.
|
||||
*
|
||||
* @author Time.ly Network Inc.
|
||||
* @since 2.0
|
||||
*
|
||||
* @package AI1EC
|
||||
* @subpackage AI1EC.Theme
|
||||
*/
|
||||
class Ai1ec_Theme_Search extends Ai1ec_Base {
|
||||
|
||||
/**
|
||||
* @var array Holds global variables which need to be restored.
|
||||
*/
|
||||
protected $_restore = array();
|
||||
|
||||
/**
|
||||
* Gets the currently available themes.
|
||||
*
|
||||
* @return array The currently available themes
|
||||
*/
|
||||
public function get_themes() {
|
||||
$this->_pre_search( $this->get_theme_dirs() );
|
||||
|
||||
$options = array(
|
||||
'errors' => null, // null -> all
|
||||
'allowed' => null, // null -> all
|
||||
);
|
||||
$theme_map = null;
|
||||
if ( function_exists( 'wp_get_themes' ) ) {
|
||||
$theme_map = wp_get_themes( $options );
|
||||
} else {
|
||||
$theme_map = get_themes() + get_broken_themes();
|
||||
}
|
||||
|
||||
add_filter(
|
||||
'theme_root_uri',
|
||||
array( $this, 'get_root_uri_for_our_themes' ),
|
||||
10,
|
||||
3
|
||||
);
|
||||
foreach ( $theme_map as $theme ) {
|
||||
$theme->get_theme_root_uri();
|
||||
}
|
||||
|
||||
$this->_post_search();
|
||||
return $theme_map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the correct uri for our core themes.
|
||||
*
|
||||
* @param string $theme_root_uri
|
||||
* @param string $site_url
|
||||
* @param string $stylesheet_or_template
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_root_uri_for_our_themes(
|
||||
$theme_root_uri,
|
||||
$site_url,
|
||||
$stylesheet_or_template
|
||||
) {
|
||||
$core_themes = explode( ',', AI1EC_CORE_THEMES );
|
||||
if ( in_array( $stylesheet_or_template, $core_themes ) ) {
|
||||
return AI1EC_URL .'/public/' . AI1EC_THEME_FOLDER;
|
||||
}
|
||||
return $theme_root_uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add core folders to scan and allow injection of other.
|
||||
*
|
||||
* @return array The folder to scan for themes
|
||||
*/
|
||||
public function get_theme_dirs() {
|
||||
$theme_dirs = array(
|
||||
WP_CONTENT_DIR . DIRECTORY_SEPARATOR . AI1EC_THEME_FOLDER,
|
||||
AI1EC_DEFAULT_THEME_ROOT
|
||||
);
|
||||
|
||||
$theme_dirs = apply_filters( 'ai1ec_register_theme', $theme_dirs );
|
||||
$selected = array();
|
||||
foreach ( $theme_dirs as $directory ) {
|
||||
if ( is_dir( $directory ) ) {
|
||||
$selected[] = $directory;
|
||||
}
|
||||
}
|
||||
return $selected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replacecs global variables.
|
||||
*
|
||||
* @param array $variables_map
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function _replace_search_globals( array $variables_map ) {
|
||||
foreach ( $variables_map as $key => $current_value ) {
|
||||
global ${$key};
|
||||
$variables_map[$key] = ${$key};
|
||||
${$key} = $current_value;
|
||||
}
|
||||
search_theme_directories( true );
|
||||
return $variables_map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set some globals to allow theme searching.
|
||||
*
|
||||
* @param array $directories
|
||||
*/
|
||||
protected function _pre_search( array $directories ) {
|
||||
$this->_restore = $this->_replace_search_globals(
|
||||
array(
|
||||
'wp_theme_directories' => $directories,
|
||||
'wp_broken_themes' => array(),
|
||||
)
|
||||
);
|
||||
add_filter(
|
||||
'wp_cache_themes_persistently',
|
||||
'__return_false',
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset globals and filters post scan.
|
||||
*/
|
||||
protected function _post_search() {
|
||||
remove_filter(
|
||||
'wp_cache_themes_persistently',
|
||||
'__return_false',
|
||||
1
|
||||
);
|
||||
$this->_replace_search_globals( $this->_restore );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the current themes by search.
|
||||
*
|
||||
* @param array $terms
|
||||
* @param array $features
|
||||
* @param bool $broken
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function filter_themes(
|
||||
array $terms = array(),
|
||||
array $features = array(),
|
||||
$broken = false
|
||||
) {
|
||||
static $theme_list = null;
|
||||
if ( null === $theme_list ) {
|
||||
$theme_list = $this->get_themes();
|
||||
}
|
||||
|
||||
foreach ( $theme_list as $key => $theme ) {
|
||||
if (
|
||||
( ! $broken && false !== $theme->errors() ) ||
|
||||
! $this->theme_matches( $theme, $terms, $features )
|
||||
) {
|
||||
unset( $theme_list[$key] );
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return $theme_list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the $theme is a match for the search.
|
||||
*
|
||||
* @param WP_Theme $theme
|
||||
* @param array $search
|
||||
* @param array $features
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function theme_matches( $theme, array $search, array $features ) {
|
||||
static $fields = array(
|
||||
'Name',
|
||||
'Title',
|
||||
'Description',
|
||||
'Author',
|
||||
'Template',
|
||||
'Stylesheet',
|
||||
);
|
||||
|
||||
$tags = array_map(
|
||||
'sanitize_title_with_dashes',
|
||||
$theme['Tags']
|
||||
);
|
||||
|
||||
// Match all phrases
|
||||
if ( count( $search ) > 0 ) {
|
||||
foreach ( $search as $word ) {
|
||||
|
||||
// In a tag?
|
||||
if ( ! in_array( $word, $tags ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// In one of the fields?
|
||||
foreach ( $fields as $field ) {
|
||||
if ( false === stripos( $theme->get( $field ), $word ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Now search the features
|
||||
if ( count( $features ) > 0 ) {
|
||||
foreach ( $features as $word ) {
|
||||
// In a tag?
|
||||
if ( ! in_array( $word, $tags ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only get here if each word exists in the tags or one of the fields
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move passed themes to backup folder.
|
||||
*
|
||||
* @param array $themes
|
||||
*/
|
||||
public function move_themes_to_backup( array $themes ) {
|
||||
global $wp_filesystem;
|
||||
$root = WP_CONTENT_DIR . DIRECTORY_SEPARATOR . AI1EC_THEME_FOLDER . DIRECTORY_SEPARATOR;
|
||||
$backup = WP_CONTENT_DIR . DIRECTORY_SEPARATOR . AI1EC_THEME_FOLDER . '-obsolete' . DIRECTORY_SEPARATOR;
|
||||
// this will also set $wp_filesystem global
|
||||
$writable = $this->_registry->get( 'filesystem.checker')->is_writable( WP_CONTENT_DIR );
|
||||
// this also means the access is 'direct'
|
||||
$backup_dir_exists = false;
|
||||
|
||||
$errors = array();
|
||||
if ( true === $writable ) {
|
||||
if ( ! $wp_filesystem->is_dir( $backup ) ) {
|
||||
$backup_dir_exists = $wp_filesystem->mkdir( $backup );
|
||||
} else {
|
||||
$backup_dir_exists = true;
|
||||
}
|
||||
} else {
|
||||
$message = __(
|
||||
'Unable to move your old core themes from <code>wp-content/themes-ai1ec</code> to <code>wp-content/themes-ai1ec-obsolete</code> because your <code>wp-content</code> folder is not writable. Please manually remove your old core themes from <code>wp-content/themes-ai1ec</code>.',
|
||||
AI1EC_PLUGIN_NAME
|
||||
);
|
||||
$errors[] = $message;
|
||||
}
|
||||
if ( true === $backup_dir_exists ) {
|
||||
foreach ( $themes as $theme_dir ) {
|
||||
if ( $wp_filesystem->is_dir( $root . $theme_dir ) ) {
|
||||
$result = $wp_filesystem->move( $root . $theme_dir, $backup . $theme_dir );
|
||||
if ( false === $result ) {
|
||||
$message = __(
|
||||
'Failed to move your old core themes from <code>wp-content/themes-ai1ec/%s</code> to <code>wp-content/themes-ai1ec-obsolete/%s</code>. Please manually remove your old core themes from <code>wp-content/themes-ai1ec/%s</code>.',
|
||||
AI1EC_PLUGIN_NAME
|
||||
);
|
||||
$errors[] = sprintf( $message, $theme_dir, $theme_dir, $theme_dir );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( ! empty( $errors ) ) {
|
||||
$notification = $this->_registry->get( 'notification.admin' );
|
||||
$notification->store(
|
||||
implode( '<br/>', $errors ),
|
||||
'error',
|
||||
2,
|
||||
array( Ai1ec_Notification_Admin::RCPT_ALL ),
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user