Add upstream plugins

Signed-off-by: Adrian Nöthlich <git@promasu.tech>
This commit is contained in:
2019-10-25 22:42:20 +02:00
parent 5d3c2ec184
commit 290736650a
1186 changed files with 302577 additions and 0 deletions

View File

@@ -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 );
}
}

View File

@@ -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;
}
}

View File

@@ -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 {
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View 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 &#8220;%s&#8221;' ),
$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&#8217;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'];
}
}

View File

@@ -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' );
}
}

View File

@@ -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
);
}
}
}