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,66 @@
<?php
/**
* The Acces Control Object class.
*
* @author Time.ly Network Inc.
* @since 2.0
*
* @package AI1EC
* @subpackage AI1EC.acl
*/
class Ai1ec_Acl_Aco {
/**
* Whether it's All event page or not.
*
* @return boolean
*/
public function is_all_events_page() {
global $typenow;
return $typenow === 'ai1ec_event';
}
/**
* Whether the current request is for a network or blog admin page
*
* Does not inform on whether the user is an admin! Use capability checks to
* tell if the user should be accessing a section or not.
*
* @return bool True if inside WordPress administration pages.
*/
public function is_admin() {
return is_admin();
}
/**
* Check if we are editing our custom post type.
*
* @return boolean
*/
public function are_we_editing_our_post() {
global $post;
return (
is_object( $post ) &&
isset( $post->post_type ) &&
AI1EC_POST_TYPE === $post->post_type
);
}
/**
* Check if it's our own custom post type.
*
* @param int|object $post Optional. Post ID or post object.
* Default is the current post from the loop.
*
* @return boolean
*/
public function is_our_post_type( $post_to_check = null ) {
if ( null === $post_to_check ) {
global $post;
$post_to_check = $post;
}
return get_post_type( $post_to_check ) === AI1EC_POST_TYPE;
}
}

View File

@@ -0,0 +1,29 @@
<?php
/**
* The base class which simply sets the registry object.
*
* @author Time.ly Network, Inc.
* @since 2.0
* @package Ai1EC
* @subpackage Ai1EC.Bootstrap
*/
abstract class Ai1ec_Base {
/**
* @var Ai1ec_Registry_Object
*/
protected $_registry;
/**
* The contructor method.
*
* Stores in object injected registry object.
*
* @param Ai1ec_Registry_Object $registry Injected registry object.
*/
public function __construct( Ai1ec_Registry_Object $registry ) {
$this->_registry = $registry;
}
}

View File

@@ -0,0 +1,19 @@
<?php
/**
* Exceptions occuring during bootstrap
*
* @author Time.ly Network Inc.
* @since 2.0
*
* @package AI1EC
* @subpackage AI1EC.Exception
*/
class Ai1ec_Bootstrap_Exception extends Ai1ec_Exception {
public function get_html_message() {
return '<p>Failure in All-in-One Event Calendar core:<br />' .
$this->getMessage() . '</p>';
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,525 @@
<?php
/**
* Autoloader Class
*
* This class is responsible for loading all the requested class of the
* system
*
* @author Time.ly Network, Inc.
* @since 2.0
* @package Ai1EC
* @subpackage Ai1EC.Loader
*/
class Ai1ec_Loader {
/**
* @var string Used to specify new instances every time.
*/
CONST NEWINST = 'n';
/**
* @var string Used to specify to treat as singleton.
*/
CONST GLOBALINST = 'g';
/**
* @var array Map of files to be included
*/
protected $_paths = array();
/**
* @var bool Set to true when internal state is changed
*/
protected $_modified = false;
/**
* @var array Map of files already included
*/
protected $_included_files = array();
/**
* @var string The prefix used for the classes
*/
protected $_prefix = null;
/**
* @var string Base path to plugins core directory
*/
protected $_base_path = null;
/**
* @var array Registered folders.
*/
protected $_registered = array();
/**
* load method
*
* Load given class, via `require`, into memory
*
* @param string $class Name of class, which needs to be loaded
*
* @return Ai1ec_Loader Instance of self for chaining
*/
public function load( $class ) {
if ( isset( $this->_paths[$class] ) ) {
$this->include_file( $this->_paths[$class]['f'] );
}
return $this;
}
/**
* Method which actually includes required file.
*
* The PHP language construct used is `require` and not a `require_once`,
* as this is internal method, which shall guard itself against incidents
* that may occur during loading classes more than once.
* During include additional callbacks may be fired to include related
* files, i.e. speed-up further requires.
*
* @param string $file Name of file to include
*
* @return Ai1ec_Loader Instance of self for chaining
*/
public function include_file( $file ) {
if ( ! isset( $this->_included_files[$file] ) ) {
$this->_included_files[$file] = true;
require $file;
}
return $this->_included_files[$file];
}
/**
* collect_classes method
*
* Method to extract classes list from filesystem.
* Returned array contains names of class, as keys, and file entites as
* value, where *entities* means either a file name
* - {@see self::match_file()} for more.
*
* @return array Map of classes and corresponding file entites
*/
public function collect_classes( $path = null, $folder_name = AI1EC_PLUGIN_NAME ) {
// extension inject theit own base path
$path = ( null === $path ) ? $this->_base_path : $path;
$names = $this->_locate_all_files( $path, $folder_name );
$names = $this->_process_reflections( $names );
$this->_cache( $path, $names );
$this->_paths = array_merge( $this->_paths, $names );
return $names;
}
/**
* Read/write cached classes map.
*
* If no entries are provided - acts as cache reader.
*
* @param array $entries Entries to write [optional=null]
*
* @return bool|array False on failure, true on success in writer
* mode, cached entry in reader mode on success
*/
protected function _cache( $path, array $entries = null ) {
$cache_file = $this->_get_cache_file_path( $path );
if ( $entries ) {
if (
is_file( $cache_file ) &&
! is_writable( $cache_file ) ||
! is_writable( dirname( $cache_file ) )
) {
return false;
}
ksort( $entries, SORT_STRING );
$content = array(
'0registered' => $this->_registered,
'1class_map' => $entries,
);
$content = var_export( $content, true );
$content = $this->_sanitize_paths( $content, $path );
$content = '<?php return ' . $content . ';';
$this->_modified = false;
if (
false === file_put_contents( $cache_file, $content, LOCK_EX )
) { // LOCK_EX is not supported on all hosts (streams)
return (bool)file_put_contents( $cache_file, $content );
}
return true;
}
if ( ! is_file( $cache_file ) ) {
return false;
}
$cached = ( require $cache_file );
$this->_registered[$cache_file] = true;
return $cached['1class_map'];
}
/**
* Gets the way classes must be instanciated.
*
* Retrieves from annotations the way classes must be retrieved.
* Possible values are
* - new: a new instance is instantiated every time
* - global: treat as singleton
* - classname.method: a factory is used, specify it in that order
* The default if nothing is specified is global.
*
* @param ReflectionClass $class
*
* @return string
*/
protected function _get_instantiator( ReflectionClass $class ) {
$doc = $class->getDocComment();
preg_match_all(
'#^\s\*\s@instantiator\s+(.*)$#im',
$doc,
$annotations
);
$instantiator = '';
if ( isset( $annotations[1][0] ) ) {
$instantiator = rtrim( $annotations[1][0] );
}
return $this->_convert_instantiator_for_map( $instantiator );
}
/**
* Check if the registry must be injected in the constructor.
* By convention the registry will always be the first parameter.
*
* @param ReflectionClass $class The class to check
*
* @return boolean true if the registry must be injected, false if not.
*/
protected function _inject_registry( ReflectionClass $class ) {
$contructor = $class->getConstructor();
if ( null !== $contructor ) {
foreach ( $contructor->getParameters() as $param ) {
$param_class = $param->getClass();
if ( $param_class instanceof ReflectionClass ) {
$name = $param_class->getName();
if ( 'Ai1ec_Registry_Object' === $name ) {
return true;
}
}
}
}
return false;
}
/**
* Update the classmap with Reflection informations.
*
* @param array $names The class map.
*
* @return array The classmap with instantiator.
*/
protected function _process_reflections( array $names ) {
$this->_paths = array_merge( $this->_paths, $names );
spl_autoload_register( array( $this, 'load' ) );
foreach ( $names as $classname => &$data ) {
try {
$class = new ReflectionClass( $data['c'] );
$data['i'] = $this->_get_instantiator( $class );
if ( $this->_inject_registry( $class ) ) {
$data['r'] = 'y';
}
} catch ( ReflectionException $excpt ) { // unreachable class
$data['i'] = self::NEWINST;
}
}
return $names;
}
/**
* Converts the long form to the short form where applicable.
*
* @param string $instantiator
*
* @return string
*/
protected function _convert_instantiator_for_map( $instantiator ) {
if ( empty( $instantiator ) || 'global' === $instantiator ) {
return self::GLOBALINST;
}
if ( 'new' === $instantiator ) {
return self::NEWINST;
}
return $instantiator;
}
/**
* _locate_all_files method
*
* Scan file system, given path, recursively, to search for files and
* extract `class` names from them.
*
* @param string $path File system path to scan
*
* @return array Map of classes and corresponding files
*/
protected function _locate_all_files( $path, $folder_name ) {
$class_list = array();
$directory = opendir( $path );
while ( false !== ( $entry = readdir( $directory ) ) ) {
if ( is_null( $entry ) || '.' === $entry{0} || 'tests' === $entry || strpos( strtolower( $entry ), 'icalcreator' ) !== false ) {
continue;
}
$local_path = $path . DIRECTORY_SEPARATOR . $entry;
$base_path = substr( $local_path, strlen( $this->_base_path ) );
if ( is_dir( $local_path ) ) {
$class_list += $this->_locate_all_files( $local_path, $folder_name );
} else {
$class_list += $this->_extract_classes( $local_path, $folder_name );
}
}
closedir( $directory );
return $class_list;
}
/**
* _extract_classes method
*
* Extract names of classes from given file.
* So far only files ending in `.php` are processed and regular expression
* is used instead of `token_get_all` to increase parsing speed.
*
* @param string $file Name of file to scan
*
* @return array List of classes in file
*/
protected function _extract_classes( $file, $folder_name ) {
$class_list = array();
if ( '.php' === strrchr( $file, '.' ) ) {
$tokens = token_get_all( file_get_contents( $file ) );
for ( $i = 2, $count = count( $tokens ); $i < $count; $i++ ) {
if (
T_CLASS === $tokens[$i - 2][0] ||
T_INTERFACE === $tokens[$i - 2][0] &&
T_WHITESPACE === $tokens[$i - 1][0] &&
T_STRING === $tokens[$i][0]
) {
$names = $this->_generate_loader_names(
$tokens[$i][1],
$file,
$folder_name
);
foreach ( $names as $name ) {
$class_list[$name] = array(
'f' => $file,
'c' => $tokens[$i][1],
);
}
}
}
}
return $class_list;
}
/**
* Generate path name abbreviation.
*
* @param string $name Path name particle.
*
* @return string Abbreviated path name.
*/
public function path_name_shortening( $name ) {
return strtoupper( $name[0] );
}
/**
* _sanitize_paths method
*
* Sanitize paths before writing to cache file.
* Make sure, that constants and absolute paths are used independently
* of system used, thus making file cross-platform generatable.
*
* @param string $content Output to be written to cache file.
* @param string $base_path Base path to use if not default.
*
* @return string Modified content, with paths replaced
*/
protected function _sanitize_paths(
$content,
$base_path = null
) {
$local_ds = '/';
$ai1ec_path = $this->_base_path;
$const_name = 'AI1EC_PATH';
if ( null !== $base_path ) {
$ai1ec_path = $base_path;
$const_name = implode( array_map(
array( $this, 'path_name_shortening' ),
explode( '-', basename( $base_path ) )
) ) . '_PATH';
$const_name = str_replace( 'AIOEC', 'AI1EC', $const_name );
}
if ( '\\' === DIRECTORY_SEPARATOR ) {
$local_ds = '\\\\';
$ai1ec_path = str_replace( '\\', '\\\\', $ai1ec_path );
}
$content = str_replace(
'\'' . $ai1ec_path . $local_ds,
$const_name . ' . DIRECTORY_SEPARATOR . \'',
$content
);
$content = str_replace(
$local_ds,
'\' . DIRECTORY_SEPARATOR . \'',
$content
);
return $content;
}
/**
* Generate all the alternatives name that the loaded recognize.
*
* For example:
* The class Ai1ec_Html_Helper can be loaded as
* - html.helper ( the path to the file )
* - Ai1ec_Html_Helper ( needed by Autoload )
*
* @param $class string the original name of the class.
* @param $file string the file
*
* @return array An array of strings with the availables names.
*/
protected function _generate_loader_names( $class, $file, $folder_name ) {
$names = array( $class );
// Remove the extension.
$file = substr( $file, 0, strrpos( $file , '.' ) );
$file = strtr( $file, array( '//' => '/' ) );
// Get just the meaningful data.
$relative_path_position = strrpos( // offset of base directory
$file,
DIRECTORY_SEPARATOR . $folder_name . DIRECTORY_SEPARATOR
);
$file = substr(
$file,
strpos( // cut to app|lib|vendor|...
$file,
DIRECTORY_SEPARATOR,
$relative_path_position + strlen( $folder_name ) + 2
)
);
$names[] = str_replace(
DIRECTORY_SEPARATOR,
'.',
trim( $file, DIRECTORY_SEPARATOR )
);
return $names;
}
/**
* Translate the key to the actual class name if any
*
* @param $key string Key requested to initialize
*
* @return array|null Array of the class, or null if none is found
*/
public function resolve_class_name( $key ) {
if ( ! isset( $this->_paths[$key] ) ) {
return null;
}
return $this->_paths[$key];
}
/**
* Update cache if object was modified
*
* @return void Destructor does not return
*/
public function __destruct() {
if ( $this->_modified ) {
$this->_cache( $this->_paths );
}
}
/**
* Convenience wrapper to detect internal extension file path.
*
* @param string $path Absolute path to extension base directory.
*
* @return bool Success loading extension classes.
*/
public function register_extension_map( $path ) {
return $this->register_map( $this->_get_cache_file_path( $path ) );
}
/**
* Register external class map to use in loading sequence
*
* @param string $file Path to class map
*
* @return bool Success loading it
*/
public function register_map( $file ) {
if (
isset( $this->_registered[$file] ) && (
! defined( 'AI1EC_DEBUG' ) ||
! AI1EC_DEBUG
)
) {
return true;
}
if ( ! is_file( $file ) ) {
return false;
}
$entries = ( require $file );
foreach ( $entries['1class_map'] as $class_name => $properties ) {
$this->_paths[$class_name] = $properties;
}
$this->_registered[$file] = true;
return true;
}
/**
* Constructor
*
* Initialize the loader creating the map of available classes, if the
* AI1EC_DEBUG constants is true the list is regenerated
*
* @throws Exception if the map is invalid
*
* @return void Constructor does not return
*/
public function __construct( $base_path ) {
$this->_base_path = $base_path;
$this->_prefix = explode( '_', __CLASS__ );
$this->_prefix = $this->_prefix[0];
$class_map = $this->_cache( $base_path );
if (
! is_array( $class_map ) ||
defined( 'AI1EC_DEBUG' ) && AI1EC_DEBUG
) {
if ( ! defined( 'AI1EC_DEBUG' ) || ! AI1EC_DEBUG ) {
// using generic `Ai1ec_Exception` as others are, potentially,
// not resolved at this time.
throw new Ai1ec_Exception(
'Generated class map is invalid: ' .
var_export( $class_map, true ) .
'. Please delete lib/bootstrap/loader-map.php (if it exists), make ' .
'sure lib/bootstrap/ is writable by the web server, and enable ' .
'debug mode by setting AI1EC_DEBUG to true (then back to false ' .
'when done).'
);
}
$class_map = $this->collect_classes();
}
$this->_paths = $class_map;
}
/**
* Method to get cache file path given path to plugin.
*
* @param string $path Path to plugin directory.
*
* @return string Absolute path to loader cache file.
*/
protected function _get_cache_file_path( $path ) {
return $path . DIRECTORY_SEPARATOR . 'lib' . DIRECTORY_SEPARATOR .
'bootstrap' . DIRECTORY_SEPARATOR . 'loader-map.php';
}
}

View File

@@ -0,0 +1,48 @@
<?php
/**
* Application Registry: handles application wide variables.
*
* @author Time.ly Network, Inc.
* @since 2.0
* @package Ai1EC
* @subpackage Ai1EC.Object
*/
class Ai1ec_Registry_Application implements Ai1ec_Registry {
/**
* @var Ai1ec_Registry_Object
*/
protected $_registry;
/**
* @var array
*/
protected $_environment = array();
/**
* The contructor method.
*
* @param Ai1ec_Registry_Object $registry
*/
function __construct( Ai1ec_Registry_Object $registry ) {
$this->_registry = $registry;
}
/* (non-PHPdoc)
* @see Ai1ec_Registry::get()
*/
public function get( $key ) {
if ( ! isset ( $this->_environment[$key] ) ) {
return false;
}
return $this->_environment[$key];
}
/* (non-PHPdoc)
* @see Ai1ec_Registry::set()
*/
public function set( $key, $value ) {
$this->_environment[$key] = $value;
}
}

View File

@@ -0,0 +1,29 @@
<?php
/**
* The basic registry interface.
*
* @author Time.ly Network, Inc.
* @since 2.0
* @package Ai1EC
* @subpackage Ai1EC.Object
*/
interface Ai1ec_Registry {
/**
* Retrieves the key from the registry
*
* @param string $key
*
* @return mixed the value associated to the key.
*/
public function get( $key );
/**
* Set the key into the registry.
*
* @param string $key
* @param mixed $value
*/
public function set( $key, $value );
}

View File

@@ -0,0 +1,236 @@
<?php
/**
* Object Registry: get instance of requested and optionally registered object.
*
* Object (instance of a class) is generater, or returned from internal cache
* if it was requested and instantiated before.
*
* @author Time.ly Network, Inc.
* @since 2.0
* @package Ai1EC
* @subpackage Ai1EC.Object
*/
class Ai1ec_Registry_Object implements Ai1ec_Registry {
/**
* @var array The internal objects cache
*/
private $_objects = array();
/**
* @var Ai1ec_Loader The Ai1ec_Loader instance used by the registry
*/
private $_loader = null;
/**
* Get the loader ( used by extensions )
*
* @return Ai1ec_Loader
*/
public function get_loader() {
return $this->_loader;
}
/**
* Method prepares environment for easier extension integration.
*
* NOTICE: only extensions, that follow internal guideliness for
* files and methods organization must call this hook.
*
* Absolute path to extensions directory is autodetected, if not
* provided, appending plugins name to path to plugins dir.
*
* @param string $name Name of the extension.
* @param string $path Absolute path to extension directory.
*
* @return Ai1ec_Registry_Object Instance of self for chaining.
*/
public function extension_acknowledge( $name, $path = null ) {
if ( null === $path ) {
$path = AI1EC_EXTENSIONS_BASEDIR . $name;
}
if ( AI1EC_DEBUG ) {
$this->_loader->collect_classes( $path, $name );
}
$this->get( 'theme.loader' )->register_extension(
$path,
plugins_url( $name )
);
$this->_loader->register_extension_map( $path );
do_action( 'ai1ec_extension_loaded', $path, $name );
return $this;
}
/**
* Get class instance.
*
* Return an instance for the requested key, this method has an internal
* cache.
*
* @param string $key Name of previously registered object or parseable
* class name
*
* @return object Instance of the requested class
*/
public function get( $key ) {
$class_data = $this->_loader->resolve_class_name( $key );
if ( ! $class_data ) {
throw new Ai1ec_Bootstrap_Exception(
'Unable to resolve class for "' . $key . '"'
);
}
$class_name = $class_data['c'];
if (
'Ai1ec_Event' === $class_name &&
$this->get( 'compatibility.check' )->use_backward_compatibility()
) {
$class_name = 'Ai1ec_Event_Compatibility';
}
$instantiator = $class_data['i'];
$args = array_slice( func_get_args(), 1 );
if ( isset ( $class_data['r'] ) ) {
array_unshift( $args, $this );
}
if ( Ai1ec_Loader::NEWINST === $instantiator ) {
return $this->initiate(
$class_name,
$args
);
}
if ( Ai1ec_Loader::GLOBALINST === $instantiator ) {
if ( ! isset( $this->_objects[$class_name] ) ) {
// Ask the loader to load the required files to avoid autoloader
$this->_loader->load( $class_name );
$this->_objects[$class_name] = $this->initiate(
$class_name,
$args
);
}
return $this->_objects[$class_name];
}
// Ok it's a factory.
$factory = explode( '.', $instantiator );
return $this->dispatch(
$factory[0],
$factory[1],
$args
);
}
/**
* Allow to set previously created globally accessible class instance.
*
* @param string $name Class name to be used.
* @param object $object Actual instance of class above.
*
* @return void
*/
public function inject_object( $name, $object ) {
if ( ! is_object( $object ) || ! ( $object instanceof $name ) ) {
throw new Ai1ec_Bootstrap_Exception(
'Attempt to inject not an object / invalid object.'
);
}
$this->_objects[$name] = $object;
}
/* (non-PHPdoc)
* @see Ai1ec_Registry::set()
*/
public function set( $key, $value ) {
// The set method allows to inject classes from extensions into the registry.
new Ai1ec_Bootstrap_Exception( 'Not implemented' );
}
/**
* Instanciate the class given the class names and arguments.
*
* @param string $class_name The name of the class to instanciate.
* @param array $argv An array of aguments for construction.
*
* @return object A new instance of the requested class
*/
public function initiate( $class_name, array $argv = array() ) {
switch ( count( $argv ) ) {
case 0:
return new $class_name();
case 1:
return new $class_name( $argv[0] );
case 2:
return new $class_name( $argv[0], $argv[1] );
case 3:
return new $class_name( $argv[0], $argv[1], $argv[2] );
case 4:
return new $class_name( $argv[0], $argv[1], $argv[2], $argv[3] );
case 5:
return new $class_name( $argv[0], $argv[1], $argv[2], $argv[3], $argv[4] );
}
$reflected = new ReflectionClass( $class_name );
return $reflected->newInstanceArgs( $argv );
}
/**
* A call_user_func_array alternative.
*
* @param string $class
* @param string $method
* @param array $params
*
* @return mixed
*/
public function dispatch( $class, $method, $params = array() ) {
if ( empty( $class ) ) {
switch ( count( $params) ) {
case 0:
return $method();
case 1:
return $method( $params[0] );
case 2:
return $method( $params[0], $params[1] );
case 3:
return $method( $params[0], $params[1], $params[2] );
default:
return call_user_func_array( $method, $params );
}
} else {
// get an instance of the class
$class = $this->get( $class );
switch ( count( $params) ) {
case 0:
return $class->{$method}();
case 1:
return $class->{$method}( $params[0] );
case 2:
return $class->{$method}( $params[0], $params[1] );
case 3:
return $class->{$method}( $params[0], $params[1], $params[2] );
default:
return call_user_func_array(
array( $class, $method ),
$params
);
}
}
}
/**
* Constructor
*
* Initialize the Registry
*
* @param Ai1ec_Loader $ai1ec_loader Instance of Ai1EC classes loader
*
* @return void Constructor does not return
*/
public function __construct( $ai1ec_loader ) {
$this->_loader = $ai1ec_loader;
}
}

View File

@@ -0,0 +1,13 @@
<?php
/**
* Exception thrown when reading from cache.
*
* @author Time.ly Network, Inc.
* @since 2.0
* @package Ai1EC
* @subpackage Ai1EC.Cache.Exception
*/
class Ai1ec_Cache_Not_Set_Exception extends Ai1ec_Exception {
}

View File

@@ -0,0 +1,13 @@
<?php
/**
* Exception thrown when failing to write to cache.
*
* @author Time.ly Network, Inc.
* @since 2.0
* @package Ai1EC
* @subpackage Ai1EC.Cache.Exception
*/
class Ai1ec_Cache_Write_Exception extends Ai1ec_Exception {
}

View File

@@ -0,0 +1,52 @@
<?php
/**
* Interface for cache engines.
*
* @author Time.ly Network, Inc.
* @since 2.0
* @package Ai1EC
* @subpackage Ai1EC.Cache
*/
interface Ai1ec_Cache_Interface {
/**
* Set entry to cache.
*
* @param string $key Key for which value must be stored.
* @param mixed $value Actual value to store.
*
* @return bool Success.
*/
public function set( $key, $value );
/**
* Add entry to cache if one does not exist.
*
* @param string $key Key for which value must be stored.
* @param mixed $value Actual value to store.
*
* @return bool Success.
*/
public function add( $key, $value );
/**
* Retrieve value from cache.
*
* @param string $key Key for which to retrieve value.
* @param mixed $default Value to return if none found.
*
* @return mixed Previously stored or $default value.
*/
public function get( $key, $default = NULL );
/**
* Delete value from cache.
*
* @param string $key Key for value to remove.
*
* @return bool Success.
*/
public function delete( $key );
}

View File

@@ -0,0 +1,102 @@
<?php
/**
* In-memory cache storage engine.
*
* Store values in memory, for use in a single session scope.
*
* @instantiator new
* @author Time.ly Network, Inc.
* @since 2.0
* @package Ai1EC
* @subpackage Ai1EC.Cache
*/
final class Ai1ec_Cache_Memory implements Ai1ec_Cache_Interface {
/**
* @var array Map of memory entries.
*/
protected $_entries = array();
/**
* @var int Number of entries to hold in map.
*/
protected $_limit = 0;
/**
* Constructor initiates stack (memory) length.
*
* @param int $limit Number of entries specific to this location.
*
* @return void Constructor does not return.
*/
public function __construct( $limit = 50 ) {
$limit = (int)$limit;
if ( $limit < 10 ) {
$limit = 10;
}
$this->_limit = $limit;
}
/**
* Write data to memory under given key.
*
* @param string $key Key under which value must be written.
* @param mixed $value Value to associate with given key.
*
* @return bool Success.
*/
public function set( $key, $value ) {
if ( count( $this->_entries ) > $this->_limit ) {
array_shift( $this->_entries ); // discard
}
$this->_entries[$key] = $value;
return true;
}
/**
* Add data to memory under given key, if it does not exist.
*
* @param string $key Key under which value must be added.
* @param mixed $value Value to associate with given key.
*
* @return bool Success.
*/
public function add( $key, $value ) {
if ( isset( $this->_entries[$key] ) ) {
return false;
}
return $this->set( $key, $value );
}
/**
* Retrieve data from memory, stored under specified key.
*
* @param string $key Key under which value is expected to be.
* @param mixed $default Value to return if nothing is found.
*
* @return mixed Found value or {$default}.
*/
public function get( $key, $default = NULL ) {
if ( ! isset( $this->_entries[$key] ) ) {
return $default;
}
return $this->_entries[$key];
}
/**
* Remove entry from cache table.
*
* @param string $key Key to be removed.
*
* @return bool Success.
*/
public function delete( $key ) {
if ( ! isset( $this->_entries[$key] ) ) {
return false;
}
unset( $this->_entries[$key] );
return true;
}
}

View File

@@ -0,0 +1,47 @@
<?php
/**
* Base class for caching strategy.
*
*
* @author Time.ly Network, Inc.
* @since 2.0
* @package Ai1EC
* @subpackage Ai1EC.Cache.Strategy
*/
abstract class Ai1ec_Cache_Strategy extends Ai1ec_Base {
/**
* Retrieves the data store for the passed key
*
* @param string $key
* @throws Ai1ec_Cache_Not_Set_Exception if the key was not set
*/
abstract public function get_data( $key );
/**
* Write the data to the persistence Layer
*
* @throws Ai1ec_Cache_Write_Exception
* @param string $key
* @param string $value
*/
abstract public function write_data( $key, $value );
/**
* Deletes the data associated with the key from the persistence layer.
*
* @param string $key
*/
abstract public function delete_data( $key );
/**
* Delete multiple cache entries matching given pattern
*
* @param string $pattern Scalar pattern, which shall match key
*
* @return int Count of entries deleted
*/
abstract public function delete_matching( $pattern );
}

View File

@@ -0,0 +1,118 @@
<?php
/**
* Concrete class for APC caching strategy.
*
* @instantiator new
* @author Time.ly Network, Inc.
* @since 2.0
* @package Ai1EC
* @subpackage Ai1EC.Cache.Strategy
*/
class Ai1ec_Cache_Strategy_Apc extends Ai1ec_Cache_Strategy {
/**
* is_available method
*
* Checks if APC is available for use.
* Following pre-requisites are checked: APC functions availability,
* APC is enabled via configuration and PHP is not running in CGI.
*
* @return bool Availability
*/
static public function is_available() {
return function_exists( 'apc_store' ) &&
function_exists( 'apc_fetch' ) &&
ini_get( 'apc.enabled' ) &&
( false === strpos( php_sapi_name(), 'cgi' ) );
}
/**
*
* @see Ai1ec_Get_Data_From_Cache::get_data()
*
*/
public function get_data( $dist_key ) {
$key = $this->_key( $dist_key );
$data = apc_fetch( $key );
if ( false === $data ) {
throw new Ai1ec_Cache_Not_Set_Exception( "$dist_key not set" );
}
return $data;
}
/**
*
* @see Ai1ec_Write_Data_To_Cache::write_data()
*
*/
public function write_data( $dist_key, $value ) {
$key = $this->_key( $dist_key );
$store_method = 'apc_add';
if ( false !== ( $existing = apc_fetch( $key ) ) ) {
if ( $value === $existing ) {
return true;
}
$store_method = 'apc_store';
} elseif ( false === function_exists( $store_method ) ) {
$store_method = 'apc_store';
}
if ( false === $store_method( $key, $value ) ) {
try {
if ( $value !== $this->get_data( $key ) ) {
throw new Ai1ec_Cache_Not_Set_Exception( 'Data mis-match' );
}
} catch ( Ai1ec_Cache_Not_Set_Exception $excpt ) {
throw new Ai1ec_Cache_Not_Set_Exception(
'Failed to write ' . $dist_key . ' to APC cache'
);
}
}
return true;
}
/**
* (non-PHPdoc)
* @see Ai1ec_Write_Data_To_Cache::delete_data()
*/
public function delete_data( $key ) {
if ( false === apc_delete( $this->_key( $key ) ) ) {
return false;
}
return true;
}
/**
*
* @see Ai1ec_Write_Data_To_Cache::delete_matching()
*/
public function delete_matching( $pattern ) {
// not implemented - concider flushing APC cache
return 0;
}
/**
* _key method
*
* Make sure we are on the safe side - in case of multi-instances
* environment some prefix is required.
*
* @param string $key Key to be used against APC cache
*
* @return string Key with prefix prepended
*/
protected function _key( $key ) {
static $prefix = null;
if ( NULL === $prefix ) {
$prefix = substr( md5( ai1ec_get_site_url() ), 0, 8 );
}
if ( 0 !== strncmp( $key, $prefix, 8 ) ) {
$key = $prefix . $key;
}
return $key;
}
}

View File

@@ -0,0 +1,102 @@
<?php
/**
* Concrete class for DB caching strategy.
*
* @instantiator new
* @author Time.ly Network, Inc.
* @since 2.0
* @package Ai1EC
* @subpackage Ai1EC.Cache.Strategy
*/
class Ai1ec_Cache_Strategy_Db extends Ai1ec_Cache_Strategy {
/**
* @var Ai1ec_Option Instance of database adapter
*/
private $model_option;
public function __construct( Ai1ec_Registry_Object $registry, Ai1ec_Option $option ) {
parent::__construct( $registry );
$this->model_option = $option;
}
/**
*
* @see Ai1ec_Get_Data_From_Cache::get_data()
*
*/
public function get_data( $key ) {
$key = $this->_key( $key );
$data = $this->model_option->get( $key );
if ( false === $data ) {
throw new Ai1ec_Cache_Not_Set_Exception(
'No data under \'' . $key . '\' present'
);
}
return maybe_unserialize( $data );
}
/**
*
* @see Ai1ec_Write_Data_To_Cache::write_data()
*
*/
public function write_data( $key, $value ) {
$result = $this->model_option->set(
$this->_key( $key ),
maybe_serialize( $value )
);
if ( false === $result ) {
throw new Ai1ec_Cache_Write_Exception(
'An error occured while saving data to \'' . $key . '\''
);
}
}
/**
* (non-PHPdoc)
* @see Ai1ec_Write_Data_To_Cache::delete_data()
*/
public function delete_data( $key ) {
return $this->model_option->delete(
$this->_key( $key )
);
}
/**
*
* @see Ai1ec_Write_Data_To_Cache::delete_matching()
*/
public function delete_matching( $pattern ) {
$db = $this->_registry->get( 'dbi.dbi' );
$sql_query = $db->prepare(
'SELECT option_name FROM ' . $db->get_table_name( 'options' ) .
' WHERE option_name LIKE %s',
'%%' . $pattern . '%%'
);
$keys = $db->get_col( $sql_query );
foreach ( $keys as $key ) {
$this->model_option->delete( $key );
}
return count( $keys );
}
/**
* _key method
*
* Get safe key name to use within options API
*
* @param string $key Key to sanitize
*
* @return string Safe to use key
*/
protected function _key( $key ) {
if ( strlen( $key ) > 53 ) {
$hash = md5( $key );
$key = substr( $key, 0, 16 ) . '_' . $hash;
}
return $key;
}
}

View File

@@ -0,0 +1,177 @@
<?php
/**
* Concrete class for file caching strategy.
*
* @instantiator new
* @author Time.ly Network, Inc.
* @since 2.0
* @package Ai1EC
* @subpackage Ai1EC.Cache.Strategy
*/
class Ai1ec_Cache_Strategy_File extends Ai1ec_Cache_Strategy {
/**
* @var string
*/
private $_cache_dir;
private $_cache_url;
public function __construct( Ai1ec_Registry_Object $registry, array $cache_dir ) {
parent::__construct( $registry );
$this->_cache_dir = $cache_dir['path'];
$this->_cache_url = $cache_dir['url'];
}
/**
*
* @see Ai1ec_Get_Data_From_Cache::get_data()
*
*/
public function get_data( $file ) {
$file = $this->_get_file_name( $file );
if ( ! $file || ! file_exists( $this->_cache_dir . $file ) ) {
throw new Ai1ec_Cache_Not_Set_Exception(
'File \'' . $file . '\' does not exist'
);
}
return maybe_unserialize(
file_get_contents( $this->_cache_dir . $file )
);
}
/**
*
* @see Ai1ec_Write_Data_To_Cache::write_data()
*
*/
public function write_data( $filename, $value ) {
$filename = $this->_safe_file_name( $filename );
$value = maybe_serialize( $value );
$result = $this->_registry->get( 'filesystem.checker' )->put_contents(
$this->_cache_dir . $filename,
$value
);
if ( false === $result ) {
$message = 'An error occured while saving data to \'' .
$this->_cache_dir . $filename . '\'';
throw new Ai1ec_Cache_Write_Exception( $message );
}
return array(
'path' => $this->_cache_dir . $filename,
'url' => $this->_cache_url . $filename,
'file' => $filename,
);
}
/**
* (non-PHPdoc)
* @see Ai1ec_Write_Data_To_Cache::delete_data()
*/
public function delete_data( $filename ) {
// Check if file exists. It might not exists if you switch themes
// twice without never rendering the CSS
$filename = $this->_safe_file_name( $filename );
if (
file_exists( $this->_cache_dir . $filename ) &&
false === unlink( $this->_cache_dir . $filename )
) {
return false;
}
return true;
}
/**
*
* @see Ai1ec_Write_Data_To_Cache::delete_matching()
*/
public function delete_matching( $pattern ) {
$dirhandle = opendir( $this->_cache_dir );
if ( false === $dirhandle ) {
return 0;
}
$count = 0;
while ( false !== ( $entry = readdir( $dirhandle ) ) ) {
if ( '.' !== $entry{0} && false !== strpos( $entry, $pattern ) ) {
if ( unlink( $this->_cache_dir . $entry ) ) {
++$count;
}
}
}
closedir( $dirhandle );
return $count;
}
/**
* Get the extension for the file if required
*
* @param string $file
*
* @return string
*/
protected function _get_extension_for_file( $file ) {
$extensions = array(
'ai1ec_parsed_css' => '.css'
);
if ( isset( $extensions[$file] ) ) {
return $extensions[$file];
}
return '';
}
/**
* Tries to get the stored filename
*
* @param string $file
*
* @return boolean | string
*/
protected function _get_file_name( $file ) {
static $file_map = array(
'ai1ec_parsed_css' => 'ai1ec_filename_css',
);
if ( isset ( $file_map[$file] ) ) {
return $this->_registry->get( 'model.option' )->get( $file_map[$file] );
}
return false;
}
/**
* _safe_file_name method
*
* Generate safe file name for any storage case.
*
* @param string $file File name currently supplied
*
* @return string Sanitized file name
*/
protected function _safe_file_name( $file ) {
static $prefix = null;
$extension = $this->_get_extension_for_file( $file );
if ( null === $prefix ) {
// always include site_url when there is more than one
$pref_string = ai1ec_site_url();
if ( ! AI1EC_DEBUG ) {
// address multiple re-saves for a single version
// i.e. when theme settings are being edited
$pref_string .= mt_rand();
}
$prefix = substr( md5( $pref_string ), 0, 8 );
}
$length = strlen( $file );
if ( ! ctype_alnum( $file ) ) {
$file = preg_replace(
'|_+|',
'_',
preg_replace( '|[^a-z0-9\-,_]|', '_', $file )
);
}
if ( 0 !== strncmp( $file, $prefix, 8 ) ) {
$file = $prefix . '_' . $file;
}
return $file . $extension;
}
}

View File

@@ -0,0 +1,106 @@
<?php
/**
* The context class which handles the caching strategy.
*
* @instantiator Ai1ec_Factory_Strategy.create_persistence_context
* @author Time.ly Network, Inc.
* @since 2.0
* @package Ai1EC
* @subpackage Ai1EC.Cache.Strategy
*/
class Ai1ec_Persistence_Context {
/**
* @var string
*/
private $key_for_persistance;
/**
*
* @var Ai1ec_Cache_Strategy
*/
private $cache_strategy;
/**
*
* @param string $key_for_peristance
* @param Ai1ec_Cache_Strategy $cache_strategy
*/
public function __construct(
$key_for_persistance,
Ai1ec_Cache_Strategy $cache_strategy
) {
$this->cache_strategy = $cache_strategy;
$this->key_for_persistance = $key_for_persistance;
}
/**
* @throws Ai1ec_Cache_Not_Set_Exception
* @return string
*/
public function get_data_from_persistence() {
try {
$data = $this->cache_strategy->get_data( $this->key_for_persistance );
}
catch ( Ai1ec_Cache_Not_Set_Exception $e ) {
throw $e;
}
return $data;
}
/**
* Are we using file cache?
*
* @return boolean
*/
public function is_file_cache() {
return $this->cache_strategy instanceof Ai1ec_Cache_Strategy_File;
}
/**
* write_data_to_persistence method
*
* Write data to persistance layer. If that fails - false is returned.
* Exceptions are suspended, as cache write is not a fatal error by no
* mean, thus shall not be escalated further. If you want exception to
* be escalated - use lower layer method directly.
*
* @param mixed $data Unserialized data to write
*
* @return boll Success
*/
public function write_data_to_persistence( $data ) {
$return = true;
try {
$return = $this->cache_strategy->write_data(
$this->key_for_persistance,
$data
);
} catch ( Ai1ec_Cache_Write_Exception $e ) {
$return = false;
}
return $return;
}
/**
* Deletes the data stored in cache.
*/
public function delete_data_from_persistence() {
$this->cache_strategy->delete_data( $this->key_for_persistance );
}
/**
* delete_matching_entries_from_persistence method
*
* Delete matching entries from persistance.
*
* @param string $pattern Expected pattern, to be contained within key
*
* @return int Count of entries deleted
*/
public function delete_matching_entries_from_persistence( $pattern ) {
return $this->cache_strategy->delete_matching( $pattern );
}
}

View File

@@ -0,0 +1,59 @@
<?php
/**
* Concrete class for void caching strategy.
*
* @instantiator new
* @author Time.ly Network, Inc.
* @since 2.0
* @package Ai1EC
* @subpackage Ai1EC.Cache.Strategy
*/
class Ai1ec_Cache_Strategy_Void extends Ai1ec_Cache_Strategy {
/**
* Checks if engine is available
*
* @return bool Always true
*/
static public function is_available() {
return true;
}
/**
*
* @see Ai1ec_Get_Data_From_Cache::get_data()
*
*/
public function get_data( $dist_key ) {
throw new Ai1ec_Cache_Not_Set_Exception( "'$dist_key' not set" );
}
/**
*
* @see Ai1ec_Write_Data_To_Cache::write_data()
*
*/
public function write_data( $dist_key, $value ) {
throw new Ai1ec_Cache_Not_Set_Exception(
'Failed to write \'' . $dist_key . '\' to void cache'
);
}
/**
* (non-PHPdoc)
* @see Ai1ec_Write_Data_To_Cache::delete_data()
*/
public function delete_data( $key ) {
return false;
}
/**
*
* @see Ai1ec_Write_Data_To_Cache::delete_matching()
*/
public function delete_matching( $pattern ) {
return 0;
}
}

View File

@@ -0,0 +1,253 @@
<?php
/**
* The abstract class for the Calendar feeds tab.
*
* @author Time.ly Network Inc.
* @since 2.0
*
* @package AI1EC
* @subpackage AI1EC.Calendar-feed
*/
abstract class Ai1ec_Connector_Plugin extends Ai1ec_Base {
/**
* An associative array where the keys are the name of the variables stored in the Settings object
* while the values are the description in the admin Panel
*
* @var array;
*
*/
protected $settings = array();
/**
* An array of variables used by the plugin. Some of this variables are required:
* title => The name of the tab in the calendar feeds settings
* id => The id used in the href of the tab. Must be unique
*
* @var array
*/
protected $variables = array();
/**
* Handles any action the plugin requires when the users makes a POST in the calendar feeds page.
*/
abstract public function handle_feeds_page_post();
/**
* Get title to be used for tab human-identification.
*
* @return string Localized string.
*/
abstract public function get_tab_title();
/**
* Renders the content of the tab, where all the action takes place.
*
*/
abstract public function render_tab_content();
/**
* Let the plugin display an admin notice if neede.
*
*/
abstract public function display_admin_notices();
/**
* Run the code that cleans up the DB and CRON functions the plugin has installed.
*
*/
abstract public function run_uninstall_procedures();
/**
* Renders the HTML for the tabbed navigation
*
* @return void
* Echoes the HTML string that act as tab header for the plugin
*/
public function render_tab_header() {
// Use the standard view helper
$args = array(
'title' => $this->get_tab_title(),
'id' => $this->variables['id'],
);
$loader = $this->_registry->get( 'theme.loader' );
$file = $loader->get_file( 'plugins/tab_header.php', $args, true );
$file->render();
}
/**
* Gets the settings for the Plugin from the settings object.
*
* @param string $class_name The name of the Plugin for which we are
* retrieving the settings.
*
* @return array An associative array with the settings stored in settings
* object or an empty array if settings are not set.
*/
protected function get_plugin_settings( $class_name ) {
$plugins_options = $this->_registry->get( 'model.settings' )
->get( 'plugins_options' );
return isset( $plugins_options[$class_name] )
? $plugins_options[$class_name]
: array();
}
/**
* Generate an arry which contains all settings data.
*
* Only data that will be processed by the admin view is considered.
*
* @return array An array of Associative arrays that hold everything that's
* needed to render the settings field in the admin section.
*/
protected function generate_settings_array_for_admin_view() {
// Get the plugin settings
$plugin_settings = $this->get_plugin_settings( get_class( $this ) );
// This is the array that will be returned
$result = array();
// Iterate over the settings
foreach ( $this->settings as $setting ) {
if ( $setting['admin-page'] === TRUE ) {
// For each setting get it's value, description and id
$result[] = array (
"setting-description" => __( $setting['description'], AI1EC_PLUGIN_NAME ),
"setting-value" => $plugin_settings[$setting['id']],
"setting-id" => $setting['id'],
);
}
}
return $result;
}
/**
* Check that at least one of the settings has ha value.
*
* @param array $settings
*
* @return boolean
*/
protected function at_least_one_config_field_is_set( array $settings ) {
foreach ( $settings as $setting ) {
if( ! empty( $setting['setting-value'] ) ) {
return TRUE;
}
}
return FALSE;
}
/**
* If the plugin settings are not set they will be initialized to ''
*
*/
public function initialize_settings_if_not_set() {
// Get the class name.
$class_name = get_class( $this );
$settings = $this->_registry->get( 'model.settings' );
$plugins_options = $settings->get( 'plugins_options' );
// Check if the options have been set
if ( ! isset( $plugins_options[$class_name] ) ) {
// If not set them. The key is the class name, the value is an associative array
$plugins_options[$class_name] = array();
foreach ( $this->settings as $setting ) {
$plugins_options[$class_name][$setting['id']] = '';
}
}
$settings->set( 'plugins_options', $plugins_options );
}
/**
* Retrieves the specified plugin setting
*
* @param string $variable_name The name of the variable to be retrieved
*
* @return mixed The variable value or FALSE if it's not set
*/
protected function get_plugin_variable( $variable_name ) {
$plugin_settings = $this->get_plugin_settings( get_class( $this ) );
return isset( $plugin_settings[$variable_name] ) ? $plugin_settings[$variable_name] : FALSE;
}
/**
* Saves the variable int he plugin settings.
*
* @param string $variable_name The name of the variable to save.
*
* @param mixed $value The value of the variable to save.
*/
protected function save_plugin_variable( $variable_name, $value ) {
$this->save_plugin_settings( array( $variable_name => $value ), TRUE );
}
/**
* Saves the plugin settings in the settings object
*
* @param array $data
* An associative array of data to be saved
*
* @param boolean $not_from_setting_page
* True if the function is not called from the setting page and must trigger the saving, false otherwise
*/
public function save_plugin_settings( array $data, $not_from_setting_page = FALSE ) {
$settings = $this->_registry->get( 'model.settings' );
$plugins_options = $settings->get( 'plugins_options' );
// Get the class name.
$class_name = get_class( $this );
// We need to save the old settings so that we can then let the Facebook plugin check if the user changed app-id / secret
$old_settings = $this->get_plugin_settings( get_class( $this ) );
// Check if the options have been set
if ( isset( $plugins_options[$class_name] ) ) {
// If the options for the plugin are set, iterate over the settings
foreach ( $this->settings as $setting ) {
// Always check that the key is set, data can come from $_POST or from an internal call
if( isset( $data[$setting['id']] ) ) {
$plugins_options[$class_name][$setting['id']] = $data[$setting['id']];
}
}
}
$settings->set( 'plugins_options', $plugins_options );
if ( $not_from_setting_page === TRUE ) {
$settings->persist( );
} else {
$old_settings['page'] = $data['page'];
do_action( "ai1ec-$class_name-postsave-setting", $old_settings );
}
}
/**
* Prints an error message with standard formatting
*
* @param string $message The error message to be echoed to the screen
*
* @param boolean $close_tab_div TRUE if after the error message we should close the tab div
*/
protected function render_error_page( $message, $close_tab_div = FALSE ) {
$args = array();
$args['message'] = $message;
$loader = $this->_registry->get( 'theme.loader' );
$file = $loader->get_file( 'plugins/display_error_message.php', $args, true );
$file->render();
if( $close_tab_div === TRUE ) {
$this->render_closing_div_of_tab();
}
}
/**
* Renders the opening div of the tab and set the active status if this tab is the active one
*
* @param string $active_feed the tab that should be active.
*/
protected function render_opening_div_of_tab() {
$args = array(
'id' => $this->variables['id'],
);
$loader = $this->_registry->get( 'theme.loader' );
$file = $loader->get_file( 'plugins/render_opening_div.php', $args, true );
$file->render();
}
/**
* This renders the closing div of the tab.
*/
protected function render_closing_div_of_tab( ) {
echo '</div>';
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,146 @@
<?php
/**
* The class which handles manual Feeds import.
*
* @author Time.ly Network Inc.
* @since 2.4
*
* @package AI1EC
* @subpackage AI1EC.Calendar-feed
*/
class Ai1ecImportConnectorPlugin extends Ai1ec_Connector_Plugin {
/**
* @var array
* title: The title of the tab and the title of the configuration section
* id: The id used in the generation of the tab
*/
protected $variables = array(
'id' => 'import',
);
public function get_tab_title() {
return Ai1ec_I18n::__( 'Import Feeds' );
}
public function __construct( Ai1ec_Registry_Object $registry ) {
parent::__construct( $registry );
}
/**
* Returns the translations array
*
* @return array
*/
private function get_translations() {
$categories = isset( $_POST['ai1ec_categories'] ) ? $_POST['ai1ec_categories'] : array();
foreach ( $categories as &$cat ) {
$term = get_term( $cat, 'events_categories' );
$cat = $term->name;
}
$translations = array(
'[feed_url]' => $_POST['ai1ec_calendar_url'],
'[categories]' => implode( ', ', $categories ),
'[user_email]' => $_POST['ai1ec_submitter_email'],
'[site_title]' => get_bloginfo( 'name' ),
'[site_url]' => ai1ec_site_url(),
'[feeds_url]' => ai1ec_admin_url(
AI1EC_FEED_SETTINGS_BASE_URL . '#ics'
),
);
return $translations;
}
/**
* (non-PHPdoc)
*
* @see Ai1ec_Connector_Plugin::render_tab_content()
*/
public function render_tab_content() {
// Render the opening div
$this->render_opening_div_of_tab();
// Render the body of the tab
$api = $this->_registry->get( 'model.api.api-feeds' );
$api_signed = $api->is_signed();
$settings = $this->_registry->get( 'model.settings' );
$factory = $this->_registry->get(
'factory.html'
);
$has_feature = $api->has_subscription_active(
Ai1ec_Api_Features::CODE_IMPORT_FEEDS
);
$reached_limit = $api->subscription_has_reached_limit(
Ai1ec_Api_Features::CODE_IMPORT_FEEDS
);
$select2_cats = $factory->create_select2_multiselect(
array(
'name' => 'ai1ec_feed_category[]',
'id' => 'ai1ec_feed_category',
'use_id' => true,
'type' => 'category',
'placeholder' => __(
'Categories (optional)',
AI1EC_PLUGIN_NAME
)
),
get_terms(
'events_categories',
array(
'hide_empty' => false
)
)
);
$select2_tags = $factory->create_select2_input(
array( 'id' => 'ai1ec_feed_tags')
);
$loader = $this->_registry->get( 'theme.loader' );
$args = array(
'event_categories' => $select2_cats,
'event_tags' => $select2_tags,
'api_signed' => $api->is_signed(),
'has_feature' => $has_feature,
'reached_limit' => $reached_limit,
);
$import_feed = $loader->get_file(
'plugins/ics/import_feed.php',
$args,
true
);
$import_feed->render();
$this->render_closing_div_of_tab();
}
/**
* (non-PHPdoc)
*
* @see Ai1ec_Connector_Plugin::display_admin_notices()
*/
public function display_admin_notices() {
return;
}
/**
* (non-PHPdoc)
*
* @see Ai1ec_Connector_Plugin::run_uninstall_procedures()
*/
public function run_uninstall_procedures() {
}
/**
* add_ics_feed function
*
* Adds submitted ics feed to the database
*
* @return string JSON output
*
*/
public function add_ics_feed() {
}
public function handle_feeds_page_post() {
}
}

View File

@@ -0,0 +1,139 @@
<?php
/**
* The class which handles suggested feeds tab.
*
* @author Time.ly Network Inc.
* @since 2.4
*
* @package AI1EC
* @subpackage AI1EC.Calendar-feed
*/
class Ai1ecSuggestedConnectorPlugin extends Ai1ec_Connector_Plugin {
/**
* @var array
* title: The title of the tab and the title of the configuration section
* id: The id used in the generation of the tab
*/
protected $variables = array(
'id' => 'suggested'
);
public function get_tab_title() {
return Ai1ec_I18n::__( 'Discover Events' );
}
public function __construct( Ai1ec_Registry_Object $registry ) {
parent::__construct( $registry );
}
/**
* (non-PHPdoc)
*
* @see Ai1ec_Connector_Plugin::handle_feeds_page_post()
*/
public function handle_feeds_page_post() {
}
/**
* (non-PHPdoc)
*
* @see Ai1ec_Connector_Plugin::render_tab_content()
*/
public function render_tab_content() {
// Render the opening div
$this->render_opening_div_of_tab();
$loader = $this->_registry->get( 'theme.loader' );
$api = $this->_registry->get( 'model.api.api-feeds' );
$event_actions = $loader->get_file(
'plugins/suggested/event_actions.php',
array(),
true
);
$display_feeds = $loader->get_file(
'plugins/suggested/display_feeds.php',
array(
'event_actions' => $event_actions,
'api_signed' => $api->is_signed()
),
true
);
$display_feeds->render();
// Render the body of the tab
$this->render_closing_div_of_tab();
}
/**
* (non-PHPdoc)
*
* @see Ai1ec_Connector_Plugin::display_admin_notices()
*/
public function display_admin_notices() {
return;
}
/**
* Events search
*/
public function search_events() {
$api = $this->_registry->get( 'model.api.api-feeds' );
$events = $api->get_suggested_events();
$imported = $api->get_feed_subscriptions();
$loader = $this->_registry->get( 'theme.loader' );
$event_actions = $loader->get_file(
'plugins/suggested/event_actions.php',
array(),
true
);
if ( null === $events ) {
echo json_encode(
array(
'list' => '',
'total' => 0
)
);
exit( 0 );
}
$page_links = paginate_links( array(
'base' => add_query_arg( 'pagenum', '%#%' ),
'format' => '',
'prev_text' => __( '&laquo;', AI1EC_PLUGIN_NAME ),
'next_text' => __( '&raquo;', AI1EC_PLUGIN_NAME ),
'total' => $events->last_page,
'current' => $events->current_page
) );
$avatar_url = $loader->get_file(
'default-event-avatar.png',
array(),
false
)->get_url();
$feeds_list = $loader->get_file(
'plugins/suggested/feeds_list.php',
array(
'suggested_feeds' => $events->data,
'default_image' => $avatar_url,
'event_actions' => $event_actions,
'page_links' => $page_links
),
true
);
$feeds_list = array(
'list' => $feeds_list->get_content(),
'total' => $events->total,
'imported' => $imported
);
echo json_encode( $feeds_list );
exit( 0 );
}
/**
* (non-PHPdoc)
*
* @see Ai1ec_Connector_Plugin::run_uninstall_procedures()
*/
public function run_uninstall_procedures() {
}
}

View File

@@ -0,0 +1,66 @@
<?php
/**
* Calendar state container.
*
* @author Time.ly Network Inc.
* @since 2.2
*
* @package AI1EC
* @subpackage AI1EC.Lib.Calendar
*/
class Ai1ec_Calendar_State extends Ai1ec_Base {
/**
* Whether calendar is initializing router or not.
*
* @var bool
*/
private $_is_routing_initializing = false;
/**
* Whether Html render strategy should append content in the_content
* filter hook.
*
* @var bool
*/
private $_append_content = true;
/**
* Returns whether routing is during initialization phase or not.
*
* @return bool
*/
public function is_routing_initializing() {
return $this->_is_routing_initializing;
}
/**
* Sets state for routing initialization phase.
*
* @param bool $status State for initializing phase.
*/
public function set_routing_initialization( $status ) {
$this->_is_routing_initializing = $status;
}
/**
* Returns whether html render strategy should append content in the_content
* filter hook.
*
* @return bool
*/
public function append_content() {
return $this->_append_content;
}
/**
* Sets state for content appending in html renderer the_content hook.
* See Ai1ec_Render_Strategy_Html::append_content()
*
* @param bool $status Whether to append content or not.
*/
public function set_append_content( $status ) {
$this->_append_content = $status;
}
}

View File

@@ -0,0 +1,148 @@
<?php
/**
* Calendar state container.
*
* @author Time.ly Network Inc.
* @since 2.3
*
* @package AI1EC
* @subpackage AI1EC.Lib.Calendar
*/
class Ai1ec_Calendar_Updates extends Ai1ec_Base {
/**
* Primary update endpoint.
*
* @const string
*/
const PRIMARY_END_POINT = 'https://update.time.ly/update';
/**
* Alternative update endpoint.
*
* @const string
*/
const SECONDARY_END_POINT = 'https://checkout.time.ly/update';
/**
* Check updates and return additional info.
*
* @param mixed $transient_data Current transient data.
*
* @return mixed Modified transient data.
*/
public function check_updates( $transient_data ) {
if ( empty( $transient_data ) ) {
return $transient_data;
}
$updates = $this->_download_updates();
if ( empty( $updates ) ) {
return $transient_data;
}
$plugins = get_plugins();
foreach ( $updates as $plugin => $update_data ) {
/** @var $plugin_data array */
$plugin_data = isset( $plugins[$plugin] ) ? $plugins[$plugin] : null;
if (
empty( $plugin_data['Version'] ) ||
version_compare( $plugin_data['Version'], $update_data['new_version'], '>=' )
) {
continue;
}
$transient_data->response[$plugin] = (object) $update_data;
}
return $transient_data;
}
/**
* Get plugin data from retrieved and cached data.
*
* @param array $data Current data.
* @param string $action Action name.
* @param array|null $args Query arguments.
*
* @return mixed Plugin data.
*/
public function plugins_api_filter( $data, $action = '', $args = null ) {
/*
if (
'plugin_information' !== $action ||
empty( $args->slug ) ||
'all-in-one-event-calendar' !== substr( $args->slug, 0, 25 )
) {
return $data;
}
$update_data = get_site_transient( 'update_plugins' );
$plugin_identifier = $args->slug . '/' . $args->slug . '.php';
if ( empty( $update_data->response[$plugin_identifier] ) ) {
return $data;
}
return $update_data->response[$plugin_identifier];
*/
$updates = $this->_download_updates();
return false;
}
/**
* Clear updates related transients.
*
* @return void Method does not return.
*/
public function clear_transients() {
delete_site_transient( 'ai1ec_update_plugins' );
delete_site_transient( 'update_plugins' );
}
/**
* Download update info. Check local transient for cached data.
*
* @return array|mixed|null|object Update data.
*/
protected function _download_updates() {
$cached_updates = get_site_transient( 'ai1ec_update_plugins' );
if ( $cached_updates ) {
return $cached_updates;
}
// try first endpoint
$response = $this->_get_data_from_endpoint( self::PRIMARY_END_POINT );
if ( is_wp_error( $response ) ) {
$response = $this->_get_data_from_endpoint( self::SECONDARY_END_POINT );
}
if ( is_wp_error( $response ) ) {
return null;
}
$data = json_decode( wp_remote_retrieve_body( $response ), true );
set_site_transient( 'ai1ec_update_plugins', $data, 30 * MINUTE_IN_SECONDS );
return $data;
}
/**
* Get update data from given endpoint.
*
* @param string $endpoint Endpoint URI.
*
* @return array|WP_Error Request result.
*/
protected function _get_data_from_endpoint( $endpoint ) {
// Use ticketing token to check for subscriptions
$token = $this->_registry->get( 'model.api.api-registration' )->get_timely_token();
if ( null === $token ) {
$token = '';
}
$request = array(
'method' => 'GET',
'timeout' => 15,
'sslverify' => false
);
return wp_remote_request( $endpoint . '/' . $token, $request );
}
}

View File

@@ -0,0 +1,102 @@
<?php
/**
* Ai1ec_Captcha_Provider interface.
*
* @author Time.ly Network Inc.
* @since 2.2
*
* @package AI1EC
* @subpackage AI1EC.Captcha
*/
abstract class Ai1ec_Captcha_Provider extends Ai1ec_Base {
/**
* Settings object.
*
* @var Ai1ec_Settings
*/
protected $_settings = null;
/**
* Theme loader object.
*
* @var Ai1ec_Theme_Loader
*/
protected $_theme_loader = null;
/**
* Whether provider is configured or not.
*
* @var bool
*/
protected $_is_configured = null;
/**
* Constructor.
*
* @param Ai1ec_Registry_Object $registry
*
* @return Ai1ec_Captcha_Provider
*
* @throws Ai1ec_Bootstrap_Exception
*/
public function __construct( Ai1ec_Registry_Object $registry ) {
parent::__construct( $registry );
$this->_settings = $registry->get( 'model.settings' );
$this->_theme_loader = $registry->get( 'theme.loader' );
}
/**
* Returns settings array.
*
* @param bool $enable_rendering Whether setting HTML will be rendered or not.
*
* @return array Array of settings.
*/
abstract public function get_settings( $enable_rendering = true );
/**
* Returns captcha challenge.
*
* @return mixed
*/
abstract public function get_challenge();
/**
* Validates challenge.
*
* @param array Challenge response data.
*
* @return mixed
*/
abstract public function validate_challenge( array $data );
/**
* Returns provider name.
*
* @return string
*/
abstract public function get_name();
/**
* Returns whether provider is properly configured or not.
*
* @return bool
*/
public function is_configured() {
if ( null !== $this->_is_configured ) {
return $this->_is_configured;
}
$this->_is_configured = true;
foreach ( $this->get_settings() as $key => $setting ) {
$value = $this->_settings->get( $key );
if ( empty( $value ) ) {
$this->_is_configured = false;
break;
}
}
return $this->_is_configured;
}
}

View File

@@ -0,0 +1,132 @@
<?php
/**
* Nocaptcha provider.
*
* @author Time.ly Network Inc.
* @since 2.2
*
* @package AI1EC
* @subpackage AI1EC.
*/
class Ai1ec_Captcha_Nocaptcha_Provider extends Ai1ec_Captcha_Provider {
/**
* Returns settings array.
*
* @param bool $enable_rendering Whether setting HTML will be rendered or not.
*
* @return array Array of settings.
*/
public function get_settings( $enable_rendering = true ) {
return array(
'google_nocaptcha_public_key' => array(
'type' => 'string',
'version' => AI1ECFS_PLUGIN_NAME,
'renderer' => array(
'class' => 'input',
'tab' => 'extensions',
'item' => 'interactive',
'type' => 'normal',
'label' => __(
'reCAPTCHA V2 public key:',
AI1ECFS_PLUGIN_NAME
),
'condition' => $enable_rendering,
),
'value' => '',
),
'google_nocaptcha_private_key' => array(
'type' => 'string',
'version' => AI1ECFS_PLUGIN_NAME,
'renderer' => array(
'class' => 'input',
'tab' => 'extensions',
'item' => 'interactive',
'type' => 'normal',
'label' => __(
'reCAPTCHA V2 private key:',
AI1ECFS_PLUGIN_NAME
),
'condition' => $enable_rendering,
),
'value' => '',
),
);
}
/**
* Returns captcha challenge.
*
* @return mixed
*/
public function get_challenge() {
$args = array(
'nocaptcha_key' => $this->_settings->get(
'google_nocaptcha_public_key'
),
);
return $this->_theme_loader->get_file(
'captcha/nocaptcha/challenge.twig',
$args,
false
)->get_content();
}
/**
* Validates challenge.
*
* @param array Challenge response data.
*
* @return mixed
*/
public function validate_challenge( array $data ) {
$response['message'] = Ai1ec_I18n::__(
'Please try verifying you are human again.'
);
$response['success'] = false;
if ( empty( $data['g-recaptcha-response'] ) ) {
$response['message'] = Ai1ec_I18n::_(
'There was an error reading the human verification data. Please try again.'
);
$response['success'] = false;
}
$url = add_query_arg(
array(
'secret' => $this->_settings->get(
'google_nocaptcha_private_key'
),
'response' => $data['g-recaptcha-response'],
),
'https://www.google.com/recaptcha/api/siteverify'
);
$json_resp = wp_remote_get( $url );
if ( is_wp_error( $json_resp ) ) {
return $response;
}
$resp = json_decode( $json_resp['body'], true );
if (
isset( $resp['success'] ) &&
$resp['success']
) {
$response = array(
'success' => true,
);
}
return $response;
}
/**
* Returns provider name.
*
* @return string
*/
public function get_name() {
return 'Google reCAPTCHA V2';
}
}

View File

@@ -0,0 +1,126 @@
<?php
/**
* ReCaptcha provider.
*
* @author Time.ly Network Inc.
* @since 2.2
*
* @package AI1EC
* @subpackage AI1EC.Captcha.Provider
*/
class Ai1ec_Captcha_Recaptcha_Provider extends Ai1ec_Captcha_Provider {
/**
* Returns settings array.
*
* @param bool $enable_rendering Whether setting HTML will be rendered or not.
*
* @return array Array of settings.
*/
public function get_settings( $enable_rendering = true ) {
return array(
'google_recaptcha_public_key' => array(
'type' => 'string',
'version' => AI1ECFS_PLUGIN_NAME,
'renderer' => array(
'class' => 'input',
'tab' => 'extensions',
'item' => 'interactive',
'type' => 'normal',
'label' => __(
'reCAPTCHA public key:',
AI1ECFS_PLUGIN_NAME
),
'condition' => $enable_rendering,
),
'value' => '',
),
'google_recaptcha_private_key' => array(
'type' => 'string',
'version' => AI1ECFS_PLUGIN_NAME,
'renderer' => array(
'class' => 'input',
'tab' => 'extensions',
'item' => 'interactive',
'type' => 'normal',
'label' => __(
'reCAPTCHA private key:',
AI1ECFS_PLUGIN_NAME
),
'condition' => $enable_rendering,
),
'value' => '',
),
);
}
/**
* Returns captcha challenge.
*
* @return mixed
*/
public function get_challenge() {
$args = array(
'verification_words' => Ai1ec_I18n::__( 'Human verification' ),
'loading_recaptcha' => Ai1ec_I18n::__( 'Loading reCAPTCHA...' ),
'recaptcha_key' => $this->_settings->get(
'google_recaptcha_public_key'
),
);
return $this->_theme_loader->get_file(
'captcha/recaptcha/challenge.twig',
$args,
false
)->get_content();
}
/**
* Validates challenge.
*
* @param array Challenge response data.
*
* @return mixed
*/
public function validate_challenge( array $data ) {
$response = array( 'success' => true );
if (
empty( $data['recaptcha_challenge_field'] ) ||
empty( $data['recaptcha_response_field'] )
) {
$response['message'] = Ai1ec_I18n::_(
'There was an error reading the human verification data. Please try again.'
);
$response['success'] = false;
}
$remoteAddress = isset( $_SERVER['HTTP_REFERER'] ) ? $_SERVER['HTTP_REFERER'] : null;
require_once( AI1EC_VENDOR_PATH . 'recaptcha/recaptchalib.php' );
$resp = recaptcha_check_answer(
$this->_settings->get( 'google_recaptcha_private_key' ),
$remoteAddress,
$data['recaptcha_challenge_field'],
$data['recaptcha_response_field']
);
if ( ! $resp->is_valid ) {
$response['message'] = Ai1ec_I18n::__(
'Please try verifying you are human again.'
);
$response['success'] = false;
}
return $response;
}
/**
* Returns provider name.
*
* @return string
*/
public function get_name() {
return 'Google reCAPTCHA';
}
}

View File

@@ -0,0 +1,68 @@
<?php
/**
* Captcha providers handler class.
*
* @author Time.ly Network Inc.
* @since 2.2
*
* @package AI1EC
* @subpackage AI1EC.Captcha
*/
class Ai1ec_Captcha_Providers extends Ai1ec_Base {
/**
* List of available captcha providers.
*
* @var array
*/
protected $_providers = null;
/**
* Returns list of available providers.
*
* @return array List of providers.
*/
public function get_providers() {
if ( null !== $this->_providers ) {
return $this->_providers;
}
$built_in = array(
'Ai1ec_Captcha_Recaptcha_Provider',
'Ai1ec_Captcha_Nocaptcha_Provider',
);
$all_providers = apply_filters( 'ai1ec_captcha_providers', $built_in );
if ( empty( $all_providers ) ) {
return array();
}
$providers = array();
foreach ( $all_providers as $provider_class ) {
$provider = new $provider_class( $this->_registry );
if ( ! $provider instanceof Ai1ec_Captcha_Provider ) {
continue;
}
$providers[] = $provider;
}
return $providers;
}
/**
* Returns providers settings.
*
* @return array Providers settings.
*/
public function get_providers_as_settings() {
$all_providers = $this->get_providers();
$settings = array();
foreach ( $all_providers as $provider ) {
$settings[] = array(
'text' => $provider->get_name(),
'value' => get_class( $provider ),
'settings' => $provider->get_settings(),
);
}
return $settings;
}
}

View File

@@ -0,0 +1,96 @@
<?php
/**
* Helps Rendering clone html.
*
* @author Time.ly Network Inc.
* @since 2.0
*
* @package AI1EC
* @subpackage AI1EC.Clone
*/
class Ai1ec_Clone_Renderer_Helper extends Ai1ec_Base {
/**
* add clone bluk action in the dropdown
*
* @wp_hook admin_footer-edit.php
*/
public function duplicate_custom_bulk_admin_footer() {
$aco = $this->_registry->get( 'acl.aco' );
if ( true === $aco->are_we_editing_our_post() ) {
?>
<script type="text/javascript">
jQuery(document).ready(function() {
jQuery('<option>').val('clone').text('<?php _e( 'Clone', AI1EC_PLUGIN_NAME )?>').appendTo("select[name='action']");
jQuery('<option>').val('clone').text('<?php _e( 'Clone', AI1EC_PLUGIN_NAME )?>').appendTo("select[name='action2']");
});
</script>
<?php
}
}
/**
* Add the link to action list for post_row_actions
*
* @wp_hook post_row_action
*
*/
function ai1ec_duplicate_post_make_duplicate_link_row( $actions, $post ) {
if ( $post->post_type == "ai1ec_event" ) {
$actions['clone'] = '<a href="'.$this->ai1ec_duplicate_post_get_clone_post_link( $post->ID, 'display', false).'" title="'
. esc_attr(__("Make new copy of event", AI1EC_PLUGIN_NAME))
. '">' . __( 'Clone', AI1EC_PLUGIN_NAME ) . '</a>';
$actions['edit_as_new_draft'] = '<a href="' . $this->ai1ec_duplicate_post_get_clone_post_link( $post->ID ) . '" title="'
. esc_attr(__( 'Copy to a new draft', AI1EC_PLUGIN_NAME ))
. '">' . __( 'Clone to Draft', AI1EC_PLUGIN_NAME ) . '</a>';
}
return $actions;
}
/**
* Retrieve duplicate post link for post.
*
*
* @param int $id Optional. Post ID.
* @param string $context Optional, default to display. How to write the '&', defaults to '&amp;'.
* @param string $draft Optional, default to true
* @return string
*/
function ai1ec_duplicate_post_get_clone_post_link( $id = 0, $context = 'display', $draft = true ) {
if ( ! $post = get_post( $id ) ) {
return;
}
if ( $draft ) {
$action_name = "ai1ec_duplicate_post_save_as_new_post_draft";
} else {
$action_name = "ai1ec_duplicate_post_save_as_new_post";
}
if ( 'display' == $context ) {
$action = '?action=' . $action_name . '&amp;post=' . $post->ID;
} else {
$action = '?action=' . $action_name . '&post=' . $post->ID;
}
$post_type_object = get_post_type_object( $post->post_type );
if ( ! $post_type_object ) {
return;
}
return apply_filters(
'ai1ec_duplicate_post_get_clone_post_link',
wp_nonce_url(
ai1ec_admin_url( 'admin.php' . $action ),
'ai1ec_clone_' . $post->ID
),
$post->ID,
$context
);
}
}

View File

@@ -0,0 +1,117 @@
<?php
/**
* The abstract command class.
*
* @author Time.ly Network Inc.
* @since 2.0
*
* @package AI1EC
* @subpackage AI1EC.Command
*/
abstract class Ai1ec_Command {
/**
* @var Ai1ec_Registry_Object
*/
protected $_registry;
/**
* @var Ai1ec_Request_Parser
*/
protected $_request;
/**
* @var Ai1ec_Http_Response_Render_Strategy
*/
protected $_render_strategy;
/**
* Public constructor.
*
* @param Ai1ec_Registry_Object $registry
* @param Ai1ec_Request_Parser $request
*/
public function __construct(
Ai1ec_Registry_Object $registry,
Ai1ec_Request_Parser $request
) {
$this->_registry = $registry;
$this->_request = $request;
}
/**
* Gets parameters from the request object.
*
* @return array|boolean
*/
public function get_parameters() {
$plugin = $controller = $action = null;
$plugin = Ai1ec_Request_Parser::get_param( 'plugin', $plugin );
$controller = Ai1ec_Request_Parser::get_param( 'controller', $controller );
$action = Ai1ec_Request_Parser::get_param( 'action', $action );
if (
is_scalar( $plugin ) &&
(string)AI1EC_PLUGIN_NAME === (string)$plugin &&
null !== $controller &&
null !== $action
) {
return array(
'controller' => $controller,
'action' => $action
);
}
return false;
}
/**
* Execute the command.
*
* @return void
*/
public function execute() {
// Set the render strategy
$this->set_render_strategy( $this->_request );
// get the data from the concrete implementation
$data = $this->do_execute();
// render it.
$this->_render_strategy->render( $data );
}
/**
* Defines whether to stop execution of command loop or not.
*
* @return bool True or false.
*/
public function stop_execution() {
return false;
}
/**
* The abstract method concrete command must implement.
*
* Retrieve whats needed and returns it
*
* @return array
*/
abstract public function do_execute();
/**
* Returns whether this is the command to be executed.
*
* I handle the logi of execution at this levele, which is not usual for
* The front controller pattern, because othe extensions need to inject
* logic into the resolver ( oAuth or ics export for instance )
* and this seems to me to be the most logical way to do this.
*
* @return boolean
*/
abstract public function is_this_to_execute();
/**
* Sets the render strategy.
*
* @param Ai1ec_Request_Parser $request
*/
abstract public function set_render_strategy( Ai1ec_Request_Parser $request );
}

View File

@@ -0,0 +1,38 @@
<?php
/**
* The concrete command that sends sign up data to API
*
* @author Time.ly Network Inc.
* @since 2.4
*
* @package AI1EC
* @subpackage AI1EC.Command
*/
class Ai1ec_Command_Api_Ticketing_Signup extends Ai1ec_Command_Save_Abstract {
/* (non-PHPdoc)
* @see Ai1ec_Command::is_this_to_execute()
*/
public function do_execute() {
$api = $this->_registry->get( 'model.api.api-registration' );
if ( true === isset($_POST['ai1ec_signout']) && '1' === $_POST['ai1ec_signout'] ) {
$api->signout();
} else {
if ( '1' === $_POST['ai1ec_signing'] ) {
$api->signup();
} else {
$api->signin();
}
}
return array(
'url' => ai1ec_admin_url(
'edit.php?post_type=ai1ec_event&page=all-in-one-event-calendar-settings'
),
'query_args' => array(
'message' => ''
),
);
}
}

View File

@@ -0,0 +1,73 @@
<?php
/**
* The concrete command that change active theme.
*
* @author Time.ly Network Inc.
* @since 2.0
*
* @package AI1EC
* @subpackage AI1EC.Command
*/
class Ai1ec_Command_Change_Theme extends Ai1ec_Command {
/**
* Executes the command to change the active theme.
*
* NOTE: {@see self::is_this_to_execute} must return true for this command
* to execute; we can trust that input has been checked for injections.
*/
public function do_execute() {
// Update the active theme in the options table.
$stylesheet = preg_replace(
'|[^a-z_\-]+|i',
'',
$_GET['ai1ec_stylesheet']
);
$this->_registry->get( 'theme.loader' )->switch_theme( array(
'theme_root' => realpath( $_GET['ai1ec_theme_root'] ),
'theme_dir' => realpath( $_GET['ai1ec_theme_dir'] ),
'theme_url' => $_GET['ai1ec_theme_url'],
'stylesheet' => $stylesheet,
'legacy' => false
) );
// Return user to themes list page with success message.
return array(
'url' => ai1ec_admin_url(
'edit.php?post_type=ai1ec_event&page=all-in-one-event-calendar-themes'
),
'query_args' => array(
'activated' => 1
)
);
}
/* (non-PHPdoc)
* @see Ai1ec_Command_Save_Abstract::set_render_strategy()
*/
public function set_render_strategy( Ai1ec_Request_Parser $request ) {
$this->_render_strategy = $this->_registry->get(
'http.response.render.strategy.redirect'
);
}
/* (non-PHPdoc)
* @see Ai1ec_Command::is_this_to_execute()
*/
public function is_this_to_execute() {
if (
isset( $_GET['ai1ec_action'] ) &&
$_GET['ai1ec_action'] === 'activate_theme' &&
current_user_can( 'switch_ai1ec_themes' ) &&
is_dir( $_GET['ai1ec_theme_dir'] ) &&
is_dir( $_GET['ai1ec_theme_root'] )
) {
check_admin_referer(
'switch-ai1ec_theme_' . $_GET['ai1ec_stylesheet']
);
return true;
}
return false;
}
}

View File

@@ -0,0 +1,41 @@
<?php
/**
* The concrete command that compiles CSS.
*
* @author Time.ly Network Inc.
* @since 2.3
*
* @package AI1EC
* @subpackage AI1EC.Command
*/
class Ai1ec_Command_Check_Updates extends Ai1ec_Command {
/*
* (non-PHPdoc) @see Ai1ec_Command::is_this_to_execute()
*/
public function is_this_to_execute() {
return isset( $_GET['ai1ec_force_updates'] );
}
/* (non-PHPdoc)
* @see Ai1ec_Command::set_render_strategy()
*/
public function set_render_strategy( Ai1ec_Request_Parser $request ) {
$this->_render_strategy = $this->_registry->get(
'http.response.render.strategy.redirect'
);
}
/* (non-PHPdoc)
* @see Ai1ec_Command::do_execute()
*/
public function do_execute() {
$this->_registry->get( 'calendar.updates' )->clear_transients();
return array (
'url' => ai1ec_admin_url( 'plugins.php' ),
'query_args' => array ()
);
}
}

View File

@@ -0,0 +1,384 @@
<?php
/**
* The concrete command that clone events.
*
* @author Time.ly Network Inc.
* @since 2.0
*
* @package AI1EC
* @subpackage AI1EC.Command
*/
class Ai1ec_Command_Clone extends Ai1ec_Command {
/**
* @var array The posts that must be cloned
*/
protected $_posts = array();
/**
* @var bool Whether to redirect or not
*/
protected $_redirect = false;
/**
* The abstract method concrete command must implement.
*
* Retrieve whats needed and returns it
*
* @return array
*/
public function do_execute() {
$id = 0;
foreach ( $this->_posts as $post ) {
$id = $this->ai1ec_duplicate_post_create_duplicate(
$post['post'],
$post['status']
);
}
if ( true === $this->_redirect ) {
if ( '' === $post['status'] ) {
return array(
'url' => ai1ec_admin_url(
'edit.php?post_type=' . AI1EC_POST_TYPE
),
'query_args' => array()
);
} else {
return array(
'url' => ai1ec_admin_url(
'post.php?action=edit&post=' . $id
),
'query_args' => array()
);
}
}
// no redirect, just go on with the page
return array();
}
/**
* Returns whether this is the command to be executed.
*
* I handle the logi of execution at this levele, which is not usual for
* The front controller pattern, because othe extensions need to inject
* logic into the resolver ( oAuth or ics export for instance )
* and this seems to me to be the most logical way to do this.
*
* @return boolean
*/
public function is_this_to_execute() {
$current_action = $this->_registry->get(
'http.request'
)->get_current_action();
if (
current_user_can( 'edit_ai1ec_events' ) &&
'clone' === $current_action &&
! empty( $_REQUEST['post'] ) &&
! empty( $_REQUEST['_wpnonce'] ) &&
wp_verify_nonce( $_REQUEST['_wpnonce'], 'bulk-posts' )
) {
foreach ( $_REQUEST['post'] as $post_id ) {
$this->_posts[] = array(
'status' => '',
'post' => get_post( $post_id )
);
}
return true;
}
// other actions need the nonce to be verified
// duplicate single post
if (
$current_action === 'ai1ec_duplicate_post_save_as_new_post' &&
! empty( $_REQUEST['post'] )
) {
check_admin_referer( 'ai1ec_clone_'. $_REQUEST['post'] );
$this->_posts[] = array(
'status' => '',
'post' => get_post( $_REQUEST['post'] )
);
$this->_redirect = true;
return true;
}
// duplicate single post as draft
if (
$current_action === 'ai1ec_duplicate_post_save_as_new_post_draft' &&
! empty( $_REQUEST['post'] )
) {
check_admin_referer( 'ai1ec_clone_'. $_REQUEST['post'] );
$this->_posts[] = array(
'status' => 'draft',
'post' => get_post( $_REQUEST['post'] )
);
$this->_redirect = true;
return true;
}
return false;
}
/**
* Sets the render strategy.
*
* @param Ai1ec_Request_Parser $request
*/
public function set_render_strategy( Ai1ec_Request_Parser $request ) {
if ( true === $this->_redirect ) {
$this->_render_strategy = $this->_registry
->get( 'http.response.render.strategy.redirect' );
} else {
$this->_render_strategy = $this->_registry
->get( 'http.response.render.strategy.void' );
}
}
/**
* Create a duplicate from a posts' instance
*/
public function ai1ec_duplicate_post_create_duplicate( $post, $status = '' ) {
$post = get_post( $post );
$new_post_author = $this->_ai1ec_duplicate_post_get_current_user();
$new_post_status = $status;
if ( empty( $new_post_status ) ) {
$new_post_status = $post->post_status;
}
$new_post_status = $this->_get_new_post_status( $new_post_status );
$new_post = array(
'menu_order' => $post->menu_order,
'comment_status' => $post->comment_status,
'ping_status' => $post->ping_status,
'pinged' => $post->pinged,
'post_author' => $new_post_author->ID,
'post_content' => $post->post_content,
'post_date' => $post->post_date,
'post_date_gmt' => get_gmt_from_date( $post->post_date ),
'post_excerpt' => $post->post_excerpt,
'post_parent' => $post->post_parent,
'post_password' => $post->post_password,
'post_status' => $new_post_status,
'post_title' => $post->post_title,
'post_type' => $post->post_type,
'to_ping' => $post->to_ping,
);
$new_post_id = wp_insert_post( $new_post );
$edit_event_url = esc_attr(
ai1ec_admin_url( "post.php?post={$new_post_id}&action=edit" )
);
$message = sprintf(
__( '<p>The event <strong>%s</strong> was cloned succesfully. <a href="%s">Edit cloned event</a></p>', AI1EC_PLUGIN_NAME ),
$post->post_title,
$edit_event_url
);
$notification = $this->_registry->get( 'notification.admin' );
$notification->store( $message );
$this->_ai1ec_duplicate_post_copy_post_taxonomies( $new_post_id, $post );
$this->_ai1ec_duplicate_post_copy_attachments( $new_post_id, $post );
$this->_ai1ec_duplicate_post_copy_post_meta_info( $new_post_id, $post );
$api = $this->_registry->get( 'model.api.api-ticketing' );
$api->clear_event_metadata( $new_post_id );
if ( $this->_registry->get( 'acl.aco' )->is_our_post_type( $post ) ) {
try {
$old_event = $this->_registry->get( 'model.event', $post->ID );
$old_event->set( 'post_id', $new_post_id );
$old_event->set( 'post', null );
$old_event->set( 'ical_feed_url', null );
$old_event->set( 'ical_source_url', null );
$old_event->set( 'ical_organizer', null );
$old_event->set( 'ical_contact', null );
$old_event->set( 'ical_uid', null );
$old_event->save();
} catch ( Ai1ec_Event_Not_Found_Exception $exception ) {
/* ignore */
}
}
$meta_post = $this->_registry->get( 'model.meta-post' );
$meta_post->delete( $new_post_id, '_dp_original' );
$meta_post->add( $new_post_id, '_dp_original', $post->ID );
// If the copy gets immediately published, we have to set a proper slug.
if (
$new_post_status == 'publish' ||
$new_post_status == 'future'
) {
$post_name = wp_unique_post_slug(
$post->post_name,
$new_post_id,
$new_post_status,
$post->post_type,
$post->post_parent
);
$new_post = array();
$new_post['ID'] = $new_post_id;
$new_post['post_name'] = $post_name;
// Update the post into the database
wp_update_post( $new_post );
}
return $new_post_id;
}
/**
* Copy the meta information of a post to another post
*/
protected function _ai1ec_duplicate_post_copy_post_meta_info( $new_id, $post ) {
$post_meta_keys = get_post_custom_keys( $post->ID );
if ( empty( $post_meta_keys ) ) {
return;
}
foreach ( $post_meta_keys as $meta_key ) {
$meta_values = get_post_custom_values( $meta_key, $post->ID );
foreach ( $meta_values as $meta_value ) {
$meta_value = maybe_unserialize( $meta_value );
$meta_value = apply_filters(
'ai1ec_duplicate_post_meta_value',
$meta_value,
$meta_key,
$post,
$new_id
);
if ( null !== $meta_value ) {
add_post_meta( $new_id, $meta_key, $meta_value );
}
}
}
}
/**
* Copy the attachments
* It simply copies the table entries, actual file won't be duplicated
*/
protected function _ai1ec_duplicate_post_copy_attachments( $new_id, $post ) {
//if (get_option('ai1ec_duplicate_post_copyattachments') == 0) return;
// get old attachments
$attachments = get_posts(
array(
'post_type' => 'attachment',
'numberposts' => -1,
'post_status' => null,
'post_parent' => $post->ID,
)
);
// clone old attachments
foreach ( $attachments as $att ) {
$new_att_author = $this->_ai1ec_duplicate_post_get_current_user();
$new_att = array(
'menu_order' => $att->menu_order,
'comment_status' => $att->comment_status,
'guid' => $att->guid,
'ping_status' => $att->ping_status,
'pinged' => $att->pinged,
'post_author' => $new_att_author->ID,
'post_content' => $att->post_content,
'post_date' => $att->post_date,
'post_date_gmt' => get_gmt_from_date( $att->post_date ),
'post_excerpt' => $att->post_excerpt,
'post_mime_type' => $att->post_mime_type,
'post_parent' => $new_id,
'post_password' => $att->post_password,
'post_status' => $this->_get_new_post_status(
$att->post_status
),
'post_title' => $att->post_title,
'post_type' => $att->post_type,
'to_ping' => $att->to_ping,
);
$new_att_id = wp_insert_post( $new_att );
// get and apply a unique slug
$att_name = wp_unique_post_slug(
$att->post_name,
$new_att_id,
$att->post_status,
$att->post_type,
$new_id
);
$new_att = array();
$new_att['ID'] = $new_att_id;
$new_att['post_name'] = $att_name;
wp_update_post( $new_att );
}
}
/**
* Copy the taxonomies of a post to another post
*/
protected function _ai1ec_duplicate_post_copy_post_taxonomies( $new_id, $post ) {
$db = $this->_registry->get( 'dbi.dbi' );
if ( $db->are_terms_set() ) {
// Clear default category (added by wp_insert_post)
wp_set_object_terms( $new_id, NULL, 'category' );
$post_taxonomies = get_object_taxonomies( $post->post_type );
$taxonomies_blacklist = array();
$taxonomies = array_diff( $post_taxonomies, $taxonomies_blacklist );
foreach ( $taxonomies as $taxonomy ) {
$post_terms = wp_get_object_terms(
$post->ID,
$taxonomy,
array( 'orderby' => 'term_order' )
);
$terms = array();
for ( $i = 0; $i < count( $post_terms ); $i++ ) {
$terms[] = $post_terms[ $i ]->slug;
}
wp_set_object_terms( $new_id, $terms, $taxonomy );
}
}
}
/**
* Get the currently registered user
*/
protected function _ai1ec_duplicate_post_get_current_user() {
if ( function_exists( 'wp_get_current_user' ) ) {
return wp_get_current_user();
} else {
$db = $this->_registry->get( 'dbi.dbi' );
$query = $db->prepare(
'SELECT * FROM ' . $wpdb->users . ' WHERE user_login = %s',
$_COOKIE[ USER_COOKIE ]
);
$current_user = $db->get_results( $query );
return $current_user;
}
}
/**
* Get the status for `duplicate' post
*
* If user cannot publish post (event), and original post status is
* *publish*, then it will be duplicated with *pending* status.
* In other cases original status will remain.
*
* @param string $old_status Status of old post
*
* @return string Status for new post
*/
protected function _get_new_post_status( $old_status ) {
if (
'publish' === $old_status &&
! current_user_can( 'publish_ai1ec_events' )
) {
return 'pending';
}
return $old_status;
}
}

View File

@@ -0,0 +1,100 @@
<?php
/**
* The concrete command that compiles CSS.
*
* @author Time.ly Network Inc.
* @since 2.1
*
* @package AI1EC
* @subpackage AI1EC.Command
*/
class Ai1ec_Command_Compile_Core_Css extends Ai1ec_Command {
/*
* (non-PHPdoc) @see Ai1ec_Command::is_this_to_execute()
*/
public function is_this_to_execute() {
if ( isset( $_GET['ai1ec_compile_css'] ) &&
AI1EC_DEBUG
) {
return true;
}
return false;
}
/* (non-PHPdoc)
* @see Ai1ec_Command::set_render_strategy()
*/
public function set_render_strategy( Ai1ec_Request_Parser $request ) {
$this->_render_strategy = $this->_registry->get(
'http.response.render.strategy.void'
);
}
/* (non-PHPdoc)
* @see Ai1ec_Command::do_execute()
*/
public function do_execute() {
$message = $this->_process_files();
echo $message;
return Ai1ec_Http_Response_Helper::stop( 0 );
}
/**
* Returns calendar theme structure.
*
* @param string $stylesheet Calendar stylesheet. Expects one of
* ['vortex','plana','umbra','gamma'].
* @return array Calendar themes.
*
* @throws Ai1ec_Invalid_Argument_Exception
*/
protected function _get_theme( $stylesheet ) {
return $this->_registry->get(
'filesystem.misc'
)->build_theme_structure( $stylesheet );
}
/**
* Returns PHP code with hashmap array.
*
* @param $hashmap Array with compilation hashes.
*
* @return string PHP code.
*/
protected function _get_hashmap_array( $hashmap ) {
return '<?php return ' . var_export( $hashmap, true ) . ';';
}
protected function _process_files() {
$less = $frontend = $this->_registry->get( 'less.lessphp' );
$option = $this->_registry->get( 'model.option' );
$theme = $this->_get_theme( $_GET['theme'] );
if ( isset( $_GET['switch'] ) ) {
$option->delete( 'ai1ec_less_variables' );
$option->set( 'ai1ec_current_theme', $theme );
return 'Theme switched to "' . $theme['stylesheet'] . '".';
}
$css = $less->parse_less_files( null, true );
$hashmap = $less->get_less_hashmap();
$hashmap = $this->_get_hashmap_array( $hashmap );
$filename = $theme['theme_dir'] . DIRECTORY_SEPARATOR .
'css' . DIRECTORY_SEPARATOR . 'ai1ec_parsed_css.css';
$hashmap_file = $theme['theme_dir'] . DIRECTORY_SEPARATOR .
'less.sha1.map.php';
$css_written = file_put_contents( $filename, $css );
$hashmap_written = file_put_contents( $hashmap_file, $hashmap );
if (
false === $css_written ||
false === $hashmap_written
) {
return 'There has been an error writing theme CSS';
}
return 'Theme CSS compiled succesfully and written in ' .
$filename . ' and classmap stored in ' . $hashmap_file;
}
}

View File

@@ -0,0 +1,41 @@
<?php
/**
* (Re)compile themes for shipping.
*
* @author Time.ly Network Inc.
* @since 2.1
*
* @package AI1EC
* @subpackage AI1EC.Command
*/
class Ai1ec_Command_Compile_Themes extends Ai1ec_Command {
/*
* (non-PHPdoc) @see Ai1ec_Command::is_this_to_execute()
*/
public function is_this_to_execute() {
return (
AI1EC_DEBUG &&
isset( $_GET['ai1ec_recompile_templates'] )
);
}
/* (non-PHPdoc)
* @see Ai1ec_Command::set_render_strategy()
*/
public function set_render_strategy( Ai1ec_Request_Parser $request ) {
$this->_render_strategy = $this->_registry->get(
'http.response.render.strategy.void'
);
}
/* (non-PHPdoc)
* @see Ai1ec_Command::do_execute()
*/
public function do_execute() {
$this->_registry->get( 'theme.compiler' )->generate();
return Ai1ec_Http_Response_Helper::stop( 0 );
}
}

View File

@@ -0,0 +1,47 @@
<?php
/**
* The concrete command that disabel gzip.
*
* @author Time.ly Network Inc.
* @since 2.0
*
* @package AI1EC
* @subpackage AI1EC.Command
*/
class Ai1ec_Command_Disable_Gzip extends Ai1ec_Command {
/* (non-PHPdoc)
* @see Ai1ec_Command::is_this_to_execute()
*/
public function is_this_to_execute() {
if ( isset( $_GET['ai1ec_disable_gzip_compression'] ) ) {
check_admin_referer( 'ai1ec_disable_gzip_compression' );
return true;
}
return false;
}
/* (non-PHPdoc)
* @see Ai1ec_Command::do_execute()
*/
public function do_execute() {
$this->_registry->get( 'model.settings' )
->set( 'disable_gzip_compression', true );
return array(
'url' => ai1ec_admin_url( 'edit.php' ),
'query_args' => array(
'post_type' => 'ai1ec_event',
'page' => 'all-in-one-event-calendar-settings',
),
);
}
/* (non-PHPdoc)
* @see Ai1ec_Command::set_render_strategy()
*/
public function set_render_strategy( Ai1ec_Request_Parser $request ) {
$this->_render_strategy = $this->_registry->get(
'http.response.render.strategy.redirect'
);
}
}

View File

@@ -0,0 +1,159 @@
<?php
/**
* The concrete command that export events.
*
* @author Time.ly Network Inc.
* @since 2.0
*
* @package AI1EC
* @subpackage AI1EC.Command
*/
class Ai1ec_Command_Export_Events extends Ai1ec_Command {
/**
* @var string The name of the old exporter controller.
*/
const EXPORT_CONTROLLER = 'ai1ec_exporter_controller';
/**
* @var string The name of the old export method.
*/
const EXPORT_METHOD = 'export_events';
/**
* @var array Request parameters
*/
protected $_params;
/* (non-PHPdoc)
* @see Ai1ec_Command::is_this_to_execute()
*/
public function is_this_to_execute() {
$params = $this->get_parameters();
if ( false === $params ) {
return false;
}
if ( $params['action'] === self::EXPORT_METHOD &&
$params['controller'] === self::EXPORT_CONTROLLER ) {
$params['tag_ids'] = Ai1ec_Request_Parser::get_param(
'ai1ec_tag_ids',
false
);
$params['cat_ids'] = Ai1ec_Request_Parser::get_param(
'ai1ec_cat_ids',
false
);
$params['post_ids'] = Ai1ec_Request_Parser::get_param(
'ai1ec_post_ids',
false
);
$params['lang'] = Ai1ec_Request_Parser::get_param(
'lang',
false
);
$params['no_html'] = (bool)Ai1ec_Request_Parser::get_param(
'no_html',
false
);
$params['xml'] = (bool)Ai1ec_Request_Parser::get_param(
'xml',
false
);
$this->_params = $params;
return true;
}
return false;
}
/* (non-PHPdoc)
* @see Ai1ec_Command::set_render_strategy()
*/
public function set_render_strategy( Ai1ec_Request_Parser $request ) {
if ( isset( $_GET['xml']) ) {
$this->_render_strategy = $this->_registry->get(
'http.response.render.strategy.xcal'
);
} else {
$this->_render_strategy = $this->_registry->get(
'http.response.render.strategy.ical'
);
}
}
/* (non-PHPdoc)
* @see Ai1ec_Command::do_execute()
*/
public function do_execute() {
$ai1ec_cat_ids = $this->_params['cat_ids'];
$ai1ec_tag_ids = $this->_params['tag_ids'];
$ai1ec_post_ids = $this->_params['post_ids'];
if ( ! empty( $this->_params['lang'] ) ) {
$loc_helper = $this->_registry->get( 'p28n.wpml' );
$loc_helper->set_language( $this->_params['lang'] );
}
$args = array( 'do_not_export_as_calendar' => false );
$filter = array();
if ( $ai1ec_cat_ids ) {
$filter['cat_ids'] = Ai1ec_Primitive_Int::convert_to_int_list(
',',
$ai1ec_cat_ids
);
}
if ( $ai1ec_tag_ids ) {
$filter['tag_ids'] = Ai1ec_Primitive_Int::convert_to_int_list(
',',
$ai1ec_tag_ids
);
}
if ( $ai1ec_post_ids ) {
$args['do_not_export_as_calendar'] = true;
$filter['post_ids'] = Ai1ec_Primitive_Int::convert_to_int_list(
',',
$ai1ec_post_ids
);
}
$filter = apply_filters( 'ai1ec_export_filter', $filter );
$start = $this->_registry->get( 'date.time', '-3 years' );
$end = $this->_registry->get( 'date.time', '+3 years' );
$search = $this->_registry->get( 'model.search' );
$params = array(
'no_html' => $this->_params['no_html'],
'xml' => $this->_params['xml'],
);
$export_controller = $this->_registry->get(
'controller.import-export',
array( 'ics' ),
$params
);
$args['events'] = $this->unique_events(
$search->get_events_between( $start, $end, $filter )
);
$ics = $export_controller->export_events( 'ics', $args );
return array( 'data' => $ics );
}
/**
* Return unique events list.
*
* @param array $events List of Ai1ec_Event objects.
*
* @return array Unique Ai1ec_Events from input.
*/
public function unique_events( array $events ) {
$ids = array();
$output = array();
foreach ( $events as $event ) {
$id = (int)$event->get( 'post_id' );
if ( ! isset( $ids[$id] ) ) {
$output[] = $event;
$ids[$id] = true;
}
}
return $output;
}
}

View File

@@ -0,0 +1,83 @@
<?php
/**
* The concrete command that renders the calendar.
*
* @author Time.ly Network Inc.
* @since 2.0
*
* @package AI1EC
* @subpackage AI1EC.Command
*/
class Ai1ec_Command_Render_Calendar extends Ai1ec_Command {
/**
* @var string
*/
protected $_request_type;
/* (non-PHPdoc)
* @see Ai1ec_Command::is_this_to_execute()
*/
public function is_this_to_execute() {
$settings = $this->_registry->get( 'model.settings' );
$calendar_page_id = $settings->get( 'calendar_page_id' );
if ( empty( $calendar_page_id ) ) {
return false;
}
$localization = $this->_registry->get( 'p28n.wpml' );
$aco = $this->_registry->get( 'acl.aco' );
$page_ids_to_match = array( $calendar_page_id ) +
$localization->get_translations_of_page(
$calendar_page_id
);
foreach ( $page_ids_to_match as $page_id ) {
if ( is_page( $page_id ) ) {
$this->_request->set_current_page( $page_id );
if ( ! post_password_required( $page_id ) ) {
return true;
}
}
}
return false;
}
/* (non-PHPdoc)
* @see Ai1ec_Command::set_render_strategy()
*/
public function set_render_strategy( Ai1ec_Request_Parser $request ) {
try {
$this->_request_type = $request->get( 'request_type' );
$this->_render_strategy = $this->_registry->get(
'http.response.render.strategy.' . $this->_request_type
);
} catch ( Ai1ec_Bootstrap_Exception $e ) {
$this->_request_type = 'html';
$this->_render_strategy = $this->_registry->get(
'http.response.render.strategy.' . $this->_request_type
);
}
}
/* (non-PHPdoc)
* @see Ai1ec_Command::do_execute()
*/
public function do_execute() {
// get the calendar html
$calendar = $this->_registry->get( 'view.calendar.page' );
$css = $this->_registry->get( 'css.frontend' )
->add_link_to_html_for_frontend();
$js = $this->_registry->get( 'controller.javascript' )
->load_frontend_js( true );
return array(
'data' => $calendar->get_content( $this->_request ),
'callback' => Ai1ec_Request_Parser::get_param(
'callback',
null
),
'caller' => 'calendar',
);
}
}

View File

@@ -0,0 +1,75 @@
<?php
/**
* The concrete command that renders the event.
*
* @author Time.ly Network Inc.
* @since 2.0
*
* @package AI1EC
* @subpackage AI1EC.Command
*/
class Ai1ec_Command_Render_Event extends Ai1ec_Command_Render_Calendar {
/* (non-PHPdoc)
* @see Ai1ec_Command::is_this_to_execute()
*/
public function is_this_to_execute() {
global $post;
if (
! isset( $post ) ||
! is_object( $post ) ||
(int)$post->ID <= 0 ||
post_password_required( $post->ID )
) {
return false;
}
return $this->_registry->get( 'acl.aco' )->is_our_post_type();
}
/* (non-PHPdoc)
* @see Ai1ec_Command::do_execute()
*/
public function do_execute() {
// If not on the single event page, return nothing.
if ( ! is_single() ) {
return array(
'data' => '',
'is_event' => true,
);
}
// Else proceed with rendering valid event. Fetch all relevant details.
$instance = -1;
if ( isset( $_REQUEST['instance_id'] ) ) {
$instance = (int)$_REQUEST['instance_id'];
}
$event = $this->_registry->get(
'model.event',
get_the_ID(),
$instance
);
$event_page = $this->_registry->get( 'view.event.single' );
$footer_html = $event_page->get_footer( $event );
$css = $this->_registry->get( 'css.frontend' )
->add_link_to_html_for_frontend();
$js = $this->_registry->get( 'controller.javascript' )
->load_frontend_js( false );
// If requesting event by JSON (remotely), return fully rendered event.
if ( 'html' !== $this->_request_type ) {
return array(
'data' => array(
'html' => $event_page->get_full_article( $event, $footer_html )
),
'callback' => Ai1ec_Request_Parser::get_param( 'callback', null ),
);
}
// Else return event details as components.
return array(
'data' => $event_page->get_content( $event ),
'is_event' => true,
'footer' => $footer_html,
);
}
}

View File

@@ -0,0 +1,154 @@
<?php
/**
* The command resolver class that handles command.
*
* @author Time.ly Network Inc.
* @since 2.0
*
* @package AI1EC
* @subpackage AI1EC.Command
*/
class Ai1ec_Command_Resolver {
/**
* @var array The available commands.
*/
private $_commands = array();
/**
* @var Ai1ec_Registry_Object The Object registry.
*/
private $_registry;
/**
* @var Ai1ec_Request_Parser The Request parser.
*/
private $_request;
/**
* Public constructor
*
* @param Ai1ec_Registry_Object $registry
* @param Ai1ec_Request_Parser $request
*
* @return void
*/
public function __construct(
Ai1ec_Registry_Object $registry,
Ai1ec_Request_Parser $request
) {
$this->add_command(
$registry->get(
'command.compile-themes', $request
)
);
$this->add_command(
$registry->get(
'command.disable-gzip', $request
)
);
$this->add_command(
$registry->get(
'command.export-events', $request
)
);
$this->add_command(
$registry->get(
'command.render-event', $request
)
);
$this->add_command(
$registry->get(
'command.render-calendar', $request
)
);
$this->add_command(
$registry->get(
'command.change-theme', $request
)
);
$this->add_command(
$registry->get(
'command.save-settings',
$request,
array(
'action' => 'ai1ec_save_settings',
'nonce_action' => Ai1ec_View_Admin_Settings::NONCE_ACTION,
'nonce_name' => Ai1ec_View_Admin_Settings::NONCE_NAME,
)
)
);
$this->add_command(
$registry->get(
'command.save-theme-options',
$request,
array(
'action' => 'ai1ec_save_theme_options',
'nonce_action' => Ai1ec_View_Theme_Options::NONCE_ACTION,
'nonce_name' => Ai1ec_View_Theme_Options::NONCE_NAME,
)
)
);
$this->add_command(
$registry->get(
'command.api-ticketing-signup',
$request,
array(
'action' => 'ai1ec_api_ticketing_signup',
'nonce_action' => Ai1ec_View_Tickets::NONCE_ACTION,
'nonce_name' => Ai1ec_View_Tickets::NONCE_NAME,
)
)
);
$this->add_command(
$registry->get(
'command.clone', $request
)
);
$this->add_command(
$registry->get(
'command.compile-core-css', $request
)
);
if (
is_admin() &&
current_user_can( 'activate_plugins' )
) {
$this->add_command(
$registry->get(
'command.check-updates', $request
)
);
}
$request->parse();
$this->_registry = $registry;
$this->_request = $request;
}
/**
* Add a command.
*
* @param Ai1ec_Command $command
*
* @return Ai1ec_Comment_Resolver Self for calls chaining
*/
public function add_command( Ai1ec_Command $command ) {
$this->_commands[] = $command;
return $this;
}
/**
* Return the command to execute or false.
*
* @return Ai1ec_Command|null
*/
public function get_commands() {
$commands = array();
foreach ( $this->_commands as $command ) {
if ( $command->is_this_to_execute() ) {
$commands[] = $command;
}
}
return $commands;
}
}

View File

@@ -0,0 +1,74 @@
<?php
/**
* The abstract command that save something in the admin.
*
* @author Time.ly Network Inc.
* @since 2.0
*
* @package AI1EC
* @subpackage AI1EC.Command
*/
abstract class Ai1ec_Command_Save_Abstract extends Ai1ec_Command {
protected $_controller = 'front';
protected $_action;
protected $_nonce_name;
protected $_nonce_action;
/**
* Public constructor, set the strategy according to the type.
*
* @param Ai1ec_Registry_Object $registry
* @param Ai1ec_Request_Parser $request
*/
public function __construct(
Ai1ec_Registry_Object $registry,
Ai1ec_Request_Parser $request,
array $args
) {
parent::__construct( $registry, $request );
if ( ! is_array( $args['action'] ) ) {
$args['action'] = array(
$args['action'] => true,
);
}
$this->_action = $args['action'];
$this->_nonce_action = $args['nonce_action'];
$this->_nonce_name = $args['nonce_name'];
}
/* (non-PHPdoc)
* @see Ai1ec_Command::is_this_to_execute()
*/
public function is_this_to_execute() {
$params = $this->get_parameters();
if ( false === $params ) {
return false;
}
if ( $params['controller'] === $this->_controller &&
isset( $this->_action[$params['action']] ) ) {
$pass = wp_verify_nonce(
$_POST[$this->_nonce_name],
$this->_nonce_action
);
if ( ! $pass ) {
wp_die( "Failed security check" );
}
return true;
}
return false;
}
/* (non-PHPdoc)
* @see Ai1ec_Command::set_render_strategy()
*/
public function set_render_strategy( Ai1ec_Request_Parser $request ) {
$this->_render_strategy = $this->_registry->get(
'http.response.render.strategy.redirect'
);
}
}

View File

@@ -0,0 +1,175 @@
<?php
/**
* The concrete command that save settings.
*
* @author Time.ly Network Inc.
* @since 2.0
*
* @package AI1EC
* @subpackage AI1EC.Command
*/
class Ai1ec_Command_Save_Settings extends Ai1ec_Command_Save_Abstract {
/* (non-PHPdoc)
* @see Ai1ec_Command::do_execute()
*/
public function do_execute() {
$settings = $this->_registry->get( 'model.settings' );
$options = $settings->get_options();
$_POST['default_tags_categories'] = (
isset( $_POST['default_tags_categories_default_categories'] ) ||
isset( $_POST['default_tags_categories_default_tags'] )
);
// set some a variable to true to trigger the saving.
$_POST['enabled_views'] = true;
// let other plugin modify the post
$_POST = apply_filters( 'ai1ec_before_save_settings', $_POST );
foreach ( $options as $name => $data ) {
$value = null;
if ( isset( $_POST[$name] ) ) {
// if a validator is pecified, use it.
if ( isset( $data['renderer']['validator'] ) ) {
$validator = $this->_registry->get(
'validator.' . $data['renderer']['validator'],
$_POST[$name]
);
try {
$value = $validator->validate();
} catch ( Ai1ec_Value_Not_Valid_Exception $e ) {
// don't save
continue;
}
} else {
switch ( $data['type'] ) {
case 'bool':
$value = true;
break;
case 'int':
$value = (int)$_POST[$name];
break;
case 'string':
$value = (string)$_POST[$name];
break;
case 'array':
$method = '_handle_saving_' . $name;
$value = null;
if ( method_exists( $this, $method ) ) {
$value = $this->$method();
}
$value = apply_filters(
'ai1ec' . $method,
$value,
$_REQUEST
);
break;
case 'mixed':
$method = '_handle_saving_' . $name;
$value = null;
if ( method_exists( $this, $method ) ) {
$value = $this->$method( $_POST[$name] );
}
$value = apply_filters(
'ai1ec' . $method,
$value,
$_REQUEST
);
break;
case 'wp_option': // set the corresponding WP option
$this->_registry->get( 'model.option' )
->set( $name, $_POST[$name], true );
$value = (string)$_POST[$name];
}
}
} else {
if ( isset( $data['type'] ) && 'bool' === $data['type'] ) {
$value = false;
}
}
if ( null !== $value ) {
$settings->set( $name, stripslashes_deep( $value ) );
}
}
$new_options = $settings->get_options();
// let extension manipulate things if needed.
do_action( 'ai1ec_settings_updated', $options, $new_options );
$settings->persist();
$api = $this->_registry->get( 'model.api.api-registration' );
$api->check_settings( true );
return array(
'url' => ai1ec_admin_url(
'edit.php?post_type=ai1ec_event&page=all-in-one-event-calendar-settings'
),
'query_args' => array(
'updated' => 1
)
);
}
/**
* Handle saving enabled_views.
*
* @return array
*/
protected function _handle_saving_enabled_views() {
$settings = $this->_registry->get( 'model.settings' );
$enabled_views = $settings->get( 'enabled_views' );
foreach ( $enabled_views as $view => &$options ) {
$options['enabled'] = isset( $_POST['view_' . $view . '_enabled'] );
$options['default'] = isset( $_POST['default_calendar_view'] )
? $_POST['default_calendar_view'] === $view
: false;
$options['enabled_mobile'] =
isset( $_POST['view_' . $view . '_enabled_mobile'] );
$options['default_mobile'] =
isset( $_POST['default_calendar_view_mobile'] ) &&
$_POST['default_calendar_view_mobile'] === $view;
}
return $enabled_views;
}
/**
* Handle saving default_tag_categories option
*
* @return array
*/
protected function _handle_saving_default_tags_categories() {
return array(
'tags' => isset( $_POST['default_tags_categories_default_tags'] ) ?
$_POST['default_tags_categories_default_tags'] :
array(),
'categories' => isset( $_POST['default_tags_categories_default_categories'] ) ?
$_POST['default_tags_categories_default_categories'] :
array(),
);
}
/**
* Creates the calendar page if a string is passed.
*
* @param int|string $calendar_page
*
* @return int
*/
protected function _handle_saving_calendar_page_id( $calendar_page ) {
if (
! is_numeric( $calendar_page ) &&
preg_match( '#^__auto_page:(.*?)$#', $calendar_page, $matches )
) {
return wp_insert_post(
array(
'post_title' => $matches[1],
'post_type' => 'page',
'post_status' => 'publish',
'comment_status' => 'closed'
)
);
} else {
return (int)$calendar_page;
}
}
}

View File

@@ -0,0 +1,70 @@
<?php
/**
* The concrete command that save theme options.
*
* @author Time.ly Network Inc.
* @since 2.0
*
* @package AI1EC
* @subpackage AI1EC.Command
*/
class Ai1ec_Command_Save_Theme_Options extends Ai1ec_Command_Save_Abstract {
/* (non-PHPdoc)
* @see Ai1ec_Command::is_this_to_execute()
*/
public function do_execute() {
$variables = array();
// Handle updating of theme options.
if ( isset( $_POST[Ai1ec_View_Theme_Options::SUBMIT_ID] ) ) {
$_POST = stripslashes_deep( $_POST );
$lessphp = $this->_registry->get( 'less.lessphp' );
$variables = $lessphp->get_saved_variables();
foreach ( $variables as $variable_name => $variable_params ) {
if ( isset( $_POST[$variable_name] ) ) {
// Avoid problems for those who are foolish enough to leave php.ini
// settings at their defaults, which has magic quotes enabled.
if ( get_magic_quotes_gpc() ) {
$_POST[$variable_name] = stripslashes( $_POST[$variable_name] );
}
if (
Ai1ec_Less_Variable_Font::CUSTOM_FONT === $_POST[$variable_name]
) {
$_POST[$variable_name] = $_POST[$variable_name .
Ai1ec_Less_Variable_Font::CUSTOM_FONT_ID_SUFFIX];
}
// update the original array
$variables[$variable_name]['value'] = $_POST[$variable_name];
}
}
$_POST = add_magic_quotes( $_POST );
}
// Handle reset of theme options.
elseif ( isset( $_POST[Ai1ec_View_Theme_Options::RESET_ID] ) ) {
$option = $this->_registry->get( 'model.option' );
$option->delete( 'ai1ec_less_variables' );
$option->delete( 'ai1ec_render_css' );
do_action( 'ai1ec_reset_less_variables' );
}
$css = $this->_registry->get( 'css.frontend' );
$css->update_variables_and_compile_css(
$variables,
isset(
$_POST[Ai1ec_View_Theme_Options::RESET_ID]
)
);
return array(
'url' => ai1ec_admin_url(
'edit.php?post_type=ai1ec_event&page=all-in-one-event-calendar-edit-css'
),
'query_args' => array(),
);
}
}

View File

@@ -0,0 +1,133 @@
<?php
/**
* Theme backward compatibility check.
*
* @author Time.ly Network Inc.
* @since 2.2
*
* @package AI1EC
* @subpackage AI1EC.Lib.Compatibility
*/
class Ai1ec_Compatibility_Check extends Ai1ec_Base {
/**
* @var null|bool Calculated response.
*/
protected $_use_backward_compatibility = null;
/**
* Returns whether calendar is using backward compatibility or not.
*
* @return bool|null Result
*
* @throws Ai1ec_Bootstrap_Exception
*/
public function use_backward_compatibility() {
if ( null === $this->_use_backward_compatibility ) {
$this->_use_backward_compatibility = (
AI1EC_THEME_COMPATIBILITY_FER &&
! $this->_registry->get(
'model.settings'
)->get( 'ai1ec_use_frontend_rendering', false )
);
}
return $this->_use_backward_compatibility;
}
/**
* Observes settings changes.
*
* If setting ai1ec_use_frontend_rendering is changed and set to true
* perfoms theme check.
*
* Checks if Date format was changed, and update dates to the new format
*
* @param array $old_options Old options array.
* @param array $new_options New options array.
*
* @return void Method does not return.
*
* @throws Ai1ec_Bootstrap_Exception
*/
public function ai1ec_settings_observer( $old_options, $new_options ) {
// Date format change checker
$old_date_format_value = isset( $old_options['input_date_format'] )
? $old_options['input_date_format']['value']
: null;
$new_date_format_value = isset( $new_options['input_date_format'] )
? $new_options['input_date_format']['value']
: null;
if (
null !== $old_date_format_value&&
null !== $new_date_format_value &&
$old_date_format_value !== $new_date_format_value
) {
// Get "Default calendar start date"
$exact_date = isset( $old_options['exact_date'] )
? $old_options['exact_date']['value']
: '';
if ( '' !== $exact_date ) {
$date_system = $this->_registry->get( 'date.system' );
// Change "Default calendar start date" format
$new_exact_date = $date_system->convert_date_format(
$exact_date,
$old_date_format_value,
$new_date_format_value
);
// Save new value
$settings = $this->_registry->get( 'model.settings' );
$settings->set( 'exact_date', $new_exact_date );
}
}
// Frontend rendering checker
$old_value = isset( $old_options['ai1ec_use_frontend_rendering'] )
? (bool)$old_options['ai1ec_use_frontend_rendering']['value']
: null;
$new_value = isset( $new_options['ai1ec_use_frontend_rendering'] )
? (bool)$new_options['ai1ec_use_frontend_rendering']['value']
: null;
if (
$old_value === $new_value ||
! $new_value
) {
return;
}
if ( $this->is_current_theme_outside_core() ) {
$this->_registry->get( 'notification.admin' )->store(
Ai1ec_I18n::__( 'You have turned on Frontend Rendering and you are using a custom calendar theme. If your theme does not support Frontend Rendering, your calendar may not work correctly.' ),
'error',
0,
array( Ai1ec_Notification_Admin::RCPT_ADMIN ),
true
);
}
}
/**
* Returns whether current calendar theme is located under core directory
* or not.
*
* @return bool Result
*
* @throws Ai1ec_Bootstrap_Exception
*/
public function is_current_theme_outside_core() {
$option = $this->_registry->get( 'model.option' );
$cur_theme = $option->get( 'ai1ec_current_theme', array() );
$theme_root = dirname( AI1EC_DEFAULT_THEME_ROOT );
return (
isset( $cur_theme['theme_root'] ) &&
(
$theme_root !== dirname( $cur_theme['theme_root'] ) &&
false === strpos(
$cur_theme['theme_root'],
'all-in-one-event-calendar-saas-theme'
)
)
);
}
}

View File

@@ -0,0 +1,50 @@
<?php
/**
* Command line compatibility options.
*
* @author Time.ly Network, Inc.
* @since 2.1
* @package Ai1EC
* @subpackage Ai1EC.Compatibility
*/
class Ai1ec_Compatibility_Cli {
/**
* @var bool Whereas current session is command line.
*/
protected $_is_cli = false;
/**
* Check current SAPI.
*
* @return void
*/
public function __construct() {
$this->_is_cli = 'cli' === php_sapi_name();
}
/**
* Check if running command line session.
*
* @return bool Yes/No
*/
public function is_cli() {
return $this->_is_cli;
}
/**
* Disable DB debug when in command line session.
*
* @param bool $debug Current value.
*
* @return bool Optionally modified value.
*/
public function disable_db_debug( $debug ) {
if ( $this->_is_cli ) {
return false;
}
return $debug;
}
}

View File

@@ -0,0 +1,69 @@
<?php
/**
* Memory related methods.
*
* @author Time.ly Network Inc.
* @since 2.1
*
* @package AI1EC
* @subpackage AI1EC.Lib
*/
class Ai1ec_Compatibility_Memory extends Ai1ec_Base {
/**
* Checks if there is enough available free memory.
*
* @param string $required_limit String memory value i.e '24M'
*
* @return bool True or false.
*/
public function check_available_memory( $required_limit = 0 ) {
if ( 0 === $required_limit ) {
return true;
}
$required = $this->_string_to_bytes( $required_limit );
$limit = $this->_string_to_bytes( ini_get( 'memory_limit' ) );
$used = $this->get_usage();
return ( $limit - $used ) >= $required;
}
/**
* Returns current memory usage if available - otherwise 0.
*
* @return int Memory usage.
*/
public function get_usage() {
if ( is_callable( 'memory_get_usage' ) ) {
return memory_get_usage();
}
return 0;
}
/**
* Converts string value to int.
*
* @param string $v String value.
*
* @return int Number.
*/
protected function _string_to_bytes( $v ) {
$letter = substr( $v, -1 );
$value = (int)substr( $v, 0, -1 );
$powers = array(
'K' => 10,
'M' => 20,
'G' => 30,
'T' => 40,
'P' => 50,
);
$multiplier = 1;
if ( isset( $powers[$letter] ) ) {
$multiplier = pow( 2, $powers[$letter] );
}
if ( 1 === $multiplier ) {
return (int)$v;
}
return $value * $multiplier;
}
}

View File

@@ -0,0 +1,142 @@
<?php
/**
* Wrapper for all the output buffer calls (ob_*)
*/
class Ai1ec_Compatibility_OutputBuffer extends Ai1ec_Base {
/**
* Wrap the ob_end_flush() method:
* Flush (send) the output buffer and turn off output buffering
*
* @return bool Returns TRUE on success or FALSE on failure
*/
public function end_flush() {
return ob_end_flush();
}
/**
* Wrap the ob_get_contents() method:
* Return the contents of the output buffer
*
* @retrun string This will return the contents of the output buffer or
* FALSE, if output buffering isn't active.
*/
public function get_contents() {
return ob_get_contents();
}
/**
* Wrap the ob_get_level() method:
* Returns the nesting level of the output buffering mechanism.
*
* @return int Returns the level of nested output buffering handlers or zero
* if output buffering is not active.
*/
public function get_level() {
return ob_get_level();
}
/**
* Wrap the ob_start() method: turn output buffering on.
*
* @param callback $output_callback Method to be called on finish.
* @param int $chunk_size Buffer size limite.
* @param int|bool|null $flags Control performable operations.
*
* @return bool Returns TRUE on success or FALSE on failure.
*/
public function start(
$output_callback = null,
$chunk_size = 0,
$flags = null
) {
if ( 'ob_gzhandler' === $output_callback && $this->is_zlib_active() ) {
$output_callback = null; // do not compress again
}
if ( null === $flags ) {
if ( defined( 'PHP_OUTPUT_HANDLER_STDFLAGS' ) ) {
$flags = PHP_OUTPUT_HANDLER_STDFLAGS;
} else {
$flags = true;
}
}
return ob_start( $output_callback, $chunk_size, $flags );
}
/**
* Gzip the content if possible.
*
* @param string $string
*/
public function gzip_if_possible( $string ) {
$gzip = $this->_registry->get( 'http.request' )->client_use_gzip();
// only use output buffering for gzip.
if ( $gzip ) {
$this->start( 'ob_gzhandler' );
header( 'Content-Encoding: gzip' );
}
echo $string;
if ( $gzip ) {
$this->end_flush();
}
}
/**
* Check if zlib compression is activated.
*
* @return bool Activation status.
*/
public function is_zlib_active() {
$zlib = ini_get( 'zlib.output_compression' );
if ( 'off' !== strtolower( $zlib ) && ! empty( $zlib ) ) {
return true;
}
return false;
}
/**
* Wrap ob_end_clean() and check the zip level to avoid crashing:
* Clean (erase) the output buffer and turn off output buffering
*
* @return bool Returns TRUE on success or FALSE on failure
*/
public function end_clean() {
return ob_end_clean();
}
/**
* Handle the closing of the object buffer when more then one object buffer
* is opened. This cause an error if it's not correctly handled
*
* @return bool Returns TRUE on success or FALSE on failure
*/
public function end_clean_all() {
if ( ini_get( 'zlib.output_compression' ) ) {
return false;
}
$level = $this->get_level();
$success = true;
while ( $level ) {
$this->end_clean();
$new_level = $this->get_level();
if ( $new_level === $level ) {
$success = false;
break;
}
$level = $new_level;
}
return $success;
}
/**
* Wrap the ob_get_clean() method:
* Gets the current buffer contents and delete current output buffer.
*
* @return string Returns the contents of the output buffer and end output
* buffering. If output buffering isn't active then FALSE is returned.
*/
public function get_clean(){
return ob_get_clean();
}
}

View File

@@ -0,0 +1,100 @@
<?php
/**
* Execution guard.
*
* Guards process execution for multiple runs at the same moment of time.
*
* @author Time.ly Network, Inc.
* @since 2.0
* @package Ai1EC
* @subpackage Ai1EC.Compatibility
*/
class Ai1ec_Compatibility_Xguard extends Ai1ec_Base {
/**
* Return time of last acquisition.
*
* If execution guard with that name was never acquired it returns 0 (zero).
* If acquisition fails it returns false.
*
* @param string $name Name of guard to be acquired.
* @param int $timeout Timeout, how long lock is held after acquisition.
*
* @return bool Success to acquire lock for given period.
*/
public function acquire( $name, $timeout = 86400 ) {
$name = $this->safe_name( $name );
$dbi = $this->_registry->get( 'dbi.dbi' );
$entry = array(
'time' => time(),
'pid' => getmypid(),
);
$table = $dbi->get_table_name( 'options' );
$dbi->query( 'START TRANSACTION' );
$query = $dbi->prepare(
'SELECT option_value FROM ' . $table .
' WHERE option_name = %s',
$name
);
$prev = $dbi->get_var( $query );
if ( ! empty( $prev ) ) {
$prev = json_decode( $prev, true );
}
if (
! empty( $prev ) &&
( (int)$prev['time'] + (int)$timeout ) >= $entry['time']
) {
$dbi->query( 'ROLLBACK' );
return false;
}
$query = '';
if ( empty( $prev ) ) {
$query = 'INSERT INTO';
} else {
$query = 'UPDATE';
}
$query .= ' `' . $table . '` SET `option_name` = %s, `option_value` = %s, `autoload` = 0';
if ( ! empty( $prev ) ) {
$query .= ' WHERE `option_name` = %s';
}
$query = $dbi->prepare( $query, $name, json_encode( $entry ), $name );
$success = $dbi->query( $query );
if ( ! $success ) {
$dbi->query( 'ROLLBACK' );
return false;
}
$dbi->query( 'COMMIT' );
return true;
}
/**
* Method release logs execution guard release phase.
*
* @param string $name Name of acquisition.
*
* @return bool Not expected to fail.
*/
public function release( $name ) {
return false !== $this->_registry->get( 'dbi.dbi' )->delete(
'options',
array( 'option_name' => $this->safe_name( $name ) ),
array( '%s' )
);
}
/**
* Prepare safe file names.
*
* @param string $name Name of acquisition
*
* @return string Actual safeguard name to use.
*/
protected function safe_name( $name ) {
$name = preg_replace( '/[^A-Za-z_0-9\-]/', '_', $name );
$name = trim( preg_replace( '/_+/', '_', $name ), '_' );
$name = 'ai1ec_xlock_' . $name;
return substr( $name, 0, 50 );
}
}

View File

@@ -0,0 +1,87 @@
<?php
/**
* Content filtering.
*
* Guards process execution for multiple runs at the same moment of time.
*
* @author Time.ly Network, Inc.
* @since 2.1
* @package Ai1EC
* @subpackage Ai1EC.Content
*/
class Ai1ec_Content_Filters extends Ai1ec_Base {
/**
* Stored original the_content filters.
* @var array
*/
protected $_filters_the_content = array();
/**
* Flag if filters are cleared.
* @var bool
*/
protected $_filters_the_content_cleared = false;
/**
* Clears all the_content filters excluding few defaults.
*
* @global array $wp_filter
*
* @return Ai1ec_Content_Filters This class.
*/
public function clear_the_content_filters() {
global $wp_filter;
if ( $this->_filters_the_content_cleared ) {
return $this;
}
if ( isset( $wp_filter['the_content'] ) ) {
$this->_filters_the_content = $wp_filter['the_content'];
}
remove_all_filters( 'the_content' );
add_filter( 'the_content', 'wptexturize' );
add_filter( 'the_content', 'convert_smilies' );
add_filter( 'the_content', 'convert_chars' );
add_filter( 'the_content', 'wpautop' );
$this->_filters_the_content_cleared = true;
return $this;
}
/**
* Restores the_content filters.
*
* @global array $wp_filter
*
* @return Ai1ec_Content_Filters This class.
*/
public function restore_the_content_filters() {
global $wp_filter;
if (
! $this->_filters_the_content_cleared ||
empty( $this->_filters_the_content )
) {
return $this;
}
$wp_filter['the_content'] = $this->_filters_the_content;
return $this;
}
/**
* Check if event edit page should display "Move to Trash" button.
*
* @param array $allcaps An array of all the user's capabilities.
* @param array $caps Actual capabilities for meta capability.
*
* @return array Capabilities or empty array.
*/
public function display_trash_link( $allcaps, $caps ) {
if (
isset( $_GET['instance'] ) &&
in_array( 'delete_published_ai1ec_events', $caps )
) {
return array();
}
return $allcaps;
}
}

View File

@@ -0,0 +1,111 @@
<?php
/**
* The cookie Data Transfer Object.
*
* @author Time.ly Network Inc.
* @since 2.0
*
* @package AI1EC
* @subpackage AI1EC.Cookie
*/
class Ai1ec_Cookie_Present_Dto {
/**
* @var boolean
*/
private $is_cookie_set_for_calendar_page = false;
/**
* @var boolean
*/
private $is_cookie_set_for_shortcode = false;
/**
* @var array
*/
private $shortcode_cookie;
/**
* @var string
*/
private $calendar_cookie;
/**
* @var boolean
*/
private $is_a_cookie_set_for_this_page = false;
/**
* @return the $is_a_cookie_set_for_this_page
*/
public function get_is_a_cookie_set_for_this_page() {
return $this->is_a_cookie_set_for_this_page;
}
/**
* @param boolean $is_a_cookie_set_for_this_page
*/
public function set_is_a_cookie_set_for_this_page(
$is_a_cookie_set_for_this_page
) {
$this->is_a_cookie_set_for_this_page = $is_a_cookie_set_for_this_page;
}
/**
* @return boolean the $is_calendar_page
*/
public function get_is_cookie_set_for_calendar_page() {
return $this->is_cookie_set_for_calendar_page;
}
/**
* @return boolean the $is_cookie_set
*/
public function get_is_cookie_set_for_shortcode() {
return $this->is_cookie_set_for_shortcode;
}
/**
* @return array the $shortcode_cookie
*/
public function get_shortcode_cookie() {
return $this->shortcode_cookie;
}
/**
* @return string the $calendar_cookie
*/
public function get_calendar_cookie() {
return $this->calendar_cookie;
}
/**
* @param boolean $is_calendar_page
*/
public function set_is_cookie_set_for_calendar_page( $is_cookie_set_for_calendar_page ) {
$this->is_cookie_set_for_calendar_page = $is_cookie_set_for_calendar_page;
}
/**
* @param boolean $is_cookie_set
*/
public function set_is_cookie_set_for_shortcode( $is_cookie_set ) {
$this->is_cookie_set_for_shortcode = $is_cookie_set;
}
/**
* @param multitype: $shortcode_cookie
*/
public function set_shortcode_cookie( $shortcode_cookie ) {
$this->shortcode_cookie = $shortcode_cookie;
}
/**
* @param string $calendar_cookie
*/
public function set_calendar_cookie( $calendar_cookie ) {
$this->calendar_cookie = $calendar_cookie;
}
}

View File

@@ -0,0 +1,70 @@
<?php
/**
* An utility class for cookies.
*
* @author Time.ly Network Inc.
* @since 2.0
*
* @package AI1EC
* @subpackage AI1EC.Cookie
*/
class Ai1ec_Cookie_Utility extends Ai1ec_Base {
static public $types = array(
'cat_ids',
'tag_ids',
'auth_ids',
);
/**
* Check if a cookie is set for the current page
*
* @return Ai1ec_Cookie_Present_Dto
*/
public function is_cookie_set_for_current_page() {
$cookie_dto = $this->_registry->get( 'cookie.dto' );
$settings = $this->_registry->get( 'model.settings' );
$calendar_url = get_page_link( $settings->get( 'calendar_page_id' ) );
$requested_page_url = Ai1ec_Wp_Uri_Helper::get_current_url( true );
$cookie_set = isset( $_COOKIE['ai1ec_saved_filter'] );
if( false !== $cookie_set ) {
$cookie = json_decode( stripslashes( $_COOKIE['ai1ec_saved_filter'] ), true );
if (
$calendar_url === $requested_page_url &&
isset( $cookie['calendar_page'] ) &&
$cookie['calendar_page'] !== $calendar_url
) {
$cookie_dto->set_calendar_cookie( $cookie['calendar_page'] );
$cookie_dto->set_is_cookie_set_for_calendar_page( true );
$cookie_dto->set_is_a_cookie_set_for_this_page( true );
} else if ( isset( $cookie[$requested_page_url] ) ) {
$cookie_dto->set_shortcode_cookie( $cookie[$requested_page_url] );
$cookie_dto->set_is_cookie_set_for_shortcode( true );
$cookie_dto->set_is_a_cookie_set_for_this_page( true );
} else if (
// we must make the is_page( $ai1ec_settings->calendar_page_id ) for a really edge case
// when for example the calendar page is http://localhost/wordpress_pro/?page_id=1
// and the requested page is http://localhost/wordpress_pro/?page_id=1234
strpos( $requested_page_url, $calendar_url ) === 0 &&
isset( $cookie['calendar_page'] ) &&
is_page( $settings->get( 'calendar_page_id' ) )
) {
// This is the case after a redirect from the calendar page
$cookie_dto->set_is_a_cookie_set_for_this_page( true );
$cookie_dto->set_calendar_cookie( $cookie['calendar_page'] );
}
}
return $cookie_dto;
}
/**
* Returns path for cookies.
*
* @return string
*/
public function get_path_for_cookie() {
$parsed = parse_url( ai1ec_site_url() );
return isset( $parsed['path'] ) ? $parsed['path'] : '/';
}
}

View File

@@ -0,0 +1,117 @@
<?php
/**
* The class which handles Admin CSS.
*
* @author Time.ly Network Inc.
* @since 2.0
*
* @package AI1EC
* @subpackage AI1EC.Css
*/
class Ai1ec_Css_Admin extends Ai1ec_Base {
/**
* Enqueue any scripts and styles in the admin side, depending on context.
*
* @wp_hook admin_enqueue_scripts
*
*/
public function admin_enqueue_scripts( $hook_suffix ) {
$settings = $this->_registry->get( 'model.settings' );
$enqueuables = array(
'widgets.php' => array(
array( 'style', 'widget.css', ),
),
'edit-tags.php' => array(
array( 'style', 'colorpicker.css', ),
array( 'style', 'bootstrap.min.css', ),
array( 'style', 'taxonomies.css', ),
),
'term.php' => array(
array( 'style', 'colorpicker.css', ),
array( 'style', 'bootstrap.min.css', ),
array( 'style', 'taxonomies.css', ),
),
$settings->get( 'settings_page' ) => array(
array( 'script', 'common', ),
array( 'script', 'wp-lists', ),
array( 'script', 'postbox', ),
array( 'style', 'settings.css', ),
array( 'style', 'bootstrap.min.css', ),
),
$settings->get( 'feeds_page' ) => array(
array( 'script', 'common', ),
array( 'script', 'wp-lists', ),
array( 'script', 'postbox', ),
array( 'style', 'settings.css', ),
array( 'style', 'bootstrap.min.css', ),
array( 'style', 'plugins/plugins-common.css', ),
),
$settings->get( 'less_variables_page' ) => array(
array( 'style', 'settings.css', ),
array( 'style', 'bootstrap.min.css', ),
array( 'style', 'bootstrap_colorpicker.css', ),
),
);
if ( isset( $enqueuables[$hook_suffix] ) ) {
return $this->process_enqueue( $enqueuables[$hook_suffix] );
}
$aco = $this->_registry->get( 'acl.aco' );
$post_pages = array( 'post.php' => true, 'post-new.php' => true );
if (
isset( $post_pages[$hook_suffix] ) ||
$aco->are_we_editing_our_post()
) {
return $this->process_enqueue(
array(
array( 'style', 'bootstrap.min.css', ),
array( 'style', 'add_new_event.css', ),
)
);
}
}
/**
* Enqueue scripts and styles.
*
* @param array $item_list List of scripts/styles to enqueue.
*
* @return bool Always true
*/
public function process_enqueue( array $item_list ) {
foreach ( $item_list as $item ) {
if ( 'script' === $item[0] ) {
wp_enqueue_script( $item[1] );
} else {
wp_enqueue_style(
$this->gen_style_hook( $item[1] ),
AI1EC_ADMIN_THEME_CSS_URL . $item[1],
array(),
AI1EC_VERSION
);
}
}
return true;
}
/**
* Generate a style hook for use with WordPress.
*
* @param string $script Name of enqueable script.
*
* @return string Hook to use with WordPress.
*/
public function gen_style_hook( $script ) {
return 'ai1ec_' . preg_replace(
'|[^a-z]+|',
'_',
basename( $script, '.css' )
);
}
}

View File

@@ -0,0 +1,376 @@
<?php
/**
* The class which handles Frontend CSS.
*
* @author Time.ly Network Inc.
* @since 2.0
*
* @package AI1EC
* @subpackage AI1EC.Css
*/
class Ai1ec_Css_Frontend extends Ai1ec_Base {
const QUERY_STRING_PARAM = 'ai1ec_render_css';
// This is for testing purpose, set it to AI1EC_PARSE_LESS_FILES_AT_EVERY_REQUEST value.
const PARSE_LESS_FILES_AT_EVERY_REQUEST = AI1EC_PARSE_LESS_FILES_AT_EVERY_REQUEST;
const KEY_FOR_PERSISTANCE = 'ai1ec_parsed_css';
/**
* @var Ai1ec_Persistence_Context
*/
private $persistance_context;
/**
* @var Ai1ec_Less_Lessphp
*/
private $lessphp_controller;
/**
* @var Ai1ec_Option
*/
private $db_adapter;
/**
* @var Ai1ec_Template_Adapter
*/
private $template_adapter;
/**
* Possible paths/url for file cache
*
* @var array
*/
protected $_cache_paths = array();
/**
* @var array which have been checked and are not writable
*/
protected $_folders_not_writable = array();
public function __construct(
Ai1ec_Registry_Object $registry
) {
parent::__construct( $registry );
$this->_cache_paths[] = array(
'path' => AI1EC_CACHE_PATH,
'url' => AI1EC_CACHE_URL
);
if ( apply_filters( 'ai1ec_check_static_dir', true ) ) {
$filesystem = $this->_registry->get( 'filesystem.checker' );
$wp_static_folder = $filesystem->get_ai1ec_static_dir_if_available();
if ( '' !== $wp_static_folder ) {
$this->_cache_paths[] = array(
'path' => $wp_static_folder,
'url' => content_url() . '/uploads/ai1ec_static/'
);
}
}
$this->persistance_context = $this->_registry->get(
'cache.strategy.persistence-context',
self::KEY_FOR_PERSISTANCE,
$this->_cache_paths,
true
);
if ( ! $this->persistance_context->is_file_cache() ) {
/* @TODO: move this to Settings -> Advanced -> Cache */
}
$this->lessphp_controller = $this->_registry->get( 'less.lessphp' );
$this->db_adapter = $this->_registry->get( 'model.option' );
}
/**
*
* Get if file cache is enabled
* @return boolean
*/
public function is_file_cache_enabled() {
return $this->persistance_context->is_file_cache();
}
/**
* Get folders which are not writable
*
* @return array
*/
public function get_folders_not_writable() {
return $this->_folders_not_writable;
}
/**
* Renders the css for our frontend.
*
* Sets etags to avoid sending not needed data
*/
public function render_css() {
header( 'HTTP/1.1 200 OK' );
header( 'Content-Type: text/css', true, 200 );
// Aggressive caching to save future requests from the same client.
$etag = '"' . md5( __FILE__ . $_GET[self::QUERY_STRING_PARAM] ) . '"';
header( 'ETag: ' . $etag );
$max_age = 31536000;
$time_sys = $this->_registry->get( 'date.system' );
header(
'Expires: ' .
gmdate(
'D, d M Y H:i:s',
$time_sys->current_time() + $max_age
) .
' GMT'
);
header( 'Cache-Control: public, max-age=' . $max_age );
if (
empty( $_SERVER['HTTP_IF_NONE_MATCH'] ) ||
$etag !== stripslashes( $_SERVER['HTTP_IF_NONE_MATCH'] )
) {
// compress data if possible
$this->_registry->get( 'compatibility.ob' )
->gzip_if_possible( $this->get_compiled_css() );
} else {
// Not modified!
status_header( 304 );
}
// We're done!
Ai1ec_Http_Response_Helper::stop( 0 );
}
/**
*
* @param string $css
* @throws Ai1ec_Cache_Write_Exception
*/
public function update_persistence_layer( $css ) {
$filename = $this->persistance_context->write_data_to_persistence( $css );
$this->db_adapter->set(
'ai1ec_filename_css',
isset( $filename['file'] ) ? $filename['file'] : false,
true
);
$this->save_less_parse_time( $filename['url'] );
}
/**
* Get the url to retrieve the css
*
* @return string
*/
public function get_css_url() {
// get what's saved. I t could be false, a int or a string.
// if it's false or a int, use PHP to render CSS
$saved_par = $this->db_adapter->get( self::QUERY_STRING_PARAM );
// if it's empty it's a new install probably. Return static css.
// if it's numeric, just consider it a new install
if ( empty( $saved_par ) ) {
$theme = $this->_registry->get(
'model.option'
)->get( 'ai1ec_current_theme' );
return Ai1ec_Http_Response_Helper::remove_protocols(
apply_filters(
'ai1ec_frontend_standard_css_url',
$theme['theme_url'] . '/css/ai1ec_parsed_css.css'
)
);
}
if ( is_numeric( $saved_par ) ) {
if ( $this->_registry->get( 'model.settings' )->get( 'render_css_as_link' ) ) {
$time = (int) $saved_par;
$template_helper = $this->_registry->get( 'template.link.helper' );
return Ai1ec_Http_Response_Helper::remove_protocols(
add_query_arg(
array( self::QUERY_STRING_PARAM => $time, ),
trailingslashit( ai1ec_get_site_url() )
)
);
} else {
add_action( 'wp_head', array( $this, 'echo_css' ) );
return '';
}
}
// otherwise return the string
return Ai1ec_Http_Response_Helper::remove_protocols(
$saved_par
);
}
/**
* Create the link that will be added to the frontend
*/
public function add_link_to_html_for_frontend() {
$url = $this->get_css_url();
if ( '' !== $url && ! is_admin() ) {
wp_enqueue_style( 'ai1ec_style', $url, array(), AI1EC_VERSION );
}
}
public function echo_css() {
echo '<style>';
echo $this->get_compiled_css();
echo '</style>';
}
/**
* Invalidate the persistence layer only after a successful compile of the
* LESS files.
*
* @param array $variables LESS variable array to use
* @param boolean $update_persistence Whether the persist successful compile
*
* @return boolean Whether successful
*/
public function invalidate_cache(
array $variables = null,
$update_persistence = false
) {
if ( ! $this->lessphp_controller->is_compilation_needed( $variables ) ) {
$this->_registry->get(
'model.option'
)->delete( 'ai1ec_render_css' );
return true;
}
$notification = $this->_registry->get( 'notification.admin' );
if (
! $this->_registry->get(
'compatibility.memory'
)->check_available_memory( AI1EC_LESS_MIN_AVAIL_MEMORY )
) {
$message = sprintf(
Ai1ec_I18n::__(
'CSS compilation failed because you don\'t have enough free memory (a minimum of %s is needed). Your calendar will not render or function properly without CSS. Please read <a href="https://time.ly/document/user-guide/getting-started/pre-sale-questions/">this article</a> to learn how to increase your PHP memory limit.'
),
AI1EC_LESS_MIN_AVAIL_MEMORY
);
$notification->store(
$message,
'error',
1,
array( Ai1ec_Notification_Admin::RCPT_ADMIN ),
true
);
return;
}
try {
// Try to parse the css
$css = $this->lessphp_controller->parse_less_files( $variables );
// Reset the parse time to force a browser reload of the CSS, whether we are
// updating persistence or not. Do it here to be sure files compile ok.
$this->save_less_parse_time();
if ( $update_persistence ) {
$this->update_persistence_layer( $css );
} else {
$this->persistance_context->delete_data_from_persistence();
}
} catch ( Ai1ec_Cache_Write_Exception $e ) {
// This means successful during parsing but problems persisting the CSS.
$message = '<p>' . Ai1ec_I18n::__( "The LESS file compiled correctly but there was an error while saving the generated CSS to persistence." ) . '</p>';
$notification->store( $message, 'error' );
return false;
} catch ( Exception $e ) {
// An error from lessphp.
$message = sprintf(
Ai1ec_I18n::__( '<p><strong>There was an error while compiling CSS.</strong> The message returned was: <em>%s</em></p>' ),
$e->getMessage()
);
$notification->store( $message, 'error', 1 );
return false;
}
return true;
}
/**
* Update the less variables on the DB and recompile the CSS
*
* @param array $variables
* @param boolean $resetting are we resetting or updating variables?
*/
public function update_variables_and_compile_css( array $variables, $resetting ) {
$no_parse_errors = $this->invalidate_cache( $variables, true );
$notification = $this->_registry->get( 'notification.admin' );
if ( $no_parse_errors ) {
$this->db_adapter->set(
Ai1ec_Less_Lessphp::DB_KEY_FOR_LESS_VARIABLES,
$variables
);
if ( true === $resetting ) {
$message = sprintf(
'<p>' . Ai1ec_I18n::__(
"Theme options were successfully reset to their default values. <a href='%s'>Visit site</a>"
) . '</p>',
ai1ec_get_site_url()
);
} else {
$message = sprintf(
'<p>' .Ai1ec_I18n::__(
"Theme options were updated successfully. <a href='%s'>Visit site</a>"
) . '</p>',
ai1ec_get_site_url()
);
}
$notification->store( $message );
}
}
/**
* Try to get the CSS from cache.
* If it's not there re-generate it and save it to cache
* If we are in preview mode, recompile the css using the theme present in the url.
*
*/
public function get_compiled_css() {
try {
// If we want to force a recompile, we throw an exception.
if( self::PARSE_LESS_FILES_AT_EVERY_REQUEST === true ) {
throw new Ai1ec_Cache_Not_Set_Exception();
}else {
// This throws an exception if the key is not set
$css = $this->persistance_context->get_data_from_persistence();
return $css;
}
} catch ( Ai1ec_Cache_Not_Set_Exception $e ) {
$css = $this->lessphp_controller->parse_less_files();
try {
$this->update_persistence_layer( $css );
return $css;
} catch ( Ai1ec_Cache_Write_Exception $e ) {
if ( ! self::PARSE_LESS_FILES_AT_EVERY_REQUEST ) {
$this->_registry->get( 'notification.admin' )
->store(
sprintf(
__(
'Your CSS is being compiled on every request, which causes your calendar to perform slowly. The following error occurred: %s',
AI1EC_PLUGIN_NAME
),
$e->getMessage()
),
'error',
2,
array( Ai1ec_Notification_Admin::RCPT_ADMIN ),
true
);
}
// If something is really broken, still return the css.
// This means we parse it every time. This should never happen.
return $css;
}
}
}
/**
* Save the path to the CSS file or false to load standard CSS
*/
private function save_less_parse_time( $data = false ) {
$to_save = is_string( $data ) ?
$data :
$this->_registry->get( 'date.system' )->current_time();
$this->db_adapter->set(
self::QUERY_STRING_PARAM,
$to_save,
true
);
}
}

View File

@@ -0,0 +1,220 @@
<?php
/**
* A model to manage database changes
*
* @author Time.ly Network Inc.
* @since 2.0
*
* @package AI1EC
* @subpackage AI1EC.Database
*/
class Ai1ec_Database_Applicator extends Ai1ec_Base {
/**
* @var Ai1ec_Dbi Instance of wpdb object
*/
protected $_db = NULL;
/**
* @var Ai1ec_Database Instance of Ai1ec_Database object
*/
protected $_database = NULL;
/**
* Constructor
*
* Initialize object, by storing instance of `wpdb` in local variable
*
* @return void Constructor does not return
*/
public function __construct( Ai1ec_Registry_Object $registry ) {
parent::__construct( $registry );
$this->_db = $registry->get( 'dbi.dbi' );
$this->_database = $registry->get( 'database.helper' );
}
/**
* remove_instance_duplicates method
*
* Remove duplicate instances, from `event_instances` table
*
* @param int $depth Private argument, denoting number of iterations to
* try, before reverting to slow approach
*
* @return bool Success
*/
public function remove_instance_duplicates( $depth = 5 ) {
$use_field = 'id';
if ( $depth < 0 ) {
$use_field = 'post_id';
}
$table = $this->_table( 'event_instances' );
if ( false === $this->_database->table_exists( $table ) ) {
return true;
}
$duplicates = $this->find_duplicates(
$table,
$use_field,
array( 'post_id', 'start' )
);
$count = count( $duplicates );
if ( $count > 0 ) {
$sql_query = 'DELETE FROM ' . $table .
' WHERE ' . $use_field . ' IN ( ' .
implode( ', ', $duplicates ) . ' )';
$this->_db->query( $sql_query );
}
if ( 'post_id' === $use_field ) { // slow branch
$event_instance_model = $this->_registry->get(
'model.event.instance'
);
foreach ( $duplicates as $post_id ) {
try {
$event_instance_model->recreate(
$this->_registry->get( 'model.event', $post_id )
);
} catch ( Ai1ec_Exception $excpt ) {
// discard any internal errors
}
}
} else if ( $count > 0 ) { // retry
return $this->remove_instance_duplicates( --$depth );
}
return true;
}
/**
* find_duplicates method
*
* Find a list of duplicates in table, given search key and groupping fields
*
* @param string $table Name of table, to search duplicates in
* @param string $primary Column, to return values for
* @param array $group List of fields, to group values on
*
* @return array List of primary field values
*/
public function find_duplicates( $table, $primary, array $group ) {
$sql_query = '
SELECT
MIN( {{primary}} ) AS dup_primary -- pop oldest
FROM {{table}}
GROUP BY {{group}}
HAVING COUNT( {{primary}} ) > 1
';
$sql_query = str_replace(
array(
'{{table}}',
'{{primary}}',
'{{group}}',
),
array(
$this->_table( $table ),
$this->_escape_column( $primary ),
implode(
', ',
array_map( array( $this, '_escape_column' ), $group )
),
),
$sql_query
);
$result = $this->_db->get_col( $sql_query );
return $result;
}
/**
* Check list of tables for consistency.
*
* @return array List of inconsistencies.
*/
public function check_db_consistency_for_date_migration() {
$db_migration = $this->_registry->get( 'database.datetime-migration' );
/* @var $db_migration Ai1ecdm_Datetime_Migration */
$tables = $db_migration->get_tables();
if ( ! is_array( $tables ) ) {
return array();
}
// for date migration purposes we can assume
// that all columns need to be the same type
$info = array();
foreach( $tables as $t_name => $t_columns ) {
if ( count( $t_columns ) < 2 ) {
continue;
}
$tbl_error = $this->_check_single_table(
$t_name,
$db_migration->get_columns( $t_name ),
$t_columns
);
if ( null !== $tbl_error ) {
$info[] = $tbl_error;
}
}
return $info;
}
/**
* Check if single table columns are the same type.
*
* @param string $t_name Table name for details purposes.
* @param array $db_cols Columns from database.
* @param array $t_columns Columns to check from DDL.
*
* @return string|null Inconsistency description, if any.
*/
protected function _check_single_table(
$t_name,
array $db_cols,
array $t_columns
) {
$type = null;
foreach ( $db_cols as $c_field => $c_type ) {
if ( ! in_array( $c_field, $t_columns ) ) {
continue;
}
if ( null === $type ) {
$type = strtolower( $c_type );
}
if ( strtolower( $c_type ) !== $type ) {
return sprintf(
Ai1ec_I18n::__(
'Date columns in table %s have different types.'
),
$t_name
);
}
}
return null;
}
/**
* Get fully qualified table name, to use in queries.
*
* @param string $table Name of table, to convert.
*
* @return string Qualified table name.
*/
protected function _table( $table ) {
$prefix = $this->_db->get_table_name( 'ai1ec_' );
if ( substr( $table, 0, strlen( $prefix ) ) !== $prefix ) {
$table = $prefix . $table;
}
return $table;
}
/**
* _escape_column method
*
* Escape column, enquoting it in MySQL specific characters
*
* @param string $name Name of column to quote
*
* @return string Escaped column name
*/
protected function _escape_column( $name ) {
return '`' . $name . '`';
}
}

View File

@@ -0,0 +1,473 @@
<?php
/**
* The date-time migration utility layer.
*
* @author Time.ly Network Inc.
* @since 2.0
*
* @package AI1EC
* @subpackage AI1EC.Database
*/
class Ai1ecdm_Datetime_Migration {
/**
* @var wpdb Instance of wpdb or it's extension.
*/
protected $_dbi = null;
/**
* @var array List of tables to be processed.
*/
protected $_tables = array();
/**
* @var array Map of indices on selected tables.
*/
protected $_indices = array();
/**
* @var string Table suffix used in migration process.
*/
protected $_table_suffix = '_dt_ui_mig';
/**
* @var string Column suffix used in data transformation.
*/
protected $_column_suffix = '_transformation';
/**
* Output debug statements.
*
* @var mixed $arg1 Number of arguments to output.
*
* @return bool True when debug is in action.
*/
static public function debug( /** polymorphic arg list **/ ) {
if ( ! defined( 'AI1EC_DEBUG' ) || ! AI1EC_DEBUG ) {
return false;
}
$argv = func_get_args();
foreach ( $argv as $value ) {
echo '<pre class="timely-debug">',
'<small>', microtime( true ), '</small>', "\n";
var_export( $value );
echo '</pre>';
}
return true;
}
/**
* Acquire references of global variables and define non-scalar values.
*
* @return void
*/
public function __construct( Ai1ec_Registry_Object $registry ) {
$this->_dbi = $registry->get( 'dbi.dbi' );
$this->_tables = array(
$this->_dbi->get_table_name( 'ai1ec_events' ) => array(
'start',
'end',
),
$this->_dbi->get_table_name( 'ai1ec_event_instances' ) => array(
'start',
'end',
),
$this->_dbi->get_table_name( 'ai1ec_facebook_users_events' ) => array(
'start',
),
);
$this->_indices = array(
$this->_dbi->get_table_name( 'ai1ec_event_instances' ) => array(
'evt_instance' => array(
'unique' => true,
'columns' => array( 'post_id', 'start' ),
'name' => 'evt_instance',
),
),
);
}
/**
* Interface to underlying methods to use as a filter callback.
*
* @wp_hook ai1ec_perform_scheme_update
*
* @return bool True when database is up to date.
*/
public function filter_scheme_update() {
return ( ! $this->is_change_required() || $this->execute() );
}
/**
* Retrieve columns for a given table.
*
* Checks if table exists before attempting to retrieve it.
*
* @param string $table Name of table to retrieve columns for.
*
* @return array Map of column names and their types.
*/
public function get_columns( $table ) {
if ( ! $this->_is_table( $table ) ) {
return array();
}
$list = $this->_dbi->get_results(
'SHOW COLUMNS FROM `' . $table . '`'
);
$columns = array();
foreach ( $list as $column ) {
$columns[$column->Field] = strtolower( $column->Type );
}
return $columns;
}
/**
* Retrieve list of indices for a given table.
*
* Checks if table exists before attempting to retrieve it.
*
* @param string $table Name of table to retrieve indices for.
*
* @return array Map of index names.
*/
public function get_indices( $table ) {
if ( ! $this->_is_table( $table ) ) {
return array();
}
$list = $this->_dbi->get_results(
'SHOW INDEX FROM `' . $table . '`'
);
$columns = array();
foreach ( $list as $column ) {
$columns[ strtolower( $column->Key_name ) ] = $column->Key_name;
}
return $columns;
}
/**
* Check if database change is required.
*
* @return bool True if any changes are required.
*/
public function is_change_required() {
foreach ( $this->_tables as $table => $columns ) {
$existing = $this->get_columns( $table );
foreach ( $existing as $column => $type ) {
if (
false === array_search( $column, $columns ) ||
0 !== stripos( $type, 'datetime' )
) {
unset( $existing[$column] );
}
}
if ( empty( $existing ) ) {
unset( $this->_tables[$table] );
}
}
if ( ! empty( $this->_tables ) ) {
return true;
}
return false;
}
/**
* Single stop for executing database changes.
*
* @return bool Success.
*/
public function execute() {
return $this->create_copies()
&& $this->apply_changes_to_copies()
&& $this->swap_tables();
}
/**
* Create copies of tables to be transformed.
*
* @return bool Success.
*/
public function create_copies() {
$tables = array_keys( $this->_tables );
foreach ( $tables as $table ) {
$suffixed = $table . $this->_table_suffix;
if (
! $this->drop( $suffixed ) ||
! $this->copy( $table, $suffixed )
) {
return false;
}
}
self::debug(
'Copies of following tables created successfully:',
$tables
);
return true;
}
/**
* Transform columns on copied tables.
*
* @return bool Success.
*/
public function apply_changes_to_copies() {
foreach ( $this->_tables as $table => $columns ) {
$name = $table . $this->_table_suffix;
if (
! (
$this->drop_indices( $table, $name )
&& $this->out_of_bounds_fix( $table, $name )
&& $this->add_columns( $name, $columns )
&& $this->transform_dates( $name, $columns )
&& $this->replace_columns( $name, $columns )
&& $this->restore_indices( $table, $name )
)
) {
return false;
}
}
self::debug(
'Table copies successfully modified:',
$this->_tables
);
return true;
}
/**
* Keep old table under unique name and move modified into it's place.
*
* @return bool Success.
*/
public function swap_tables() {
$tables = array_keys( $this->_tables );
$renames = array();
foreach ( $tables as $table ) {
$modified = $table . $this->_table_suffix;
$backup = $table . '_' . date( 'Y_m_d' ) . '_' . getmypid();
$renames[] = '`' . $table . '` TO `' . $backup . '`';
$renames[] = '`' . $modified . '` TO `' . $table . '`';
}
$sql_query = 'RENAME TABLE ' . implode( ', ', $renames );
if ( false === $this->_dbi->query( $sql_query ) ) {
return false;
}
self::debug(
'Tables successfully swaped:',
$this->_tables
);
return true;
}
/**
* Drop given table indices.
*
* @param string $name Original table name.
* @param string $table Table to actually perform changes upon.
*
* @return bool Success.
*/
public function drop_indices( $name, $table ) {
self::debug( __METHOD__ );
if ( ! isset( $this->_indices[$name] ) ) {
return true;
}
$existing = $this->get_indices( $table );
foreach ( $this->_indices[$name] as $index => $options ) {
if ( isset( $existing[$index] ) ) {
$sql_query = 'ALTER TABLE `' . $table . '` DROP INDEX `' .
$index . '`';
if ( false === $this->_dbi->query( $sql_query ) ) {
return false;
}
}
}
return true;
}
/**
* Add intermediate columns to a table.
*
* @param string $table Name of table to modify.
* @param array $columns List of column names to be added.
*
* @return bool Success.
*/
public function add_columns( $table, $columns ) {
self::debug( __METHOD__ );
$column_particles = array();
foreach ( $columns as $column ) {
$name = $column . $this->_column_suffix;
$column_particles[] = 'ADD COLUMN ' . $name .
' INT(10) UNSIGNED NOT NULL';
}
$sql_query = 'ALTER TABLE `' . $table . '` ' .
implode( ', ', $column_particles );
return ( false !== $this->_dbi->query( $sql_query ) );
}
/**
* Copy date values from `DATETIME` to `INT(10)` columns.
*
* @param string $table Name of table to modify.
* @param array $columns List of column names to be copied.
*
* @return bool Success.
*/
public function transform_dates( $table, $columns ) {
self::debug( __METHOD__ );
$update_particles = array();
foreach ( $columns as $column ) {
$name = $column . $this->_column_suffix;
$new_value = '\'1970-01-01 00:00:00\'';
if ( 'end' === $column && in_array( 'start', $columns ) ) {
$new_value = 'IFNULL(`start`, ' . $new_value . ')';
}
$update_particles[] = '`' . $name .
'` = UNIX_TIMESTAMP( IFNULL(`' . $column . '`, ' . $new_value . ' ))';
}
$sql_query = 'UPDATE `' . $table . '` SET ' .
implode( ', ', $update_particles );
return ( false !== $this->_dbi->query( $sql_query ) );
}
/**
* Drop old columns and move intermediate columns into their place.
*
* @param string $table Name of table to modify.
* @param array $columns List of column names to be replaced.
*
* @return bool Success.
*/
public function replace_columns( $table, $columns ) {
self::debug( __METHOD__ );
$snippets = array();
foreach ( $columns as $column ) {
$snippets[] = 'DROP COLUMN `' . $column . '`';
$snippets[] = 'CHANGE COLUMN `' . $column . $this->_column_suffix .
'` `' . $column . '` INT(10) UNSIGNED NOT NULL';
}
$sql_query = 'ALTER TABLE `' . $table . '` ' .
implode( ', ', $snippets );
return ( false !== $this->_dbi->query( $sql_query ) );
}
/**
* Restore indices for table processed.
*
* @param string $name Original table name.
* @param string $table Table to actually perform changes upon.
*
* @return bool Success.
*/
public function restore_indices( $name, $table ) {
self::debug( __METHOD__ );
if ( ! isset( $this->_indices[$name] ) ) {
return true;
}
foreach ( $this->_indices[$name] as $index => $options ) {
$sql_query = 'ALTER TABLE `' . $table . '` ADD';
if ( $options['unique'] ) {
$sql_query .= ' UNIQUE';
}
$sql_query .= ' INDEX `' .
$index . '` (`' .
implode( '`, `', $options['columns'] ) .
'`)';
if ( false === $this->_dbi->query( $sql_query ) ) {
return false;
}
}
return true;
}
/**
* Drop table.
*
* @param string $table Name of table to drop.
*
* @return bool Success.
*/
public function drop( $table ) {
$sql_query = 'DROP TABLE IF EXISTS ' . $table;
return ( false !== $this->_dbi->query( $sql_query ) );
}
/**
* Create table copy with full data set.
*
* @param string $existing Name of table to copy.
* @param string $new_table Name of table to create.
*
* @return bool Success.
*/
public function copy( $existing, $new_table ) {
$queries = array(
'CREATE TABLE ' . $new_table . ' LIKE ' . $existing,
'INSERT INTO ' . $new_table . ' SELECT * FROM ' . $existing,
);
foreach ( $queries as $query ) {
self::debug( $query );
if ( false === $this->_dbi->query( $query ) ) {
return false;
}
}
$count_new = $this->_dbi->get_var(
'SELECT COUNT(*) FROM ' . $new_table
);
$count_old = $this->_dbi->get_var(
'SELECT COUNT(*) FROM ' . $existing
);
// check if difference between tables records doesn't exceed
// several least significant bits of old table entries count
if ( absint( $count_new - $count_old ) > ( $count_old >> 4 ) ) {
return false;
}
return true;
}
/**
* Return list of tables to be processed
*
* @return array List of tables to be processed
*/
public function get_tables() {
return $this->_tables;
}
/**
* Delete events dated before or at `1970-01-01 00:00:00`.
*
* @param string $table Original table.
* @param string $name Temporary table to replay changes onto.
*
* @return bool Success.
*/
public function out_of_bounds_fix( $table, $name ) {
static $instances = null;
if ( null === $instances ) {
$instances = $this->_dbi->get_table_name( 'ai1ec_event_instances' );
}
if ( $instances !== $table ) {
return true;
}
$query = 'DELETE FROM `' .
$this->_dbi->get_table_name( $name ) .
'` WHERE `start` <= \'1970-01-01 00:00:00\'';
return ( false !== $this->_dbi->query( $query ) );
}
/**
* Check if given table exists.
*
* @param string $table Name of table to check.
*
* @return bool Existence.
*/
protected function _is_table( $table ) {
$name = $this->_dbi->get_var(
$this->_dbi->prepare( 'SHOW TABLES LIKE %s', $table )
);
return ( (string)$table === (string)$name );
}
}

View File

@@ -0,0 +1,29 @@
<?php
/**
* In case of database update failure this exception is thrown
*
* @author Time.ly Network Inc.
* @since 2.0
*
* @package AI1EC
* @subpackage AI1EC.Database.Exception
*/
class Ai1ec_Database_Error extends Ai1ec_Exception {
/**
* Override parent method to include tip.
*
* @return string Message to render.
*/
public function get_html_message() {
$message = '<p>' . Ai1ec_I18n::__(
'Database update has failed. Please make sure, that database user, defined in <em>wp-config.php</em> has permissions, to make changes (<strong>ALTER TABLE</strong>) to the database.'
) .
'</p><p>' . sprintf(
Ai1ec_I18n::__( 'Error encountered: %s' ),
$this->getMessage()
) . '</p>';
return $message;
}
}

View File

@@ -0,0 +1,14 @@
<?php
/**
* In case of database schema modification failure this exception is thrown
*
* @author Time.ly Network Inc.
* @since 2.0
*
* @package AI1EC
* @subpackage AI1EC.Database.Exception
*/
class Ai1ec_Database_Schema_Exception extends Ai1ec_Exception
{
}

View File

@@ -0,0 +1,12 @@
<?php
/**
* In case of database update failure this exception is thrown
*
* @author Time.ly Network Inc.
* @since 2.0
*
* @package AI1EC
* @subpackage AI1EC.Database.Exception
*/
class Ai1ec_Database_Update_Exception extends Ai1ec_Exception {
}

View File

@@ -0,0 +1,756 @@
<?php
/**
* Ai1ec_Database class
*
* Class responsible for generic database operations
*
* @author Time.ly Network Inc.
* @since 2.0
*
* @package AI1EC
* @subpackage AI1EC.Database
*/
class Ai1ec_Database_Helper {
/**
* @var array Map of tables and their parsed definitions
*/
protected $_schema_delta = array();
/**
* @var array List of valid table prefixes
*/
protected $_prefixes = array();
/**
* @var wpdb Localized instance of wpdb object
*/
protected $_db = NULL;
/**
* @var bool If set to true - no operations will be performed
*/
protected $_dry_run = false;
/**
* Constructor
*
* @param Ai1ec_Registry_Object $registry
*
* @return void
*/
public function __construct( Ai1ec_Registry_Object $registry ) {
$this->_db = $registry->get( 'dbi.dbi' );
$this->_prefixes = array(
$this->_db->get_table_name( 'ai1ec_' ),
$this->_db->get_table_name(),
'',
);
}
/**
* Check if dry run is enabled
*
* @param bool $dry Change dryness [optional=NULL]
*
* @return bool Dryness of run or previous value
*/
public function is_dry( $dry = NULL ) {
if ( NULL !== $dry ) {
$previous = $this->_dry_run;
$this->_dry_run = (bool)$dry;
return $previous;
}
return $this->_dry_run;
}
/**
* Get fully-qualified table name given it's abbreviated form
*
* @param string $name Name (abbreviation) of table to check
* @param bool $ignore_check Return longest name if no table exist [false]
*
* @return string Fully-qualified table name
*
* @throws Ai1ec_Database_Schema_Exception If no table matches
*/
public function table( $name, $ignore_check = false ) {
$existing = $this->get_all_tables();
$table = NULL;
$candidate = NULL;
foreach ( $this->_prefixes as $prefix ) {
$candidate = $prefix . $name;
$index = strtolower( $candidate );
if ( isset( $existing[$index] ) ) {
$table = $existing[$index];
break;
}
}
if ( NULL === $table ) {
if ( true === $ignore_check ) {
return $candidate;
}
throw new Ai1ec_Database_Schema_Exception(
'Table \'' . $name . '\' does not exist'
);
}
return $table;
}
/**
* Drop given indices from table
*
* @param string $table Name of table to modify
* @param string|array $indices List, or single, of indices to remove
*
* @return bool Success
*
* @throws Ai1ec_Database_Schema_Exception If table is not found
*/
public function drop_indices( $table, $indices ) {
if ( ! is_array( $indices ) ) {
$indices = array( (string)$indices );
}
$table = $this->table( $table );
$existing = $this->get_indices( $table );
$removed = 0;
foreach ( $indices as $index ) {
if (
! isset( $existing[$index] ) ||
$this->_dry_query(
'ALTER TABLE ' . $table . ' DROP INDEX ' . $index
)
) {
++$removed;
}
}
return ( count( $indices ) === $removed );
}
/**
* Create indices for given table
*
* Input ({@see $indices}) must be the same, as output of
* method {@see self::get_indices()}.
*
* @param string $table Name of table to create indices for
* @param array $indices Indices representation to be created
*
* @return bool Success
*
* @throws Ai1ec_Database_Schema_Exception If table is not found
*/
public function create_indices( $table, array $indices ) {
$table = $this->table( $table );
foreach ( $indices as $name => $definition ) {
$query = 'ALTER TABLE ' . $table . ' ADD ';
if ( $definition['unique'] ) {
$query .= 'UNIQUE ';
}
$query .= 'KEY ' . $name . ' (' .
implode( ', ', $definition['columns'] ) .
')';
if ( ! $this->_dry_query( $query ) ) {
return false;
}
}
return true;
}
/**
* get_indices method
*
* Get map of indices defined for table.
*
* @NOTICE: no optimization will be performed here, and response will not
* be cached, to allow checking result of DDL statements.
*
* Returned array structure (example):
* array(
* 'index_name' => array(
* 'name' => 'index_name',
* 'columns' => array(
* 'column1',
* 'column2',
* 'column3',
* ),
* 'unique' => true,
* ),
* )
*
* @param string $table Name of table to retrieve index names for
*
* @return array Map of index names and their representation
*
* @throws Ai1ec_Database_Schema_Exception If table is not found
*/
public function get_indices( $table ) {
$sql_query = 'SHOW INDEXES FROM ' . $this->table( $table );
$result = $this->_db->get_results( $sql_query );
$indices = array();
foreach ( $result as $index ) {
$name = $index->Key_name;
if ( ! isset( $indices[$name] ) ) {
$indices[$name] = array(
'name' => $name,
'columns' => array(),
'unique' => ! (bool)intval( $index->Non_unique ),
);
}
$indices[$name]['columns'][$index->Column_name] = $index->Sub_part;
}
return $indices;
}
/**
* Perform query, unless `dry_run` is selected. In later case just output
* the final query and return true.
*
* @param string $query SQL Query to execute
*
* @return mixed Query state, or true in dry run mode
*/
public function _dry_query( $query ) {
if ( $this->is_dry() ) {
pr( $query );
return true;
}
$result = $this->_db->query( $query );
if ( AI1EC_DEBUG ) {
echo '<h4>', $query, '</h4><pre>', var_export( $result, true ), '</pre>';
}
return $result;
}
/**
* Check if given table exists
*
* @param string $table Name of table to check
*
* @return bool Existance
*/
public function table_exists( $table ) {
$map = $this->get_all_tables();
return isset( $map[strtolower( $table )] );
}
/**
* Return a list of all tables currently present
*
* @return array Map of tables present
*/
public function get_all_tables() {
/**
* @TODO: refactor using dbi.dbi::get_tables
*/
$sql_query = 'SHOW TABLES LIKE \'' .
$this->_db->get_table_name() .
'%\'';
$result = $this->_db->get_col( $sql_query );
$tables = array();
foreach ( $result as $table ) {
$tables[strtolower( $table )] = $table;
}
return $tables;
}
/**
* apply_delta method
*
* Attempt to parse and apply given database tables definition, as a delta.
* Some validation is made prior to calling DB, and fields/indexes are also
* checked for consistency after sending queries to DB.
*
* NOTICE: only "CREATE TABLE" statements are handled. Others will, likely,
* be ignored, if passed through this method.
*
* @param string|array $query Single or multiple queries to perform on DB
*
* @return bool Success
*
* @throws Ai1ec_Database_Error In case of any error
*/
public function apply_delta( $query ) {
if ( ! function_exists( 'dbDelta' ) ) {
require_once ABSPATH . 'wp-admin' . DIRECTORY_SEPARATOR .
'includes' . DIRECTORY_SEPARATOR . 'upgrade.php';
}
$success = false;
$this->_schema_delta = array();
$queries = $this->_prepare_delta( $query );
$result = dbDelta( $queries );
$success = $this->_check_delta();
return $success;
}
/**
* get_notices_helper method
*
* DIP implementing method, to give access to Ai1ec_Deferred_Rendering_Helper.
*
* @param Ai1ec_Deferred_Rendering_Helper $replacement Notices implementor
*
* @return Ai1ec_Deferred_Rendering_Helper Instance of notices implementor
*/
public function get_notices_helper(
Ai1ec_Deferred_Rendering_Helper $replacement = NULL
) {
static $helper = NULL;
if ( NULL !== $replacement ) {
$helper = $replacement;
}
if ( NULL === $helper ) {
$helper = Ai1ec_Deferred_Rendering_Helper::get_instance();
}
return $helper;
}
/**
* _prepare_delta method
*
* Prepare statements for execution.
* Attempt to parse various SQL definitions and compose the one, that is
* most likely to be accepted by delta engine.
*
* @param string|array $queries Single or multiple queries to perform on DB
*
* @return bool Success
*
* @throws Ai1ec_Database_Error In case of any error
*/
protected function _prepare_delta( $queries ) {
if ( ! is_array( $queries ) ) {
$queries = explode( ';', $queries );
$queries = array_filter( $queries );
}
$current_table = NULL;
$ctable_regexp = '#
\s*CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?`?([^ ]+)`?\s*
\((.+)\)
([^()]*)
#six';
foreach ( $queries as $query ) {
if ( preg_match( $ctable_regexp, $query, $matches ) ) {
$this->_schema_delta[$matches[1]] = array(
'tblname' => $matches[1],
'cryptic' => NULL,
'creator' => '',
'columns' => array(),
'indexes' => array(),
'content' => preg_replace( '#`#', '', $matches[2] ),
'clauses' => $matches[3],
);
}
}
$this->_parse_delta();
$sane_queries = array();
foreach ( $this->_schema_delta as $table => $definition ) {
$create = 'CREATE TABLE ' . $table . " (\n";
foreach ( $definition['columns'] as $column ) {
$create .= ' ' . $column['create'] . ",\n";
}
foreach ( $definition['indexes'] as $index ) {
$create .= ' ' . $index['create'] . ",\n";
}
$create = substr( $create, 0, -2 ) . "\n";
$create .= ')' . $definition['clauses'];
$this->_schema_delta[$table]['creator'] = $create;
$this->_schema_delta[$table]['cryptic'] = md5( $create );
$sane_queries[] = $create;
}
return $sane_queries;
}
/**
* _parse_delta method
*
* Parse table application (creation) statements into atomical particles.
* Here "atomical particles" stands for either columns, or indexes.
*
* @return void Method does not return
*
* @throws Ai1ec_Database_Error In case of any error
*/
protected function _parse_delta() {
foreach ( $this->_schema_delta as $table => $definitions ) {
$listing = explode( "\n", $definitions['content'] );
$listing = array_filter( $listing, array( $this, '_is_not_empty_line' ) );
$lines = count( $listing );
$lineno = 0;
foreach ( $listing as $line ) {
++$lineno;
$line = trim( preg_replace( '#\s+#', ' ', $line ) );
$line_new = rtrim( $line, ',' );
if (
$lineno < $lines && $line === $line_new ||
$lineno == $lines && $line !== $line_new
) {
throw new Ai1ec_Database_Error(
'Missing comma in line \'' . $line . '\''
);
}
$line = $line_new;
unset( $line_new );
$type = 'indexes';
if ( false === ( $record = $this->_parse_index( $line ) ) ) {
$type = 'columns';
$record = $this->_parse_column( $line );
}
if ( isset(
$this->_schema_delta[$table][$type][$record['name']]
) ) {
throw new Ai1ec_Database_Error(
'For table `' . $table . '` entry ' . $type .
' named `' . $record['name'] . '` was declared twice' .
' in ' . $definitions
);
}
$this->_schema_delta[$table][$type][$record['name']] = $record;
}
}
}
/**
* _parse_index method
*
* Given string attempts to detect, if it is an index, and if yes - parse
* it to more navigable index definition for future validations.
* Creates modified index create line, for delta application.
*
* @param string $description Single "line" of CREATE TABLE statement body
*
* @return array|bool Index definition, or false if input does not look like index
*
* @throws Ai1ec_Database_Error In case of any error
*/
protected function _parse_index( $description ) {
$description = preg_replace(
'#^CONSTRAINT(\s+`?[^ ]+`?)?\s+#six',
'',
$description
);
$details = explode( ' ', $description );
$index = array(
'name' => NULL,
'content' => array(),
'create' => '',
);
$details[0] = strtoupper( $details[0] );
switch ( $details[0] ) {
case 'PRIMARY':
$index['name'] = 'PRIMARY';
$index['create'] = 'PRIMARY KEY ';
break;
case 'UNIQUE':
$name = $details[1];
if (
0 === strcasecmp( 'KEY', $name ) ||
0 === strcasecmp( 'INDEX', $name )
) {
$name = $details[2];
}
$index['name'] = $name;
$index['create'] = 'UNIQUE KEY ' . $name;
break;
case 'KEY':
case 'INDEX':
$index['name'] = $details[1];
$index['create'] = 'KEY ' . $index['name'];
break;
default:
return false;
}
$index['content'] = $this->_parse_index_content( $description );
$index['create'] .= ' (';
foreach ( $index['content'] as $column => $length ) {
$index['create'] .= $column;
if ( NULL !== $length ) {
$index['create'] .= '(' . $length . ')';
}
$index['create'] .= ',';
}
$index['create'] = substr( $index['create'], 0, -1 );
$index['create'] .= ')';
return $index;
}
/**
* _parse_column method
*
* Parse column to parseable definition.
* Some valid definitions may still be not recognizes (namely SET and ENUM)
* thus one shall beware, when attempting to create such.
* Create alternative create table entry line for delta application.
*
* @param string $description Single "line" of CREATE TABLE statement body
*
* @return array Column definition
*
* @throws Ai1ec_Database_Error In case of any error
*/
protected function _parse_column( $description ) {
$column_regexp = '#^
([a-z][a-z_]+)\s+
(
[A-Z]+
(?:\s*\(\s*\d+(?:\s*,\s*\d+\s*)?\s*\))?
(?:\s+UNSIGNED)?
(?:\s+ZEROFILL)?
(?:\s+BINARY)?
(?:
\s+CHARACTER\s+SET\s+[a-z][a-z_]+
(?:\s+COLLATE\s+[a-z][a-z0-9_]+)?
)?
)
(
\s+(?:NOT\s+)?NULL
)?
(
\s+DEFAULT\s+[^\s]+
)?
(\s+ON\s+UPDATE\s+CURRENT_(?:TIMESTAMP|DATE))?
(\s+AUTO_INCREMENT)?
\s*,?\s*
$#six';
if ( ! preg_match( $column_regexp, $description, $matches ) ) {
throw new Ai1ec_Database_Error(
'Invalid column description ' . $description
);
}
$column = array(
'name' => $matches[1],
'content' => array(),
'create' => '',
);
if ( 0 === strcasecmp( 'boolean', $matches[2] ) ) {
$matches[2] = 'tinyint(1)';
}
$column['content']['type'] = $matches[2];
$column['content']['null'] = (
! isset( $matches[3] ) ||
0 !== strcasecmp( 'NOT NULL', trim( $matches[3] ) )
);
$column['create'] = $column['name'] . ' ' . $column['content']['type'];
if ( isset( $matches[3] ) ) {
$column['create'] .= ' ' .
implode(
' ',
array_map(
'trim',
array_slice( $matches, 3 )
)
);
}
return $column;
}
/**
* _parse_index_content method
*
* Parse index content, to a map of columns and their length.
* All index (content) cases shall be covered, although it is only tested.
*
* @param string Single line of CREATE TABLE statement, containing index definition
*
* @return array Map of columns and their length, as per index definition
*
* @throws Ai1ec_Database_Error In case of any error
*/
protected function _parse_index_content( $description ) {
if ( ! preg_match( '#^[^(]+\((.+)\)$#', $description, $matches ) ) {
throw new Ai1ec_Database_Error(
'Invalid index description ' . $description
);
}
$columns = array();
$textual = explode( ',', $matches[1] );
$column_regexp = '#\s*([^(]+)(?:\s*\(\s*(\d+)\s*\))?\s*#sx';
foreach ( $textual as $column ) {
if (
! preg_match( $column_regexp, $column, $matches ) || (
isset( $matches[2] ) &&
(string)$matches[2] !== (string)intval( $matches[2] )
)
) {
throw new Ai1ec_Database_Error(
'Invalid index (columns) description ' . $description .
' as per \'' . $column . '\''
);
}
$matches[1] = trim( $matches[1] );
$columns[$matches[1]] = NULL;
if ( isset( $matches[2] ) ) {
$columns[$matches[1]] = (int)$matches[2];
}
}
return $columns;
}
/**
* _check_delta method
*
* Given parsed schema definitions (in {@see self::$_schema_delta} map) this
* method performs checks, to ensure that table exists, columns are of
* expected type, and indexes match their definition in original query.
*
* @return bool Success
*
* @throws Ai1ec_Database_Error In case of any error
*/
protected function _check_delta() {
if ( empty( $this->_schema_delta ) ) {
return true;
}
foreach ( $this->_schema_delta as $table => $description ) {
$columns = $this->_db->get_results( 'SHOW FULL COLUMNS FROM ' . $table );
if ( empty( $columns ) ) {
throw new Ai1ec_Database_Error(
'Required table `' . $table . '` was not created'
);
}
$db_column_names = array();
foreach ( $columns as $column ) {
if ( ! isset( $description['columns'][$column->Field] ) ) {
if ( $this->_db->query(
'ALTER TABLE `' . $table .
'` DROP COLUMN `' . $column->Field . '`'
) ) {
continue;
}
continue; // ignore so far
//throw new Ai1ec_Database_Error(
// 'Unknown column `' . $column->Field .
// '` is present in table `' . $table . '`'
//);
}
$db_column_names[$column->Field] = $column->Field;
$type_db = $column->Type;
$collation = '';
if ( $column->Collation ) {
$collation = ' CHARACTER SET ' .
substr(
$column->Collation,
0,
strpos( $column->Collation, '_' )
) . ' COLLATE ' . $column->Collation;
}
$type_req = $description['columns'][$column->Field]
['content']['type'];
if (
false !== stripos(
$type_req,
' COLLATE '
)
) {
// suspend collation checking
//$type_db .= $collation;
$type_req = preg_replace(
'#^
(.+)
\s+CHARACTER\s+SET\s+[a-z0-9_]+
\s+COLLATE\s+[a-z0-9_]+
(.+)?\s*
$#six',
'$1$2',
$type_req
);
}
$type_db = strtolower(
preg_replace( '#\s+#', '', $type_db )
);
$type_req = strtolower(
preg_replace( '#\s+#', '', $type_req )
);
if ( 0 !== strcmp( $type_db, $type_req ) ) {
throw new Ai1ec_Database_Error(
'Field `' . $table . '`.`' . $column->Field .
'` is of incompatible type'
);
}
if (
'YES' === $column->Null &&
false === $description['columns'][$column->Field]
['content']['null'] ||
'NO' === $column->Null &&
true === $description['columns'][$column->Field]
['content']['null']
) {
throw new Ai1ec_Database_Error(
'Field `' . $table . '`.`' . $column->Field .
'` NULLability is flipped'
);
}
}
if (
$missing = array_diff(
array_keys( $description['columns'] ),
$db_column_names
)
) {
throw new Ai1ec_Database_Error(
'In table `' . $table . '` fields are missing: ' .
implode( ', ', $missing )
);
}
$indexes = $this->get_indices( $table );
foreach ( $indexes as $name => $definition ) {
if ( ! isset( $description['indexes'][$name] ) ) {
continue; // ignore so far
//throw new Ai1ec_Database_Error(
// 'Unknown index `' . $name .
// '` is defined for table `' . $table . '`'
//);
}
if (
$missed = array_diff_assoc(
$description['indexes'][$name]['content'],
$definition['columns']
)
) {
throw new Ai1ec_Database_Error(
'Index `' . $name .
'` definition for table `' . $table . '` has invalid ' .
' fields: ' . implode( ', ', array_keys( $missed ) )
);
}
}
if (
$missing = array_diff(
array_keys( $description['indexes'] ),
array_keys( $indexes )
)
) {
throw new Ai1ec_Database_Error(
'In table `' . $table . '` indexes are missing: ' .
implode( ', ', $missing )
);
}
}
return true;
}
/**
* _is_not_empty_line method
*
* Helper method, to check that any given line is not empty.
* Aids array_filter in detecting empty SQL query lines.
*
* @param string $line Single line of DB query statement
*
* @return bool True if line is not empty, false otherwise
*/
protected function _is_not_empty_line( $line ) {
$line = trim( $line );
return ! empty( $line );
}
}

View File

@@ -0,0 +1,62 @@
<?php
/**
* Library function for massive time conversion operations.
*
* @author Time.ly Network, Inc.
* @since 2.0
* @package Ai1EC
* @subpackage Ai1EC.Date
*/
class Ai1ec_Date_Converter {
/**
* @var Ai1ec_Registry_Object Instance of objects registry.
*/
protected $_registry = null;
/**
* Get reference of object registry.
*
* @param Ai1ec_Registry_Object $registry Injected objects registry.
*
* @return void
*/
public function __construct( Ai1ec_Registry_Object $registry ) {
$this->_registry = $registry;
}
/**
* Change timezone of times provided.
*
* @param array $input List of time entries to convert.
* @param string $source_tz Timezone to convert from.
* @param string $target_tz Timezone to convert to.
* @param string $format Format of target time entries.
*
* @return array List of converted times.
*/
public function change_timezone(
array $input,
$source_tz,
$target_tz = 'UTC',
$format = 'U'
) {
$output = array();
foreach ( $input as $time ) {
try {
$time_object = $this->_registry->get(
'date.time',
$input,
$source_tz
);
$output[] = $time_object->format( $format, $target_tz );
unset( $time_object );
} catch ( Ai1ec_Date_Exception $exception ) {
// ignore
}
}
return $output;
}
}

View File

@@ -0,0 +1,53 @@
<?php
/**
* Wrapper for `DateTimeZone` to extend it with convenient methods
*
* @author Justas Butkus <justas@butkus.lt>
* @since 2013.03.06
*
* @package AllInOneCalendar
* @subpackage AllInOneCalendar.Utility.Time
*/
class Ai1ec_Date_Date_Time_Zone extends DateTimeZone {
/**
* Map of transitions details for given timestamp
* {@see DateTimeZone::getTransitions()} for representation of details.
* Return a map of prev(ious), curr(ent) and next transitions for
* a given timestamp.
*
* @NOTICE: if we start accepting PHP 5.3 - update getTransitions
* usage, to add offsets.
*
* @param int $timestamp UNIX timestamp (UTC0) for which to find transitions
*
* @return array Map of prev|curr|next transitions
*/
public function getDetailedTransitions( $timestamp ) {
$transition_list = $this->getTransitions();
$output = array(
'prev' => NULL,
'curr' => NULL,
'next' => NULL,
);
$previous = $current = NULL;
foreach ( $transition_list as $transition ) {
if (
NULL !== $previous &&
$timestamp >= $current['ts'] &&
$timestamp < $transition['ts']
) {
$output['prev'] = $previous;
$output['curr'] = $current;
$output['next'] = $transition;
break;
}
$previous = $current;
$current = $transition;
}
unset( $previous, $current, $transition_list, $transition );
return $output;
}
}

View File

@@ -0,0 +1,12 @@
<?php
/**
* Base exception for all date/time operation failures.
*
* @author Time.ly Network, Inc.
* @since 2.0
* @package Ai1EC
* @subpackage Ai1EC.Date.exception
*/
class Ai1ec_Date_Exception extends Ai1ec_Exception {
}

View File

@@ -0,0 +1,12 @@
<?php
/**
* Exception to be thrown when timezone operation fails.
*
* @author Time.ly Network, Inc.
* @since 2.0
* @package Ai1EC
* @subpackage Ai1EC.Date
*/
class Ai1ec_Date_Timezone_Exception extends Ai1ec_Date_Exception {
}

View File

@@ -0,0 +1,42 @@
<?php
/**
* Legacy Time utility.
*
* @author Time.ly Network, Inc.
* @since 2.0
* @package Ai1EC
* @subpackage Ai1EC.Date
*/
class Ai1ec_Time_Utility {
/**
* @var Ai1ec_Registry_Object
*/
static protected $_registry;
/**
* @param Ai1ec_Registry_Object $registry
*/
static public function set_registry( Ai1ec_Registry_Object $registry ) {
self::$_registry = $registry;
}
/**
* Legacy function needed for theme compatibility
*
* @param string $format
* @param int $timestamp
* @param bool $is_gmt
*/
static public function date_i18n(
$format,
$timestamp = false,
$is_gmt = true
) {
$timezone = ( $is_gmt ) ? 'UTC' : 'sys.default';
return self::$_registry->get( 'date.time', $timestamp, $timezone )
->format_i18n( $format );
}
}

View File

@@ -0,0 +1,279 @@
<?php
/**
* Wrap library calls to date subsystem.
*
* Meant to increase performance and work around known bugs in environment.
*
* @author Time.ly Network, Inc.
* @since 2.0
* @package Ai1EC
* @subpackage Ai1EC.Date
*/
class Ai1ec_Date_System extends Ai1ec_Base {
/**
* @var array List of local time (key '0') and GMT time (key '1').
*/
protected $_current_time = array();
/**
* @var Ai1ec_Cache_Memory
*/
protected $_gmtdates;
/**
* Initiate current time list.
*
* @param Ai1ec_Registry_Object $registry
*
* @return void
*/
public function __construct( Ai1ec_Registry_Object $registry ) {
parent::__construct( $registry );
$gmt_time = ( version_compare( PHP_VERSION, '5.1.0' ) >= 0 )
? time()
: gmmktime();
$requestTime = isset( $_SERVER['REQUEST_TIME'] ) ? (int)$_SERVER['REQUEST_TIME'] : time();
$this->_current_time = array(
$requestTime,
$gmt_time,
);
$this->_gmtdates = $registry->get( 'cache.memory' );
}
/**
* Get current time UNIX timestamp.
*
* Uses in-memory value, instead of re-calling `time()` / `gmmktime()`.
*
* @param bool $is_gmt Set to true to get GMT timestamp.
*
* @return int Current time UNIX timestamp
*/
public function current_time( $is_gmt = false ) {
return $this->_current_time[(int)( (bool)$is_gmt )];
}
/**
* Returns the associative array of date patterns supported by the plugin.
*
* Currently the formats are:
* array(
* 'def' => 'd/m/yyyy',
* 'us' => 'm/d/yyyy',
* 'iso' => 'yyyy-m-d',
* 'dot' => 'm.d.yyyy',
* );
*
* 'd' or 'dd' represent the day, 'm' or 'mm' represent the month, and 'yy'
* or 'yyyy' represent the year.
*
* @return array List of supported date patterns.
*/
public function get_date_patterns() {
return array(
'def' => 'd/m/yyyy',
'us' => 'm/d/yyyy',
'iso' => 'yyyy-m-d',
'dot' => 'm.d.yyyy',
);
}
/**
* Get acceptable date format.
*
* Returns the date pattern (in the form 'd-m-yyyy', for example) associated
* with the provided key, used by plugin settings. Simply a static map as
* follows:
*
* @param string $key Key for the date format.
*
* @return string Associated date format pattern.
*/
public function get_date_pattern_by_key( $key = 'def' ) {
$patterns = $this->get_date_patterns();
if ( ! isset( $patterns[$key] ) ) {
return (string)current( $patterns );
}
return $patterns[$key];
}
/**
* Format timestamp into URL safe, user selected representation.
*
* Returns a formatted date given a timestamp, based on the given date
* format, with any '/' characters replaced with URL-friendly '-'
* characters.
*
* @see Ai1ec_Date_System::get_date_patterns() for supported date formats.
*
* @param int $timestamp UNIX timestamp representing a date.
* @param string $pattern Key of date pattern (@see
* self::get_date_format_patter()) to
* format date with
*
* @return string Formatted date string.
*/
public function format_date_for_url( $timestamp, $pattern = 'def' ) {
$date = $this->format_date( $timestamp, $pattern );
$date = str_replace( '/', '-', $date );
return $date;
}
/**
* Similar to {@see format_date_for_url} just using new DateTime interface.
*
* @param Ai1ec_Date_Time $datetime Instance of datetime to format.
* @param string $pattern Target format to use.
*
* @return string Formatted datetime string.
*/
public function format_datetime_for_url(
Ai1ec_Date_Time $datetime,
$pattern = 'def'
) {
$date = $datetime->format( $this->get_date_format_patter( $pattern ) );
return str_replace( '/', '-', $date );
}
/**
* Returns the date formatted with new pattern from a given date and old pattern.
*
* @see self::get_date_patterns() for supported date formats.
*
* @param string $date Formatted date string
* @param string $old_pattern Key of old date pattern (@see
* self::get_date_format_patter())
* @param string $new_pattern Key of new date pattern (@see
* self::get_date_format_patter())
* @return string Formatted date string with new pattern
*/
public function convert_date_format( $date, $old_pattern, $new_pattern ) {
// Convert old date to timestamp
$timeArray = date_parse_from_format( $this->get_date_format_patter( $old_pattern ), $date );
$timestamp = mktime(
$timeArray['hour'], $timeArray['minute'], $timeArray['second'],
$timeArray['month'], $timeArray['day'], $timeArray['year']
);
// Convert to new date pattern
return $this->format_date( $timestamp, $new_pattern );
}
/**
* Returns a formatted date given a timestamp, based on the given date format.
*
* @see self::get_date_patterns() for supported date formats.
*
* @param int $timestamp UNIX timestamp representing a date (in GMT)
* @param string $pattern Key of date pattern (@see
* self::get_date_format_patter()) to
* format date with
* @return string Formatted date string
*/
public function format_date( $timestamp, $pattern = 'def' ) {
return gmdate( $this->get_date_format_patter( $pattern ), $timestamp );
}
public function get_date_format_patter( $requested ) {
$pattern = $this->get_date_pattern_by_key( $requested );
$pattern = str_replace(
array( 'dd', 'd', 'mm', 'm', 'yyyy', 'yy' ),
array( 'd', 'j', 'm', 'n', 'Y', 'y' ),
$pattern
);
return $pattern;
}
/**
* Returns human-readable version of the GMT offset.
*
* @param string $timezone_name Olsen Timezone name [optional=null]
*
* @return string GMT offset expression
*/
public function get_gmt_offset_expr( $timezone_name = null ) {
$timezone = $this->get_gmt_offset( $timezone_name );
$offset_h = (int)( $timezone / 60 );
$offset_m = absint( $timezone - $offset_h * 60 );
$timezone = sprintf(
Ai1ec_I18n::__( 'GMT%+d:%02d' ),
$offset_h,
$offset_m
);
return $timezone;
}
/**
* Get current GMT offset in seconds.
*
* @param string $timezone_name Olsen Timezone name [optional=null]
*
* @return int Offset from GMT in seconds.
*/
public function get_gmt_offset( $timezone_name = null ) {
if ( null === $timezone_name ) {
$timezone_name = 'sys.default';
}
$current = $this->_registry->get(
'date.time',
'now',
$timezone_name
);
return $current->get_gmt_offset();
}
/**
* gmgetdate method
*
* Get date/time information in GMT
*
* @param int $timestamp Timestamp at which information shall be evaluated
*
* @return array Associative array of information related to the timestamp
*/
public function gmgetdate( $timestamp = NULL ) {
if ( NULL === $timestamp ) {
$timestamp = isset( $_SERVER['REQUEST_TIME'] ) ? (int)$_SERVER['REQUEST_TIME'] : time();
}
if ( NULL === ( $date = $this->_gmtdates->get( $timestamp ) ) ) {
$particles = explode(
',',
gmdate( 's,i,G,j,w,n,Y,z,l,F,U', $timestamp )
);
$date = array_combine(
array(
'seconds',
'minutes',
'hours',
'mday',
'wday',
'mon',
'year',
'yday',
'weekday',
'month',
0
),
$particles
);
$this->_gmtdates->set( $timestamp, $date );
}
return $date;
}
/**
* Returns current rounded time as unix integer.
*
* @param int $shift Shift value.
*
* @return int Unix timestamp.
*/
public function get_current_rounded_time( $shift = 11 ) {
return $this->current_time() >> $shift << $shift;
}
}

View File

@@ -0,0 +1,216 @@
<?php
/**
* Time and date internationalization management library
*
* @author Timely Network Inc
* @since 2012.10.09
*
* @package AllInOneCalendar
* @subpackage AllInOneCalendar.Lib.Utility
*/
class Ai1ec_Time_I18n_Utility extends Ai1ec_Base {
/**
* @var char Separator to wrap unique keys and avoid collisions
* EOT is used instead of NUL, as NUL is used by `date()`
* functions family as guard and causes memory leaks.
*/
protected $_separator = "\004";
/**
* @var array Map of keys, used by date methods
*/
protected $_keys = array();
/**
* @var array Map of keys for substition
*/
protected $_skeys = array();
/**
* @var string Format to use when calling `date_i18n()`
*/
protected $_format = NULL;
/**
* @var Ai1ec_Memory_Utility Parsed time entries
*/
protected $_memory = NULL;
/**
* @var Ai1ec_Memory_Utility Parsed format entries
*/
protected $_transf = NULL;
/**
* Constructor
*
* Initialize internal memory objects and date keys.
*
* @param Ai1ec_Memory_Utility $memory Optionally inject memory to use
*
* @return void Constructor does not return
*/
public function __construct(
Ai1ec_Registry_Object $registry,
Ai1ec_Cache_Memory $memory = null
) {
parent::__construct( $registry );
if ( NULL === $memory ) {
$memory = $this->_registry->get( 'cache.memory', 120 ); // 30 * 4
}
$this->_memory = $memory;
$this->_transf = $this->_registry->get( 'cache.memory' );
$this->_keys = $this->_initialize_keys();
$this->_skeys = $this->_initialize_keys(
$this->_separator,
$this->_separator
);
$this->_format = implode( $this->_separator, $this->_keys );
}
/**
* format method
*
* Convenient wrapper for `date_i18n()`, which caches both faster format
* version and response for {$timestamp} and {$is_gmt} combination.
*
* @param string $format Format string to output timestamp in
* @param int $timestamp UNIX timestamp to output in given format
* @param bool $is_gmt Set to true, to treat {$timestamp} as GMT
*
* @return string Formatted date-time entry
*/
public function format( $format, $timestamp = false, $is_gmt = false ) {
$time_elements = $this->parse( $timestamp, $is_gmt );
$local_format = $this->_safe_format( $format );
return str_replace( $this->_skeys, $time_elements, $local_format );
}
/**
* parse method
*
* Parse given timestamp into I18n date/time values map.
*
* @param int $timestamp Timestamp to parse
* @param bool $is_gmt Set to true, to treat value as present in GMT
*
* @return array Map of date format keys and corresponding time values
*/
public function parse( $timestamp = false, $is_gmt = false ) {
$timestamp = (int)$timestamp;
if ( $timestamp <= 0 ) {
$timestamp = $this->_registry->get( 'date.system' )->current_time();
}
$cache_key = $timestamp . "\0" . $is_gmt;
if ( NULL === ( $record = $this->_memory->get( $cache_key ) ) ) {
$record = array_combine(
$this->_keys,
explode(
$this->_separator,
date_i18n( $this->_format, $timestamp, $is_gmt )
)
);
$this->_memory->set( $cache_key, $record );
}
return $record;
}
/**
* _safe_format method
*
* Prepare safe format value, to use in substitutions.
* In prepared string special values are wrapped by {$_separator} to allow
* fast replacement methods, using binary search.
*
* @param string $format Given format to polish
*
* @return string Modified format, with special keys wrapped in bin fields
*/
protected function _safe_format( $format ) {
if ( NULL === ( $safe = $this->_transf->get( $format ) ) ) {
$safe = '';
$state = 0;
$separator = $this->_separator;
$length = strlen( $format );
for ( $index = 0; $index < $length; $index++ ) {
if ( $state > 0 ) {
--$state;
}
$current = $format{$index};
if ( 0 === $state ) {
if ( '\\' === $current ) {
$state = 2;
} elseif ( isset( $this->_keys[$current] ) ) {
$current = $separator . $current . $separator;
}
}
if ( 2 !== $state ) {
$safe .= $current;
}
}
$this->_transf->set( $format, $safe );
}
return $safe;
}
/**
* _initialize_keys method
*
* Prepare list of keys, used by date functions.
* Optionally wrap values (keys are the same, always).
*
* @param string $prepend Prefix to date key
* @param string $append Suffix to date key
*
* @return array Map of date keys
*/
protected function _initialize_keys( $prepend = '', $append = '' ) {
$keys = array(
'd',
'D',
'j',
'l',
'N',
'S',
'w',
'z',
'W',
'F',
'm',
'M',
'n',
't',
'L',
'o',
'Y',
'y',
'a',
'A',
'B',
'g',
'G',
'h',
'H',
'i',
's',
'u',
'e',
'I',
'O',
'P',
'T',
'Z',
'c',
'r',
'U',
);
$map = array();
foreach ( $keys as $key ) {
$map[$key] = $prepend . $key . $append;
}
return $map;
}
}

View File

@@ -0,0 +1,423 @@
<?php
/**
* Time entity.
*
* @instantiator new
* @author Time.ly Network, Inc.
* @since 2.0
* @package Ai1EC
* @subpackage Ai1EC.Date
*/
class Ai1ec_Date_Time {
/**
* @var Ai1ec_Registry_Object Instance of objects registry.
*/
protected $_registry = null;
/**
* @var DateTime Instance of date time object used to perform manipulations.
*/
protected $_date_time = null;
/**
* @var string Olsen name of preferred timezone to use if none is requested.
*/
protected $_preferred_timezone = null;
/**
* @var bool Set to true when `no value` is set.
*/
protected $_is_empty = false;
/**
* Initialize local date entity.
*
* @param Ai1ec_Registry_Object $registry Objects registry instance.
* @param string $time For details {@see self::format}.
* @param string $timezone For details {@see self::format}.
*
* @return void
*/
public function __construct(
Ai1ec_Registry_Object $registry,
$time = 'now',
$timezone = 'UTC'
) {
$this->_registry = $registry;
$this->set_date_time( $time, $timezone );
}
/**
* Since clone is shallow, we need to clone the DateTime object
*/
public function __clone() {
$this->_date_time = clone $this->_date_time;
}
/**
* Return formatted date in desired timezone.
*
* NOTICE: consider optimizing by storing multiple copies of `DateTime` for
* each requested timezone, or some of them, as of now timezone is changed
* back and forth every time when formatting is called for.
*
* @param string $format Desired format as accepted by {@see date}.
* @param string $timezone Valid timezone identifier. Defaults to current.
*
* @return string Formatted date time.
*
* @throws Ai1ec_Date_Timezone_Exception If timezone is not recognized.
*/
public function format( $format = 'U', $timezone = null ) {
if ( $this->_is_empty ) {
return null;
}
if ( 'U' === $format ) { // performance cut
return $this->_date_time->format( 'U' );
}
$timezone = $this->get_default_format_timezone( $timezone );
$last_tz = $this->get_timezone();
$this->set_timezone( $timezone );
$formatted = $this->_date_time->format( $format );
$this->set_timezone( $last_tz );
return $formatted;
}
/**
* Format date time to i18n representation.
*
* @param string $format Target I18n format.
* @param string $timezone Valid timezone identifier. Defaults to current.
*
* @return string Formatted time.
*/
public function format_i18n( $format, $timezone = null ) {
$parser = $this->_registry->get( 'parser.date' );
$parsed = $parser->get_format( $format );
$inflected = $this->format( $parsed, $timezone );
$formatted = $parser->squeeze( $inflected );
return $formatted;
}
/**
* Commodity method to format to UTC.
*
* @param string $format Target format, defaults to UNIX timestamp.
*
* @return string Formatted datetime string.
*/
public function format_to_gmt( $format = 'U' ) {
return $this->format( $format, 'UTC' );
}
/**
* Create JavaScript ready date/time information string.
*
* @param bool $event_timezone Set to true to format in event timezone.
*
* @return string JavaScript date/time string.
*/
public function format_to_javascript( $event_timezone = false ) {
$event_timezone = ( $event_timezone )
? $this->get_timezone()
: null;
return $this->format( 'Y-m-d\TH:i:s', $event_timezone );
}
/**
* Get timezone to use when format doesn't have one.
*
* Precedence:
* 1. Timezone supplied for formatting;
* 2. Objects preferred timezone;
* 3. Default systems timezone.
*
* @var string $timezone Requested formatting timezone.
*
* @return string Olsen timezone name to use.
*/
public function get_default_format_timezone( $timezone = null ) {
if ( null !== $timezone ) {
return $timezone;
}
if ( null !== $this->_preferred_timezone ) {
return $this->_preferred_timezone;
}
return $this->_registry->get( 'date.timezone' )
->get_default_timezone();
}
/**
* Offset from GMT in minutes.
*
* @return int Signed integer - offset.
*/
public function get_gmt_offset() {
return $this->_date_time->getOffset() / 60;
}
/**
* Returns timezone offset as human readable GMT string.
*
* @return string
*/
public function get_gmt_offset_as_text() {
$offset = $this->_date_time->getOffset();
$offsetHours = $offset / 3600;
$offset = $offset % 3600;
$offsetMinutes = abs( $offset ) / 60;
return sprintf( '(GMT%+03d:%02d)', $offsetHours, $offsetMinutes );
}
/**
* Set preferred timezone to use when format is called without any.
*
* @param DateTimeZone $timezone Preferred timezone instance.
*
* @return Ai1ec_Date_Time Instance of self for chaining.
*/
public function set_preferred_timezone( $timezone ) {
if ( $timezone instanceof DateTimeZone ) {
$timezone = $timezone->getName();
}
$this->_preferred_timezone = (string)$timezone;
return $this;
}
/**
* Change timezone of stored entity.
*
* @param string $timezone Valid timezone identifier.
*
* @return Ai1ec_Date Instance of self for chaining.
*
* @throws Ai1ec_Date_Timezone_Exception If timezone is not recognized.
*/
public function set_timezone( $timezone = 'UTC' ) {
$date_time_tz = ( $timezone instanceof DateTimeZone )
? $timezone
: $this->_registry->get( 'date.timezone' )->get( $timezone );
$this->_date_time->setTimezone( $date_time_tz );
return $this;
}
/**
* Get timezone associated with current object.
*
* @return string|null Valid PHP timezone string or null on error.
*/
public function get_timezone() {
$timezone = $this->_date_time->getTimezone();
if ( false === $timezone ) {
return null;
}
return $timezone->getName();
}
/**
* Get difference in seconds between to dates.
*
* In PHP versions post 5.3.0 the {@see DateTimeImmutable::diff()} is
* used. In earlier versions the difference between two timestamps is
* being checked.
*
* @param Ai1ec_Date_Time $comparable Other date time entity.
*
* @return int Number of seconds between two dates.
*/
public function diff_sec( Ai1ec_Date_Time $comparable, $timezone = null ) {
// NOTICE: `$this->_is_empty` is not touched here intentionally
// because there is no meaningful difference to `empty` value.
// It is left to be handled at upper level - you are not likely to
// reach situation where you compare something against empty value.
if ( version_compare( PHP_VERSION, '5.3.0' ) < 0 ) {
$difference = $this->_date_time->format( 'U' ) -
$comparable->_date_time->format( 'U' );
if ( $difference < 0 ) {
$difference *= -1;
}
return $difference;
}
$difference = $this->_date_time->diff( $comparable->_date_time, true );
return (
$difference->days * 86400 +
$difference->h * 3600 +
$difference->i * 60 +
$difference->s
);
}
/**
* Adjust only date fragment of entity.
*
* @param int $year Year of the date.
* @param int $month Month of the date.
* @param int $day Day of the date.
*
* @return Ai1ec_Date_Time Instance of self for chaining.
*/
public function set_date( $year, $month, $day ) {
$this->_date_time->setDate( $year, $month, $day );
$this->_is_empty = false;
return $this;
}
/**
* Adjust only time fragment of entity.
*
* @param int $hour Hour of the time.
* @param int $minute Minute of the time.
* @param int $second Second of the time.
*
* @return Ai1ec_Date_Time Instance of self for chaining.
*/
public function set_time( $hour, $minute = 0, $second = 0 ) {
$this->_date_time->setTime( $hour, $minute, $second );
$this->_is_empty = false;
return $this;
}
/**
* Adjust day part of date time entity.
*
* @param int $quantifier Day adjustment quantifier.
*
* @return Ai1ec_Date_Time Instance of self for chaining.
*/
public function adjust_day( $quantifier ) {
// NOTICE: `$this->_is_empty` is not touched here, because if you
// start adjusting value it's likely not empty by then.
$this->adjust( $quantifier, 'day' );
return $this;
}
/**
* Adjust day part of date time entity.
*
* @param int $quantifier Day adjustment quantifier.
*
* @return Ai1ec_Date_Time Instance of self for chaining.
*/
public function adjust_month( $quantifier ) {
$this->adjust( $quantifier, 'month' );
return $this;
}
/**
* Change/initiate stored date time entity.
*
* NOTICE: time specifiers falling in range 0..2048 will be treated
* as a UNIX timestamp, to full format specification, thus ignoring
* any value passed for timezone.
*
* @param string $time Valid (PHP-parseable) date/time identifier.
* @param string $timezone Valid timezone identifier.
*
* @return Ai1ec_Date Instance of self for chaining.
*/
public function set_date_time( $time = 'now', $timezone = 'UTC' ) {
if ( $time instanceof self ) {
$this->_is_empty = $time->_is_empty;
$this->_date_time = clone $time->_date_time;
$this->_preferred_timezone = $time->_preferred_timezone;
if ( 'UTC' !== $timezone && $timezone ) {
$this->set_timezone( $timezone );
}
return $this;
}
$this->assert_utc_timezone();
$date_time_tz = $this->_registry->get( 'date.timezone' )
->get( $timezone );
$reset_tz = false;
$this->_is_empty = false;
if ( null === $time ) {
$this->_is_empty = true;
$time = '@' . ~PHP_INT_MAX;
$reset_tz = true;
} else if ( $this->is_timestamp( $time ) ) {
$time = '@' . $time; // treat as UNIX timestamp
$reset_tz = true; // store intended TZ
}
// PHP <= 5.3.5 compatible
$this->_date_time = new DateTime( $time, $date_time_tz );
if ( $reset_tz ) {
$this->set_timezone( $date_time_tz );
}
return $this;
}
/**
* Check if value should be treated as a UNIX timestamp.
*
* @param string $time Provided time value.
*
* @return bool True if seems like UNIX timestamp.
*/
public function is_timestamp( $time ) {
// '20001231T001559Z'
if ( isset( $time{8} ) && 'T' === $time{8} ) {
return false;
}
if ( (string)(int)$time !== (string)$time ) {
return false;
}
// 1000..2459 are treated as hours, 2460..9999 - as years
if ( $time > 999 && $time < 2460 ) {
return false;
}
return true;
}
/**
* Assert that current timezone is UTC.
*
* @return bool Success.
*/
public function assert_utc_timezone() {
$default = (string)date_default_timezone_get();
$success = true;
if ( 'UTC' !== $default ) {
// issue admin notice
$success = date_default_timezone_set( 'UTC' );
}
return $success;
}
/**
* Magic method for compatibility.
*
* @return string ISO-8601 formatted date-time.
*/
public function __toString() {
return $this->format( 'c' );
}
/**
* Modifies the DateTime object
*
* @param int $quantifieruantifier
* @param string $longname
*/
public function adjust( $quantifier, $longname ) {
$quantifier = (int)$quantifier;
if ( $quantifier > 0 && '+' !== $quantifier{0} ) {
$quantifier = '+' . $quantifier;
}
$modifier = $quantifier . ' ' . $longname;
$this->_date_time->modify( $modifier );
return $this;
}
/**
* Explicitly check if value (date) is empty.
*
* @return bool Emptiness
*/
public function is_empty() {
return $this->_is_empty;
}
}

View File

@@ -0,0 +1,617 @@
<?php
/**
* Timezones manipulation object.
*
* @author Time.ly Network, Inc.
* @since 2.0
* @package Ai1EC
* @subpackage Ai1EC.Date
*/
class Ai1ec_Date_Timezone extends Ai1ec_Base {
/**
* @var Ai1ec_Cache_Interface In-memory storage for timezone objects.
*/
protected $_cache = null;
/**
* @var array Map of timezone names and their Olson TZ counterparts.
*/
protected $_zones = array(
'+00:00' => 'UTC',
'Z' => 'UTC',
'AUS Central Standard Time' => 'Australia/Darwin',
'AUS Eastern Standard Time' => 'Australia/Sydney',
'Acre' => 'America/Rio_Branco',
'Afghanistan' => 'Asia/Kabul',
'Afghanistan Standard Time' => 'Asia/Kabul',
'Africa_Central' => 'Africa/Maputo',
'Africa_Eastern' => 'Africa/Nairobi',
'Africa_FarWestern' => 'Africa/El_Aaiun',
'Africa_Southern' => 'Africa/Johannesburg',
'Africa_Western' => 'Africa/Lagos',
'Aktyubinsk' => 'Asia/Aqtobe',
'Alaska' => 'America/Juneau',
'Alaska_Hawaii' => 'America/Anchorage',
'Alaskan Standard Time' => 'America/Anchorage',
'Almaty' => 'Asia/Almaty',
'Amazon' => 'America/Manaus',
'America_Central' => 'America/Chicago',
'America_Eastern' => 'America/New_York',
'America_Mountain' => 'America/Denver',
'America_Pacific' => 'America/Los_Angeles',
'Anadyr' => 'Asia/Anadyr',
'Aqtau' => 'Asia/Aqtau',
'Aqtobe' => 'Asia/Aqtobe',
'Arab Standard Time' => 'Asia/Riyadh',
'Arabian' => 'Asia/Riyadh',
'Arabian Standard Time' => 'Asia/Dubai',
'Arabic Standard Time' => 'Asia/Baghdad',
'Argentina' => 'America/Buenos_Aires',
'Argentina Standard Time' => 'America/Buenos_Aires',
'Argentina_Western' => 'America/Mendoza',
'Armenia' => 'Asia/Yerevan',
'Armenian Standard Time' => 'Asia/Yerevan',
'Ashkhabad' => 'Asia/Ashgabat',
'Atlantic' => 'America/Halifax',
'Atlantic Standard Time' => 'America/Halifax',
'Australia_Central' => 'Australia/Adelaide',
'Australia_CentralWestern' => 'Australia/Eucla',
'Australia_Eastern' => 'Australia/Sydney',
'Australia_Western' => 'Australia/Perth',
'Azerbaijan' => 'Asia/Baku',
'Azerbaijan Standard Time' => 'Asia/Baku',
'Azores' => 'Atlantic/Azores',
'Azores Standard Time' => 'Atlantic/Azores',
'Baku' => 'Asia/Baku',
'Bangladesh' => 'Asia/Dhaka',
'Bering' => 'America/Adak',
'Bhutan' => 'Asia/Thimphu',
'Bolivia' => 'America/La_Paz',
'Borneo' => 'Asia/Kuching',
'Brasilia' => 'America/Sao_Paulo',
'British' => 'Europe/London',
'Brunei' => 'Asia/Brunei',
'Canada Central Standard Time' => 'America/Regina',
'Cape Verde Standard Time' => 'Atlantic/Cape_Verde',
'Cape_Verde' => 'Atlantic/Cape_Verde',
'Caucasus Standard Time' => 'Asia/Yerevan',
'Cen. Australia Standard Time' => 'Australia/Adelaide',
'Central America Standard Time' => 'America/Guatemala',
'Central Asia Standard Time' => 'Asia/Dhaka',
'Central Brazilian Standard Time' => 'America/Manaus',
'Central Europe Standard Time' => 'Europe/Budapest',
'Central European Standard Time' => 'Europe/Warsaw',
'Central Pacific Standard Time' => 'Pacific/Guadalcanal',
'Central Standard Time' => 'America/Chicago',
'Central Standard Time (Mexico)' => 'America/Mexico_City',
'Chamorro' => 'Pacific/Saipan',
'Changbai' => 'Asia/Harbin',
'Chatham' => 'Pacific/Chatham',
'Chile' => 'America/Santiago',
'China' => 'Asia/Shanghai',
'China Standard Time' => 'Asia/Shanghai',
'Choibalsan' => 'Asia/Choibalsan',
'Christmas' => 'Indian/Christmas',
'Cocos' => 'Indian/Cocos',
'Colombia' => 'America/Bogota',
'Cook' => 'Pacific/Rarotonga',
'Cuba' => 'America/Havana',
'Dacca' => 'Asia/Dhaka',
'Dateline Standard Time' => 'Etc/GMT+12',
'Davis' => 'Antarctica/Davis',
'Dominican' => 'America/Santo_Domingo',
'DumontDUrville' => 'Antarctica/DumontDUrville',
'Dushanbe' => 'Asia/Dushanbe',
'Dutch_Guiana' => 'America/Paramaribo',
'E. Africa Standard Time' => 'Africa/Nairobi',
'E. Australia Standard Time' => 'Australia/Brisbane',
'E. Europe Standard Time' => 'Europe/Minsk',
'E. South America Standard Time' => 'America/Sao_Paulo',
'East_Timor' => 'Asia/Dili',
'Easter' => 'Pacific/Easter',
'Eastern Standard Time' => 'America/New_York',
'Ecuador' => 'America/Guayaquil',
'Egypt Standard Time' => 'Africa/Cairo',
'Ekaterinburg Standard Time' => 'Asia/Yekaterinburg',
'Europe_Central' => 'Europe/Paris',
'Europe_Eastern' => 'Europe/Bucharest',
'Europe_Western' => 'Atlantic/Canary',
'FLE Standard Time' => 'Europe/Kiev',
'Falkland' => 'Atlantic/Stanley',
'Fiji' => 'Pacific/Fiji',
'Fiji Standard Time' => 'Pacific/Fiji',
'French_Guiana' => 'America/Cayenne',
'French_Southern' => 'Indian/Kerguelen',
'Frunze' => 'Asia/Bishkek',
'GMT' => 'UTC', // seems better than 'Atlantic/Reykjavik'
'GMT Standard Time' => 'Europe/London',
'GTB Standard Time' => 'Europe/Istanbul',
'Galapagos' => 'Pacific/Galapagos',
'Gambier' => 'Pacific/Gambier',
'Georgia' => 'Asia/Tbilisi',
'Georgian Standard Time' => 'Etc/GMT-3',
'Gilbert_Islands' => 'Pacific/Tarawa',
'Goose_Bay' => 'America/Goose_Bay',
'Greenland Standard Time' => 'America/Godthab',
'Greenland_Central' => 'America/Scoresbysund',
'Greenland_Eastern' => 'America/Scoresbysund',
'Greenland_Western' => 'America/Godthab',
'Greenwich Standard Time' => 'Atlantic/Reykjavik',
'Guam' => 'Pacific/Guam',
'Gulf' => 'Asia/Dubai',
'Guyana' => 'America/Guyana',
'Hawaii_Aleutian' => 'Pacific/Honolulu',
'Hawaiian Standard Time' => 'Pacific/Honolulu',
'Hong_Kong' => 'Asia/Hong_Kong',
'Hovd' => 'Asia/Hovd',
'India' => 'Asia/Calcutta',
'India Standard Time' => 'Asia/Calcutta',
'Indian_Ocean' => 'Indian/Chagos',
'Indochina' => 'Asia/Saigon',
'Indonesia_Central' => 'Asia/Makassar',
'Indonesia_Eastern' => 'Asia/Jayapura',
'Indonesia_Western' => 'Asia/Jakarta',
'Iran' => 'Asia/Tehran',
'Iran Standard Time' => 'Asia/Tehran',
'Irish' => 'Europe/Dublin',
'Irkutsk' => 'Asia/Irkutsk',
'Israel' => 'Asia/Jerusalem',
'Israel Standard Time' => 'Asia/Jerusalem',
'Japan' => 'Asia/Tokyo',
'Jordan Standard Time' => 'Asia/Amman',
'Kamchatka' => 'Asia/Kamchatka',
'Karachi' => 'Asia/Karachi',
'Kashgar' => 'Asia/Kashgar',
'Kazakhstan_Eastern' => 'Asia/Almaty',
'Kazakhstan_Western' => 'Asia/Aqtobe',
'Kizilorda' => 'Asia/Qyzylorda',
'Korea' => 'Asia/Seoul',
'Korea Standard Time' => 'Asia/Seoul',
'Kosrae' => 'Pacific/Kosrae',
'Krasnoyarsk' => 'Asia/Krasnoyarsk',
'Kuybyshev' => 'Europe/Samara',
'Kwajalein' => 'Pacific/Kwajalein',
'Kyrgystan' => 'Asia/Bishkek',
'Lanka' => 'Asia/Colombo',
'Liberia' => 'Africa/Monrovia',
'Line_Islands' => 'Pacific/Kiritimati',
'Long_Shu' => 'Asia/Chongqing',
'Lord_Howe' => 'Australia/Lord_Howe',
'Macau' => 'Asia/Macau',
'Magadan' => 'Asia/Magadan',
'Malaya' => 'Asia/Kuala_Lumpur',
'Malaysia' => 'Asia/Kuching',
'Maldives' => 'Indian/Maldives',
'Marquesas' => 'Pacific/Marquesas',
'Marshall_Islands' => 'Pacific/Majuro',
'Mauritius' => 'Indian/Mauritius',
'Mauritius Standard Time' => 'Indian/Mauritius',
'Mawson' => 'Antarctica/Mawson',
'Mexico Standard Time' => 'America/Mexico_City',
'Mexico Standard Time 2' => 'America/Chihuahua',
'Mid-Atlantic Standard Time' => 'Atlantic/South_Georgia',
'Middle East Standard Time' => 'Asia/Beirut',
'Mongolia' => 'Asia/Ulaanbaatar',
'Montevideo Standard Time' => 'America/Montevideo',
'Morocco Standard Time' => 'Africa/Casablanca',
'Moscow' => 'Europe/Moscow',
'Mountain Standard Time' => 'America/Denver',
'Mountain Standard Time (Mexico)' => 'America/Chihuahua',
'Myanmar' => 'Asia/Rangoon',
'Myanmar Standard Time' => 'Asia/Rangoon',
'N. Central Asia Standard Time' => 'Asia/Novosibirsk',
'Namibia Standard Time' => 'Africa/Windhoek',
'Nauru' => 'Pacific/Nauru',
'Nepal' => 'Asia/Katmandu',
'Nepal Standard Time' => 'Asia/Katmandu',
'New Zealand Standard Time' => 'Pacific/Auckland',
'New_Caledonia' => 'Pacific/Noumea',
'New_Zealand' => 'Pacific/Auckland',
'Newfoundland' => 'America/St_Johns',
'Newfoundland Standard Time' => 'America/St_Johns',
'Niue' => 'Pacific/Niue',
'Norfolk' => 'Pacific/Norfolk',
'Noronha' => 'America/Noronha',
'North Asia East Standard Time' => 'Asia/Irkutsk',
'North Asia Standard Time' => 'Asia/Krasnoyarsk',
'North_Mariana' => 'Pacific/Saipan',
'Novosibirsk' => 'Asia/Novosibirsk',
'Omsk' => 'Asia/Omsk',
'Oral' => 'Asia/Oral',
'Pacific SA Standard Time' => 'America/Santiago',
'Pacific Standard Time' => 'America/Los_Angeles',
'Pacific Standard Time (Mexico)' => 'America/Tijuana',
'Pakistan' => 'Asia/Karachi',
'Pakistan Standard Time' => 'Asia/Karachi',
'Palau' => 'Pacific/Palau',
'Papua_New_Guinea' => 'Pacific/Port_Moresby',
'Paraguay' => 'America/Asuncion',
'Peru' => 'America/Lima',
'Philippines' => 'Asia/Manila',
'Phoenix_Islands' => 'Pacific/Enderbury',
'Pierre_Miquelon' => 'America/Miquelon',
'Pitcairn' => 'Pacific/Pitcairn',
'Ponape' => 'Pacific/Ponape',
'Qyzylorda' => 'Asia/Qyzylorda',
'Reunion' => 'Indian/Reunion',
'Romance Standard Time' => 'Europe/Paris',
'Rothera' => 'Antarctica/Rothera',
'Russian Standard Time' => 'Europe/Moscow',
'SA Eastern Standard Time' => 'Etc/GMT+3',
'SA Pacific Standard Time' => 'America/Bogota',
'SA Western Standard Time' => 'America/La_Paz',
'SE Asia Standard Time' => 'Asia/Bangkok',
'Sakhalin' => 'Asia/Sakhalin',
'Samara' => 'Europe/Samara',
'Samarkand' => 'Asia/Samarkand',
'Samoa' => 'Pacific/Apia',
'Samoa Standard Time' => 'Pacific/Apia',
'Seychelles' => 'Indian/Mahe',
'Shevchenko' => 'Asia/Aqtau',
'Singapore' => 'Asia/Singapore',
'Singapore Standard Time' => 'Asia/Singapore',
'Solomon' => 'Pacific/Guadalcanal',
'South Africa Standard Time' => 'Africa/Johannesburg',
'South_Georgia' => 'Atlantic/South_Georgia',
'Sri Lanka Standard Time' => 'Asia/Colombo',
'Suriname' => 'America/Paramaribo',
'Sverdlovsk' => 'Asia/Yekaterinburg',
'Syowa' => 'Antarctica/Syowa',
'Tahiti' => 'Pacific/Tahiti',
'Taipei' => 'Asia/Taipei',
'Taipei Standard Time' => 'Asia/Taipei',
'Tajikistan' => 'Asia/Dushanbe',
'Tashkent' => 'Asia/Tashkent',
'Tasmania Standard Time' => 'Australia/Hobart',
'Tbilisi' => 'Asia/Tbilisi',
'Tokelau' => 'Pacific/Fakaofo',
'Tokyo Standard Time' => 'Asia/Tokyo',
'Tonga' => 'Pacific/Tongatapu',
'Tonga Standard Time' => 'Pacific/Tongatapu',
'Truk' => 'Pacific/Truk',
'Turkey' => 'Europe/Istanbul',
'Turkmenistan' => 'Asia/Ashgabat',
'Tuvalu' => 'Pacific/Funafuti',
'US/Eastern' => 'America/New_York',
'US Eastern Standard Time' => 'Etc/GMT+5',
'US Mountain Standard Time' => 'America/Phoenix',
'Uralsk' => 'Asia/Oral',
'Uruguay' => 'America/Montevideo',
'Urumqi' => 'Asia/Urumqi',
'Uzbekistan' => 'Asia/Tashkent',
'Vanuatu' => 'Pacific/Efate',
'Venezuela' => 'America/Caracas',
'Venezuela Standard Time' => 'America/Caracas',
'Vladivostok' => 'Asia/Vladivostok',
'Vladivostok Standard Time' => 'Asia/Vladivostok',
'Volgograd' => 'Europe/Volgograd',
'Vostok' => 'Antarctica/Vostok',
'W. Australia Standard Time' => 'Australia/Perth',
'W. Central Africa Standard Time' => 'Africa/Lagos',
'W. Europe Standard Time' => 'Europe/Berlin',
'Wake' => 'Pacific/Wake',
'Wallis' => 'Pacific/Wallis',
'West Asia Standard Time' => 'Asia/Tashkent',
'West Pacific Standard Time' => 'Pacific/Port_Moresby',
'Yakutsk' => 'Asia/Yakutsk',
'Yakutsk Standard Time' => 'Asia/Yakutsk',
'Yekaterinburg' => 'Asia/Yekaterinburg',
'Yerevan' => 'Asia/Yerevan',
'Yukon' => 'America/Yakutat',
);
/**
* @var array Map of timezones acceptable by DateTimeZone but not strtotime.
*/
protected $_invalid_legacy = array(
'US/Eastern' => true,
);
/**
* @var array|bool List of system identifiers or false if none available.
*/
protected $_identifiers = false;
/**
* Initialize local cache and identifiers.
*
* @param Ai1ec_Registry_Object $registry Registry to use.
*
* @return void
*/
public function __construct( Ai1ec_Registry_Object $registry ) {
parent::__construct( $registry );
$this->_cache = $this->_registry->get( 'cache.memory' );
$this->_init_identifiers();
}
/**
* Get default timezone to use in input/output.
*
* Approach is as follows:
* - check user profile for timezone preference;
* - if user has no preference - check site for timezone selection;
* - if site has no selection - raise notice and use 'UTC'.
*
* @return string Olson timezone string identifier.
*/
public function get_default_timezone() {
static $default_timezone = null;
if ( null === $default_timezone ) {
$candidates = array();
$candidates[] = (string)$this->_registry->get( 'model.meta-user' )
->get_current( 'ai1ec_timezone' );
$candidates[] = (string)$this->_registry->get( 'model.option' )
->get( 'timezone_string' );
$candidates[] = (string)$this->_registry->get( 'model.option' )
->get( 'gmt_offset' );
$candidates = array_filter( $candidates, 'strlen' );
foreach ( $candidates as $timezone ) {
$timezone = $this->get_name( $timezone );
if ( false !== $timezone ) {
$default_timezone = $timezone;
break;
}
}
if ( null === $default_timezone ) {
$default_timezone = 'UTC';
$this->_registry->get( 'notification.admin' )->store(
sprintf(
Ai1ec_I18n::__(
'Please select site timezone in %s <em>Timezone</em> dropdown menu.'
),
'<a href="' . ai1ec_admin_url( 'options-general.php' ) .
'">' . Ai1ec_I18n::__( 'Settings' ) . '</a>'
),
'error'
);
}
}
return $default_timezone;
}
/**
* Attempt to decode GMT offset to some Olson timezone.
*
* @param float $zone GMT offset.
*
* @return string Valid Olson timezone name (UTC is last resort).
*/
public function decode_gmt_timezone( $zone ) {
$auto_zone = timezone_name_from_abbr( null, $zone * 3600, true );
if ( false !== $auto_zone ) {
return $auto_zone;
}
$auto_zone = timezone_name_from_abbr(
null,
( (int) $zone ) * 3600,
true
);
if ( false !== $auto_zone ) {
return $auto_zone;
}
$this->_registry->get( 'notification.admin' )->store(
sprintf(
Ai1ec_I18n::__(
'Timezone "UTC%+d" is not recognized. Please %suse valid%s timezone name, until then events will be created in UTC timezone.'
),
$zone,
'<a href="' . ai1ec_admin_url( 'options-general.php' ) . '">',
'</a>'
),
'error'
);
return 'UTC';
}
/**
* Get valid timezone name from input.
*
* @param string $zone Name to check/parse.
*
* @return string Timezone name to use
*/
public function get_name( $zone ) {
if ( is_numeric( $zone ) ) {
$decoded_zone = $this->decode_gmt_timezone( $zone );
if ( 'UTC' !== $decoded_zone ) {
$message = sprintf(
Ai1ec_I18n::__(
'Selected timezone "UTC%+d" will be treated as %s.'
),
$zone,
$decoded_zone
);
$this->_registry->get( 'notification.admin' )
->store( $message );
}
$zone = $decoded_zone;
}
if ( false === $this->_identifiers ) {
return $zone; // anything should do, as zones are not supported
}
if ( ! isset( $this->_identifiers[$zone] ) ) {
$zone = $this->_olson_lookup( $zone );
$valid_legacy = false;
try {
new DateTimeZone( $zone ); // throw away instantly
$valid_legacy = true;
} catch ( Exception $excpt ) {
$valid_legacy = false;
}
if ( ! $valid_legacy || isset( $this->_invalid_legacy[$zone] ) ) {
return $this->guess_zone( $zone );
}
$this->_identifiers[$zone] = $zone;
unset( $valid_legacy );
}
return $zone;
}
/**
* Quick map look-up to discard zones that have limited recognition.
*
* @param string $zone Name of timezone to lookup.
*
* @return string Timezone name to use. Might be the same as $zone.
*/
protected function _olson_lookup( $zone ) {
if ( isset( $this->_zones[$zone] ) ) {
return $this->_zones[$zone];
}
return $zone;
}
/**
* Check if timezone is set in wp_option
*
*/
public function is_timezone_not_set() {
$timezone = $this->_registry->get( 'model.option' )
->get( 'timezone_string' );
return empty( $timezone );
}
/**
* Render options for select in settings
*
* @return array
*/
public function get_timezones( $only_zones = false ) {
$zones = DateTimeZone::listIdentifiers();
if (
empty( $zones )
) {
return array();
}
if ( ! $only_zones ) {
$manual = __( 'Manual Offset', AI1EC_PLUGIN_NAME );
$options = array();
$options[$manual][] = array(
'text' => __( 'Choose your timezone', AI1EC_PLUGIN_NAME ),
'value' => '',
'args' => array(
'selected' => 'selected'
)
);
}
foreach ( $zones as $zone ) {
$exploded_zone = explode( '/', $zone );
if ( ! isset( $exploded_zone[1] ) && ! $only_zones ) {
$exploded_zone[1] = $exploded_zone[0];
$exploded_zone[0] = $manual;
}
$optgroup = $exploded_zone[0];
unset( $exploded_zone[0] );
$options[$optgroup][] = array(
'text' => implode( '/', $exploded_zone ),
'value' => $zone,
);
}
return $options;
}
/**
* Guess valid timezone identifier from arbitrary input.
*
* @param string $meta_name Arbitrary input.
*
* @return string|bool Parsed timezone name or false if none found.
*/
public function guess_zone( $meta_name ) {
if ( isset( $this->_zones[$meta_name] ) ) {
return $this->_zones[$meta_name];
}
$name_variants = array(
strtr( $meta_name, ' ', '_' ),
strtr( $meta_name, '_', ' ' ),
);
if ( false !== ( $parenthesis_pos = strpos( $meta_name, '(' ) ) ) {
foreach ( $name_variants as $name ) {
$name_variants[] = substr( $name, 0, $parenthesis_pos - 1 );
}
}
foreach ( $name_variants as $name ) {
if ( isset( $this->_zones[$name] ) ) {
// cache to avoid future lookups and return
$this->_zones[$meta_name] = $this->_zones[$name];
return $this->_zones[$name];
}
}
if (
isset( $meta_name{0} ) &&
'(' === $meta_name{0} &&
$closing_pos = strpos( $meta_name, ')' )
) {
$meta_name = trim( substr( $meta_name, $closing_pos + 1 ) );
return $this->guess_zone( $meta_name );
}
if (
false === strpos( $meta_name, ' Standard ' ) &&
false !== ( $time_pos = strpos( $meta_name, ' Time' ) )
) {
$meta_name = substr( $meta_name, 0, $time_pos ) .
' Standard' . substr( $meta_name, $time_pos );
return $this->guess_zone( $meta_name );
}
return false;
}
/**
* Get timezone object instance.
*
* @param string $timezone Name of timezone to get instance for.
*
* @return DateTimeZone Instance of timezone object.
*
* @throws Ai1ec_Date_Timezone_Exception If an error occurs.
*/
public function get( $timezone ) {
if ( 'sys.default' === $timezone ) {
$timezone = $this->get_default_timezone();
}
$name = $this->get_name( $timezone );
if ( ! $name ) {
$name = $this->get_name( $this->get_default_timezone() );
}
$zone = $this->_cache->get( $name, null );
if ( null === $zone ) {
$exception = null;
try {
$zone = new DateTimeZone( $name );
} catch ( Exception $invalid_tz ) {
$exception = $invalid_tz;
}
if ( null !== $exception ) {
throw new Ai1ec_Date_Timezone_Exception( $exception->getMessage() );
}
$this->_cache->set( $name, $zone );
}
return $zone;
}
/**
* Add system identifiers to object registry.
*
* @return bool Success
*/
protected function _init_identifiers() {
$identifiers = DateTimeZone::listIdentifiers();
if ( ! $identifiers ) {
return false;
}
$mapped = array();
foreach ( $identifiers as $zone ) {
$zone = (string)$zone;
$mapped[$zone] = true;
$this->_zones[$zone] = $zone;
}
unset( $identifiers, $zone );
$this->_identifiers = $mapped;
return true;
}
}

View File

@@ -0,0 +1,155 @@
<?php
/**
* Validation utility library
*
* @author Timely Network Inc
* @since 2012.08.21
*
* @package AllInOneCalendar
* @subpackage AllInOneCalendar.Lib.Utility
*/
class Ai1ec_Validation_Utility {
/**
* Check if the date supplied is valid. It validates $date in the format given
* by $pattern, which matches one of the supported date patterns.
*
* @param string $date Date string to validate
* @param  string $pattern Key of date pattern (@see
* self::get_date_patterns()) to
* match date string against
* @return boolean
*/
static public function validate_date( $date, $pattern = 'def' ) {
$result = self::validate_date_and_return_parsed_date( $date, $pattern );
if( $result === false ) {
return false;
}
return true;
}
/**
* Check if the date supplied is valid. It validates date in the format given
* by $pattern, which matches one of the supported date patterns.
*
* @param string $date Date string to parse
* @param string $pattern Key of date pattern (@see
* self::get_date_patterns()) to
* match date string against
* @return array|boolean An array with the parsed date or false if the date
* is not valid.
*/
static public function validate_date_and_return_parsed_date(
$date, $pattern = 'def'
) {
$pattern = self::_get_pattern_regexp( $pattern );
if ( preg_match( $pattern, $date, $matches ) ) {
if ( checkdate( $matches['m'], $matches['d'], $matches['y'] ) ) {
return array(
'month' => $matches['m'],
'day' => $matches['d'],
'year' => $matches['y'],
);
}
}
return false;
}
/**
* Convert input into a valid ISO date.
*
* @param string $date Date to convert to ISO.
* @param string $pattern Format used to store it.
*
* @return string|bool Re-formatted date or false on failure.
*/
static public function format_as_iso( $date, $pattern = 'def' ) {
$regexp = self::_get_pattern_regexp( $pattern );
if ( ! preg_match( $regexp, $date, $matches ) ) {
return false;
}
return sprintf(
'%04d-%02d-%02d',
$matches['y'],
$matches['m'],
$matches['d']
);
}
/**
* Create regexp with named groups to match positional elements.
*
* @param string $pattern Pattern to convert.
*
* @return string Regular expression pattern.
*/
static protected function _get_pattern_regexp( $pattern ) {
$pattern = self::get_date_pattern_by_key( $pattern );
$pattern = preg_quote( $pattern, '/' );
$pattern = str_replace(
array( 'dd', 'd', 'mm', 'm', 'yyyy', 'yy' ),
array( '(?P<d>\d{2})', '(?P<d>\d{1,2})', '(?P<m>\d{2})', '(?P<m>\d{1,2})', '(?P<y>\d{4})', '(?P<y>\d{2})' ),
$pattern
);
// Accept hyphens and dots in place of forward slashes (for URLs).
$pattern = str_replace( '\/', '[\/\-\.]', $pattern );
return '#^' . $pattern . '$#';
}
/**
* Check if the string or integer is a valid timestamp.
*
* @see http://stackoverflow.com/questions/2524680/check-whether-the-string-is-a-unix-timestamp
* @param string|int $timestamp
* @return boolean
*/
static public function is_valid_time_stamp( $timestamp ) {
return
(
is_int( $timestamp ) ||
( (string)(int)$timestamp ) === (string)$timestamp
)
&& ( $timestamp <= PHP_INT_MAX )
&& ( $timestamp >= 0 /*~ PHP_INT_MAX*/ );
// do not allow negative timestamps until this is widely accepted
}
/**
* Returns the associative array of date patterns supported by the plugin,
* currently:
* array(
* 'def' => 'd/m/yyyy',
* 'us' => 'm/d/yyyy',
* 'iso' => 'yyyy-m-d',
* 'dot' => 'm.d.yyyy',
* );
*
* 'd' or 'dd' represent the day, 'm' or 'mm' represent the month, and 'yy'
* or 'yyyy' represent the year.
*
* @return array Supported date patterns
*/
static public function get_date_patterns() {
return array(
'def' => 'd/m/yyyy',
'us' => 'm/d/yyyy',
'iso' => 'yyyy-m-d',
'dot' => 'm.d.yyyy',
);
}
/**
* Returns the date pattern (in the form 'd-m-yyyy', for example) associated
* with the provided key, used by plugin settings. Simply a static map as
* follows:
*
* @param string $key Key for the date format
* @return string Associated date format pattern
*/
static public function get_date_pattern_by_key( $key = 'def' ) {
$patterns = self::get_date_patterns();
return $patterns[$key];
}
}

View File

@@ -0,0 +1,25 @@
<?php
/**
* DBI utils.
*
* @author Time.ly Network Inc.
* @since 2.2
*
* @package AI1EC
* @subpackage AI1EC.Dbi
*/
class Ai1ec_Dbi_Utils extends Ai1ec_Base {
/**
* Returns SQL string for INSERT statement.
*
* @param array $value Array of values.
*
* @return string SQL statement.
*/
public function array_value_to_sql_value( array $value ) {
return '(' . implode( ',', array_values( $value ) ) . ')';
}
}

View File

@@ -0,0 +1,453 @@
<?php
/**
* Wrapper for WPDB (WordPress DB Class)
*
* Thic class wrap the access to WordPress DB class ($wpdb) and
* allows us to abstract from the WordPress code and to expand it
* with convenience method specific for ai1ec
*
* @author Time.ly Network, Inc.
* @since 2.0
* @package Ai1EC
* @subpackage Ai1EC.Dbi
*/
class Ai1ec_Dbi {
/**
* @var Ai1ec_Registry_Object Instance of object registry.
*/
protected $_registry = null;
/**
* @var wpdb Instance of database interface object
*/
protected $_dbi = null;
/**
* @var array Queries executed for log.
*/
protected $_queries = array();
/**
* @var bool Set to true when logging is enabled.
*/
protected $_log_enabled = false;
/**
* Constructor assigns injected database access object to class variable.
*
* @param Ai1ec_Registry_Object $registry Injected registry.
* @param wpdb $dbi Injected database access object.
*
* @return void Constructor does not return.
*/
public function __construct(
Ai1ec_Registry_Object $registry,
$dbi = null
) {
if ( null === $dbi ) {
global $wpdb;
$dbi = $wpdb;
}
$this->_dbi = $dbi;
$this->_registry = $registry;
$this->_registry->get( 'controller.shutdown' )->register(
array( $this, 'shutdown' )
);
add_action(
'ai1ec_loaded',
array( $this, 'check_debug' ),
PHP_INT_MAX
);
$this->set_timezone();
}
/**
* Set timezone to UTC to avoid conversion errors.
*
* @return void
*/
public function set_timezone() {
$this->_dbi->query( "SET time_zone = '+0:00'" );
}
/**
* Call explicitly when debug output must be disabled.
*
* @return void Method is not meant to return.
*/
public function disable_debug() {
$this->_log_enabled = false;
}
/**
* Only attempt to enable debug after all add-ons are loaded.
*
* @wp_hook ai1ec_loaded
*
* @uses apply_filters ai1ec_dbi_debug
*
* @return void
*/
public function check_debug() {
$this->_log_enabled = apply_filters(
'ai1ec_dbi_debug',
( false !== AI1EC_DEBUG )
);
}
/**
* Perform a MySQL database query, using current database connection.
*
* @param string $sql_query Database query
*
* @return int|false Number of rows affected/selected or false on error
*/
public function query( $sql_query ) {
$this->_query_profile( $sql_query );
$result = $this->_dbi->query( $sql_query );
$this->_query_profile( $result );
return $result;
}
/**
* Retrieve one column from the database.
*
* Executes a SQL query and returns the column from the SQL result.
* If the SQL result contains more than one column, this function returns the column specified.
* If $query is null, this function returns the specified column from the previous SQL result.
*
* @param string|null $query Optional. SQL query. Defaults to previous query.
* @param int $col Optional. Column to return. Indexed from 0.
*
* @return array Database query result. Array indexed from 0 by SQL result row number.
*/
public function get_col( $query = null , $col = 0 ) {
$this->_query_profile( $query );
$result = $this->_dbi->get_col( $query, $col );
$this->_query_profile( count( $result ) );
return $result;
}
/**
* Check if the terms variable is set in the Wpdb object
*/
public function are_terms_set() {
return isset( $this->_dbi->terms );
}
/**
* Prepares a SQL query for safe execution. Uses sprintf()-like syntax.
*
* The following directives can be used in the query format string:
* %d (integer)
* %f (float)
* %s (string)
* %% (literal percentage sign - no argument needed)
*
* All of %d, %f, and %s are to be left unquoted in the query string and they need an argument passed for them.
* Literals (%) as parts of the query must be properly written as %%.
*
* This function only supports a small subset of the sprintf syntax; it only supports %d (integer), %f (float), and %s (string).
* Does not support sign, padding, alignment, width or precision specifiers.
* Does not support argument numbering/swapping.
*
* May be called like {@link http://php.net/sprintf sprintf()} or like {@link http://php.net/vsprintf vsprintf()}.
*
* Both %d and %s should be left unquoted in the query string.
*
* @param string $query Query statement with sprintf()-like placeholders
* @param array|mixed $args The array of variables to substitute into the query's placeholders if being called like
* {@link http://php.net/vsprintf vsprintf()}, or the first variable to substitute into the query's placeholders if
* being called like {@link http://php.net/sprintf sprintf()}.
* @param mixed $args,... further variables to substitute into the query's placeholders if being called like
* {@link http://php.net/sprintf sprintf()}.
*
* @return null|false|string Sanitized query string, null if there is no query, false if there is an error and string
* if there was something to prepare
*/
public function prepare( $query, $args ) {
if ( null === $query ) {
return null;
}
$args = func_get_args();
array_shift( $args );
// If args were passed as an array (as in vsprintf), move them up
if ( isset( $args[0] ) && is_array( $args[0] ) ) {
$args = $args[0];
}
$query = str_replace( "'%s'", '%s', $query ); // in case someone mistakenly already singlequoted it
$query = str_replace( '"%s"', '%s', $query ); // doublequote unquoting
$query = preg_replace( '|(?<!%)%f|', '%F', $query ); // Force floats to be locale unaware
$query = preg_replace( '|(?<!%)%s|', "'%s'", $query ); // quote the strings, avoiding escaped strings like %%s
array_walk( $args, array( $this->_dbi, 'escape_by_ref' ) );
return @vsprintf( $query, $args );
}
/**
* Retrieve an entire SQL result set from the database (i.e., many rows)
*
* Executes a SQL query and returns the entire SQL result.
*
* @param string $query SQL query.
* @param string $output Optional. Any of ARRAY_A | ARRAY_N | OBJECT | OBJECT_K constants. With one of the first three, return an array of rows indexed from 0 by SQL result row number.
* Each row is an associative array (column => value, ...), a numerically indexed array (0 => value, ...), or an object. ( ->column = value ), respectively.
* With OBJECT_K, return an associative array of row objects keyed by the value of each row's first column's value. Duplicate keys are discarded.
*
* @return mixed Database query results
*/
public function get_results( $query, $output = OBJECT ){
$this->_query_profile( $query );
$result = $this->_dbi->get_results( $query, $output );
$this->_query_profile( count( $result ) );
return $result;
}
/**
* Retrieve one variable from the database.
*
* Executes a SQL query and returns the value from the SQL result.
* If the SQL result contains more than one column and/or more than one row, this function returns the value in the column and row specified.
* If $query is null, this function returns the value in the specified column and row from the previous SQL result.
*
* @param string|null $query SQL query. Defaults to null, use the result from the previous query.
* @param int $col Column of value to return. Indexed from 0.
* @param int $row Row of value to return. Indexed from 0.
*
* @return string|null Database query result (as string), or null on failure
*/
public function get_var( $query = null, $col = 0, $row = 0 ) {
$this->_query_profile( $query );
$result = $this->_dbi->get_var( $query, $col, $row );
$this->_query_profile( null !== $result );
return $result;
}
/**
* Retrieve one row from the database.
*
* Executes a SQL query and returns the row from the SQL result
*
* @param string|null $query SQL query.
* @param string $output Optional. one of ARRAY_A | ARRAY_N | OBJECT constants. Return an associative array (column => value, ...),
* a numerically indexed array (0 => value, ...) or an object ( ->column = value ), respectively.
* @param int $row Optional. Row to return. Indexed from 0.
*
* @return mixed Database query result in format specified by $output or null on failure
*/
public function get_row( $query = null, $output = OBJECT, $row = 0 ) {
$this->_query_profile( $query );
$result = $this->_dbi->get_row( $query, $output, $row );
$this->_query_profile( null !== $result );
return $result;
}
/**
* Insert a row into a table.
*
* @param string $table table name
* @param array $data Data to insert (in column => value pairs). Both $data columns and $data values should be "raw" (neither should be SQL escaped).
* @param array|string $format Optional. An array of formats to be mapped to each of the value in $data. If string, that format will be used for all of the values in $data.
* A format is one of '%d', '%f', '%s' (integer, float, string). If omitted, all values in $data will be treated as strings unless otherwise specified in wpdb::$field_types.
*
* @return int|false The number of rows inserted, or false on error.
*/
public function insert( $table, $data, $format = null ) {
$this->_query_profile(
'INSERT INTO ' . $table . '; data: ' . json_encode( $data )
);
$result = $this->_dbi->insert(
$this->get_table_name( $table ),
$data,
$format
);
$this->_query_profile( $result );
return $result;
}
/**
* Perform removal from table.
*
* @param string $table Table to remove from.
* @param array $where Where conditions
* @param array $format Format entities for where.
*
* @return int|false Number of rows deleted or false.
*/
public function delete( $table, $where, $format = null ) {
$this->_query_profile(
'DELETE FROM ' . $table . '; conditions: ' . json_encode( $where )
);
$result = $this->_dbi->delete(
$this->get_table_name( $table ),
$where,
$format
);
$this->_query_profile( $result );
return $result;
}
/**
* Update a row in the table
*
* @param string $table table name
* @param array $data Data to update (in column => value pairs). Both $data columns and $data values should be "raw" (neither should be SQL escaped).
* @param array $where A named array of WHERE clauses (in column => value pairs). Multiple clauses will be joined with ANDs. Both $where columns and $where values should be "raw".
* @param array|string $format Optional. An array of formats to be mapped to each of the values in $data. If string, that format will be used for all of the values in $data.
* A format is one of '%d', '%f', '%s' (integer, float, string). If omitted, all values in $data will be treated as strings unless otherwise specified in wpdb::$field_types.
* @param array|string $where_format Optional. An array of formats to be mapped to each of the values in $where. If string, that format will be used for all of the items in $where. A format is one of '%d', '%f', '%s' (integer, float, string). If omitted, all values in $where will be treated as strings.
*
* @return int|false The number of rows updated, or false on error.
*/
public function update( $table, $data, $where, $format = null, $where_format = null ) {
$this->_query_profile( 'UPDATE ' . $table . ': ' . implode( '//', $data ) );
$result = $this->_dbi->update( $table, $data, $where, $format, $where_format );
$this->_query_profile( $result );
return $result;
}
/**
* Retrieve all results from given table.
*
* @param string $table Name of table.
* @param array $columns List of columns to retrieve.
* @param string $output See {@see self::get_results()} $output for more.
*
* @return array Collection.
*/
public function select( $table, array $columns, $output = OBJECT ) {
$sql_query = 'SELECT `' . implode( '`, `', $columns ) . '` FROM `' .
$this->get_table_name( $table ) . '`';
return $this->get_results( $sql_query, $output );
}
/**
* The database version number.
*
* @return false|string false on failure, version number on success
*/
public function db_version() {
return $this->_dbi->db_version();
}
/**
* Return the id of last `insert` operation.
*
* @return int Returns integer optionally zero when no insert was performed.
*/
public function get_insert_id() {
return $this->_dbi->insert_id;
}
/**
* Return the full name for the table.
*
* @param string $table Table name.
*
* @return string Full table name for the table requested.
*/
public function get_table_name( $table = '' ) {
static $prefix_len = null;
if ( ! isset( $this->_dbi->{$table} ) ) {
if ( null === $prefix_len ) {
$prefix_len = strlen( $this->_dbi->prefix );
}
if ( 0 === strncmp( $this->_dbi->prefix, $table, $prefix_len ) ) {
return $table;
}
return $this->_dbi->prefix . $table;
}
return $this->_dbi->{$table};
}
/**
* Return escaped value.
*
* @param string $input Value to be escaped.
*
* @return string Escaped value.
*/
public function escape( $input ) {
$this->_dbi->escape_by_ref( $input );
return $input;
}
/**
* In debug mode prints DB queries table.
*
* @return void
*/
public function shutdown() {
if ( ! $this->_log_enabled ) {
return false;
}
echo '<div class="timely timely-debug">
<table class="table table-striped">
<thead>
<tr>
<th>N.</th>
<th>Query</th>
<th>Duration, ms</th>
<th>Row Count</th>
</tr>
</thead>
<tbody>';
$i = 0;
$time = 0;
foreach ( $this->_queries as $query ) {
$time += $query['d'];
echo '<tr>
<td>', ++$i, '</td>
<td>', $query['q'], '</td>
<td>', round( $query['d'] * 1000, 2 ), '</td>
<td>', (int)$query['r'], '</td>
</tr>';
}
echo '
</tbody>
<tfoot>
<tr>
<th colspan="4">Total time, ms: ',
round( $time * 1000, 2 ), '</th>
</tr>
</tfoot>
</table>
</div>';
return true;
}
/**
* Method aiding query profiling.
*
* How to use:
* - on method resulting in query start call _query_profiler( 'SQL query' )
* - on it's end call _query_profiler( (int)number_of_rows|(bool)false )
*
* @param mixed $query_or_result Query on first call, result on second.
*
* @return void
*/
protected function _query_profile( $query_or_result ) {
static $last = null;
if ( null === $last ) {
$last = array(
'd' => microtime( true ),
'q' => $query_or_result,
);
} else {
if ( count( $this->_queries ) > 200 ) {
array_shift( $this->_queries );
}
$this->_queries[] = array(
'd' => microtime( true ) - $last['d'],
'q' => $last['q'],
'r' => $query_or_result,
);
$last = null;
}
}
}

View File

@@ -0,0 +1,269 @@
<?php
/**
* Checks configurations and notifies admin.
*
* @author Time.ly Network Inc.
* @since 2.0
*
* @package AI1EC
* @subpackage AI1EC.Lib
*/
class Ai1ec_Environment_Checks extends Ai1ec_Base {
const CORE_NAME = 'all-in-one-event-calendar/all-in-one-event-calendar.php';
/**
* List of dependencies.
*
* @var array
*/
protected $_addons = array(
'all-in-one-event-calendar-extended-views/all-in-one-event-calendar-extended-views.php' => '1.1.3',
'all-in-one-event-calendar-super-widget/all-in-one-event-calendar-super-widget.php' => '1.1.0',
'all-in-one-event-calendar-featured-events/all-in-one-event-calendar-featured-events.php' => '1.0.5',
'all-in-one-event-calendar-frontend-submissions/all-in-one-event-calendar-frontend-submissions.php' => '1.1.3',
);
/**
* Runs checks for necessary config options.
*
* @return void Method does not return.
*/
public function run_checks() {
$role = get_role( 'administrator' );
$current_user = get_userdata( get_current_user_id() );
if (
! is_object( $role ) ||
! is_object( $current_user ) ||
! $role->has_cap( 'manage_ai1ec_options' ) ||
(
defined( 'DOING_AJAX' ) &&
DOING_AJAX
)
) {
return;
}
do_action( 'ai1ec_env_check' );
global $plugin_page;
$settings = $this->_registry->get( 'model.settings' );
$option = $this->_registry->get( 'model.option' );
$notification = $this->_registry->get( 'notification.admin' );
$created_calendar_page = false;
// check if is set calendar page
if ( ! $settings->get( 'calendar_page_id' ) ) {
$calendar_page_id = wp_insert_post(
array(
'post_title' => 'Calendar',
'post_type' => 'page',
'post_status' => 'publish',
'comment_status' => 'closed'
)
);
$settings->set( 'calendar_page_id', $calendar_page_id );
$created_calendar_page = true;
}
if (
$plugin_page !== AI1EC_PLUGIN_NAME . '-settings' &&
$created_calendar_page
) {
if (
$current_user->has_cap( 'manage_ai1ec_options' )
) {
$msg = sprintf(
Ai1ec_I18n::__( 'The plugin is successfully installed! <a href="%s">Add some events</a> and see them on your <a href="%s">Calendar page</a>.<br />Visit the <a href="%s">Settings page</a> to configure the plugin and get most of it.' ),
'post-new.php?post_type=ai1ec_event',
get_page_link( $calendar_page_id ),
ai1ec_admin_url( AI1EC_SETTINGS_BASE_URL )
);
$notification->store(
$msg,
'updated',
2,
array( Ai1ec_Notification_Admin::RCPT_ADMIN )
);
} else {
$msg = Ai1ec_I18n::__(
'The plugin is installed, but has not been configured. Please log in as an Administrator to set it up.'
);
$notification->store(
$msg,
'updated',
2,
array( Ai1ec_Notification_Admin::RCPT_ALL )
);
}
return;
}
// Tell user to sign in to API in order to use Feeds
$ics_current_db_version = $option->get( Ai1ecIcsConnectorPlugin::ICS_OPTION_DB_VERSION );
if ( $ics_current_db_version != null && $ics_current_db_version != '' ) {
$rows = $this->_registry->get( 'dbi.dbi' )->select(
'ai1ec_event_feeds',
array( 'feed_id' )
);
$api_reg = $this->_registry->get( 'model.api.api-registration' );
$is_signed = $api_reg->is_signed();
if ( 0 < count( $rows ) && ! $is_signed ) {
$msg = Ai1ec_I18n::__(
'<b>ACTION REQUIRED!</b> Please, <a href="edit.php?post_type=ai1ec_event&page=all-in-one-event-calendar-settings">sign</a> into Timely Network to continue syncing your imported events.'
);
$notification->store(
$msg,
'error',
0,
array( Ai1ec_Notification_Admin::RCPT_ADMIN ),
false
);
}
}
// Check for needed PHP extensions.
if (
! function_exists( 'iconv' ) &&
! $option->get( 'ai1ec_iconv_notification' )
) {
$msg = Ai1ec_I18n::__(
'PHP extension "iconv" needed for All-In-One-Event-Calendar is missing. Please, check your PHP configuration.<br />'
);
$notification->store(
$msg,
'error',
0,
array( Ai1ec_Notification_Admin::RCPT_ADMIN ),
true
);
$option->set( 'ai1ec_iconv_notification', true );
}
if (
! function_exists( 'mb_check_encoding' ) &&
! $option->get( 'ai1ec_mbstring_notification' )
) {
$msg = Ai1ec_I18n::__(
'PHP extension "mbstring" needed for All-In-One-Event-Calendar is missing. Please, check your PHP configuration.<br />'
);
$notification->store(
$msg,
'error',
0,
array( Ai1ec_Notification_Admin::RCPT_ADMIN ),
true
);
$option->set( 'ai1ec_mbstring_notification', true );
}
global $wp_rewrite;
$option = $this->_registry->get( 'model.option' );
$rewrite = $option->get( 'ai1ec_force_flush_rewrite_rules' );
if (
! $rewrite ||
! is_object( $wp_rewrite ) ||
! isset( $wp_rewrite->rules ) ||
! is_array ( $wp_rewrite->rules ) ||
0 === count( $wp_rewrite->rules )
) {
return;
}
$this->_registry->get( 'rewrite.helper' )->flush_rewrite_rules();
$option->set( 'ai1ec_force_flush_rewrite_rules', false );
}
/**
* Checks for add-on versions.
*
* @param string $plugin Plugin name.
*
* @return void Method does not return.
*/
public function check_addons_activation( $plugin ) {
switch ( $plugin ) {
case self::CORE_NAME:
$this->_check_active_addons();
break;
default:
$min_version = isset( $this->_addons[$plugin] )
? $this->_addons[$plugin]
: null;
if ( null !== $min_version ) {
$this->_plugin_activation( $plugin, $min_version );
}
break;
}
}
/**
* Launches after bulk update.
*
* @param bool $result Input filter value.
*
* @return bool Output filter value.
*/
public function check_bulk_addons_activation( $result ) {
$this->_check_active_addons( true );
return $result;
}
/**
* Checks all Time.ly addons.
*
* @param bool $silent Whether to perform silent plugin deactivation or not.
*
* @return void Method does not return.
*/
protected function _check_active_addons( $silent = false ) {
foreach ( $this->_addons as $addon => $version ) {
if ( is_plugin_active( $addon ) ) {
$this->_plugin_activation( $addon, $version, true, $silent );
}
}
}
/**
* Performs Extended Views version check.
*
* @param string $addon Addon identifier.
* @param string $min_version Minimum required version.
* @param bool $core If set to true Core deactivates active and
* outdated addons when it is activated. If set
* false it means that addon activation process
* called this method and it's enough to throw
* and exception and allow exception handler
* to deactivate addon with proper notices.
* @param bool $silent Whether to perform silent plugin deactivation
* or not.
*
* @return void Method does not return.
*
* @throws Ai1ec_Bootstrap_Exception
* @throws Ai1ec_Outdated_Addon_Exception
*/
protected function _plugin_activation(
$addon,
$min_version,
$core = false,
$silent = false
) {
$ev_data = get_plugin_data(
WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . $addon
);
if ( ! isset( $ev_data['Version'] ) ) {
return;
}
$version = $ev_data['Version'];
if ( -1 === version_compare( $version, $min_version ) ) {
$msg1 = Ai1ec_I18n::__( 'The add-on <strong>%s</strong> must be updated to at least version %s to maintain compatibility with the core calendar.' );
$msg2 = Ai1ec_I18n::__( 'If you do not see update notices below, ensure you have properly <a href="https://time.ly/document/user-guide/getting-started/license-keys/" target="_blank">entered your licence keys</a>. Alternatively, navigate to <a href="https://time.ly/your-account/">your account</a> to download the latest version of the add-on(s) and <a href="https://time.ly/document/user-guide/troubleshooting/perform-manual-upgrade/">update manually</a>. Please <a href="https://time.ly/forums/">post in the forum</a> if you have trouble. We are happy to help.' );
$message = sprintf(
'<span class="highlight" style="margin: 0 -6px; padding: 4px 6px">' .
$msg1 . '</span></p><p>' . $msg2,
$ev_data['Name'],
$min_version
);
$this->_registry->get( 'calendar.updates' )->clear_transients();
throw new Ai1ec_Outdated_Addon_Exception( $message, $addon );
}
}
}

View File

@@ -0,0 +1,60 @@
<?php
/**
* The exception thrown when value doesn't pass validation.
*
* @author Time.ly Network Inc.
* @since 2.2
*
* @package AI1EC
* @subpackage AI1EC.Lib
*/
class Ai1ec_Outdated_Addon_Exception extends Ai1ec_Exception {
protected $_addon;
/**
* Constructor.
*
* @param string $message Exception message.
* @param string $addon Addon to disable.
*
* @return void Method does not return.
*/
public function __construct( $message, $addon ) {
parent::__construct( $message );
$this->_addon = $addon;
}
/**
* Returns addon name.
*
* @return string Addon name.
*/
public function plugin_to_disable() {
return $this->_addon;
}
/**
* Overrides __toString() to avoid stack trace.
*
* @return string Empty string.
*/
public function __toString() {
return '';
}
/**
* @see Ai1ec_Exception::get_redirect_url()
*/
public function get_redirect_url() {
return ai1ec_admin_url( 'plugins.php' );
}
/**
* @see Ai1ec_Exception::display_backtrace()
*/
public function display_backtrace(){
return false;
}
}

View File

@@ -0,0 +1,62 @@
<?php
/**
* Abstract Class for Callback Events.
*
* @author Time.ly Network Inc.
* @since 2.0
*
* @package AI1EC
* @subpackage AI1EC.Event
*/
abstract class Ai1ec_Event_Callback_Abstract {
/**
* @var Ai1ec_Registry_Object The Object registry.
*/
protected $_registry = null;
/**
* @var string The registry method name defined in the class map.
*/
protected $_registry_name = null;
/**
* @var string The method invoked by the current callback.
*/
protected $_method = null;
/**
* Initiate callback objects.
*
* @param Ai1ec_Registry_Object $registry Registry object.
* @param string $path Registry method name defined in the class map.
* @param string $method Method invoked by the currect callback.
*
* @return void Constructor does not return.
*/
public function __construct(
Ai1ec_Registry_Object $registry,
$path,
$method
) {
$this->_registry = $registry;
$this->_registry_name = $path;
$this->_method = $method;
}
/**
* Invoke the method added to the current callback.
*
* @return mixed Value returned by the current method.
*/
public function run() {
$argv = func_get_args();
return $this->_registry->dispatch(
$this->_registry_name,
$this->_method,
$argv
);
}
}

View File

@@ -0,0 +1,14 @@
<?php
/**
* Event Callback Action creation.
*
* @author Time.ly Network Inc.
* @since 2.0
*
* @instantiator new
* @package AI1EC
* @subpackage AI1EC.Event
*/
class Ai1ec_Event_Callback_Action extends Ai1ec_Event_Callback_Abstract {
}

View File

@@ -0,0 +1,14 @@
<?php
/**
* Event Callback Filter creation.
*
* @author Time.ly Network Inc.
* @since 2.0
*
* @instantiator new
* @package AI1EC
* @subpackage AI1EC.Event
*/
class Ai1ec_Event_Callback_Filter extends Ai1ec_Event_Callback_Abstract {
}

View File

@@ -0,0 +1,14 @@
<?php
/**
* Event Callback Shortcode creation.
*
* @author Time.ly Network Inc.
* @since 2.0
*
* @instantiator new
* @package AI1EC
* @subpackage AI1EC.Event
*/
class Ai1ec_Event_Callback_Shortcode extends Ai1ec_Event_Callback_Abstract {
}

View File

@@ -0,0 +1,145 @@
<?php
/**
* Event Dispatcher processing.
*
* @author Time.ly Network Inc.
* @since 2.0
*
* @package AI1EC
* @subpackage AI1EC.Event
*/
class Ai1ec_Event_Dispatcher extends Ai1ec_Base {
/**
* Register callback object.
*
* @param string $hook Name of the event hook.
* @param Ai1ec_Event_Callback_Abstract $entity Event Callback object.
* @param integer $priority Priorify of the event hook execution.
* @param integer $accepted_args Number of accepted method parameters.
*
* @return Ai1ec_Event_Dispatcher Event Dispatcher Object.
*/
public function register(
$hook,
Ai1ec_Event_Callback_Abstract $entity,
$priority = 10,
$accepted_args = 1
) {
$wp_method = 'add_action';
if ( $entity instanceof Ai1ec_Event_Callback_Filter ) {
$wp_method = 'add_filter';
}
$wp_method(
$hook,
array( $entity, 'run' ),
$priority,
$accepted_args
);
return $this;
}
/**
* Creates a callback object and register it.
*
* @param string $hook Name of the event hook.
* @param array $method Method to call.
* @param integer $priority Priorify of the event hook execution.
* @param integer $accepted_args Number of accepted method parameters.
* @param string $type The type to add.
*
* @return void
*/
protected function _register(
$hook,
array $method,
$type,
$priority = 10,
$accepted_args = 1
) {
$action = $this->_registry->get(
'event.callback.' . $type,
$method[0],
$method[1]
);
$this->register(
$hook,
$action,
$priority,
$accepted_args
);
}
/**
* Register a filter.
*
* @param string $hook Name of the event hook.
* @param array $method Method to call.
* @param integer $priority Priorify of the event hook execution.
* @param integer $accepted_args Number of accepted method parameters.
*
* @return void
*/
public function register_filter(
$hook,
array $method,
$priority = 10,
$accepted_args = 1
) {
$this->_register(
$hook,
$method,
'filter',
$priority,
$accepted_args
);
}
/**
* Register an action.
*
* @param string $hook Name of the event hook.
* @param array $method Method to call.
* @param integer $priority Priorify of the event hook execution.
* @param integer $accepted_args Number of accepted method parameters.
*
* @return void
*/
public function register_action(
$hook,
array $method,
$priority = 10,
$accepted_args = 1
) {
$this->_register(
$hook,
$method,
'action',
$priority,
$accepted_args
);
}
/**
* Register a shortcode.
*
* @param string $shortcode Name of the shortcode tag.
* @param array $method Method to call.
*
* @return void
*/
public function register_shortcode(
$shortcode,
array $method
) {
$entity = $this->_registry->get(
'event.callback.shortcode',
$method[0],
$method[1]
);
add_shortcode( $shortcode, array( $entity, 'run' ) );
return $this;
}
}

View File

@@ -0,0 +1,52 @@
<?php
/**
* Abstract base class for all our excpetion.
*
* @author Time.ly Network Inc.
* @since 2.0
*
* @package AI1EC
* @subpackage AI1EC.Exception
*/
class Ai1ec_Exception extends Exception {
/**
* A message to be displayed for admin
*
* Specific Exceptions should override this.
*
* @return string Message to be displayed for admin
*/
public function get_html_message() {
return $this->getMessage();
}
/**
* Return the èath of the plugin to disable it.
* If empty it disables core.
*
* @return string
*/
public function plugin_to_disable() {
return '';
}
/**
* Returns destination URL if exception handler redirects.
*
* @return string Result.
*/
public function get_redirect_url() {
return ai1ec_get_admin_url();
}
/**
* Defined whether exception handler should attach backtrace or not.
*
* @return bool Value.
*/
public function display_backtrace(){
return true;
}
}

View File

@@ -0,0 +1,13 @@
<?php
/**
* Exception that is raised when a PHP error happens in our plugin.
*
* @author Time.ly Network Inc
* @since 2.0
*
* @package AI1EC
* @subpackage AI1EC.Exception
*/
class Ai1ec_Error_Exception extends ErrorException {
}

View File

@@ -0,0 +1,606 @@
<?php
/**
* Handles exception and errors
*
* @author Time.ly Network Inc.
* @since 2.0
*
* @package AI1EC
* @subpackage AI1EC.Exception
*/
class Ai1ec_Exception_Handler {
/**
* @var string The option for the messgae in the db
*/
const DB_DEACTIVATE_MESSAGE = 'ai1ec_deactivate_message';
/**
* @var string The GET parameter to reactivate the plugin
*/
const DB_REACTIVATE_PLUGIN = 'ai1ec_reactivate_plugin';
/**
* @var callable|null Previously set exception handler if any
*/
protected $_prev_ex_handler;
/**
* @var callable|null Previously set error handler if any
*/
protected $_prev_er_handler;
/**
* @var string The name of the Exception class to handle
*/
protected $_exception_class;
/**
* @var string The name of the ErrorException class to handle
*/
protected $_error_exception_class;
/**
* @var string The message to display in the admin notice
*/
protected $_message;
/**
* @var array Mapped list of errors that are non-fatal, to be ignored
* in production.
*/
protected $_nonfatal_errors = null;
/**
* Store exception handler that was previously set
*
* @param callable|null $_prev_ex_handler
*
* @return void Method does not return
*/
public function set_prev_ex_handler( $prev_ex_handler ) {
$this->_prev_ex_handler = $prev_ex_handler;
}
/**
* Store error handler that was previously set
*
* @param callable|null $_prev_er_handler
*
* @return void Method does not return
*/
public function set_prev_er_handler( $prev_er_handler ) {
$this->_prev_er_handler = $prev_er_handler;
}
/**
* Constructor accepts names of classes to be handled
*
* @param string $exception_class Name of exceptions base class to handle
* @param string $error_class Name of errors base class to handle
*
* @return void Constructor newer returns
*/
public function __construct( $exception_class, $error_class ) {
$this->_exception_class = $exception_class;
$this->_error_exception_class = $error_class;
$this->_nonfatal_errors = array(
E_USER_WARNING => true,
E_WARNING => true,
E_USER_NOTICE => true,
E_NOTICE => true,
E_STRICT => true,
);
if ( version_compare( PHP_VERSION, '5.3.0' ) >= 0 ) {
// wrapper `constant( 'XXX' )` is used to avoid compile notices
// on earlier PHP versions.
$this->_nonfatal_errors[constant( 'E_DEPRECATED' )] = true;
$this->_nonfatal_errors[constant( 'E_USER_DEPRECATED') ] = true;
}
}
/**
* Return add-on, which caused the exception or null if it was Core.
*
* Relies on `plugin_to_disable` method which may be implemented by
* an exception. If it returns non empty value - it is returned.
*
* @param Exception $exception Actual exception which was thrown.
*
* @return string|null Add-on identifier (plugin url), or null.
*/
public function is_caused_by_addon( $exception ) {
$addon = null;
if ( method_exists( $exception, 'plugin_to_disable' ) ) {
$addon = $exception->plugin_to_disable();
if ( empty( $addon ) ) {
$addon = null;
}
}
if ( null === $addon ) {
$position = strlen( dirname( AI1EC_PATH ) ) + 1;
$length = strlen( AI1EC_PLUGIN_NAME );
$trace_list = $exception->getTrace();
array_unshift(
$trace_list,
array( 'file' => $exception->getFile() )
);
foreach ( $trace_list as $trace ) {
if (
! isset( $trace['file'] ) ||
! isset( $trace['file'][$position] )
) {
continue;
}
$file = substr(
$trace['file'],
$position,
strpos( $trace['file'], '/', $position ) - $position
);
if ( 0 === strncmp( AI1EC_PLUGIN_NAME, $file, $length ) ) {
if ( AI1EC_PLUGIN_NAME !== $file ) {
$addon = $file . '/' . $file . '.php';
}
}
}
}
if ( 'core' === strtolower( $addon ) ) {
return null;
}
return $addon;
}
/**
* Get tag-line for disabling.
*
* Extracts plugin name from file.
*
* @param string $addon Name of disabled add-on.
*
* @return string Message to display before full trace.
*/
public function get_disabled_line( $addon ) {
$file = dirname( AI1EC_PATH ) . DIRECTORY_SEPARATOR . $addon;
$line = '';
if (
is_file( $file ) &&
preg_match(
'|Plugin Name:\s*(.+)|',
file_get_contents( $file ),
$matches
)
) {
$line = '<p><strong>' .
sprintf(
__( 'The add-on "%s" has been disabled due to an error:' ),
__( trim( $matches[1] ), dirname( $addon ) )
) .
'</strong></p>';
}
return $line;
}
/**
* Global exceptions handling method
*
* @param Exception $exception Previously thrown exception to handle
*
* @return void Exception handler is not expected to return
*/
public function handle_exception( $exception ) {
if ( defined( 'AI1EC_DEBUG' ) && true === AI1EC_DEBUG ) {
echo '<pre>';
$this->var_debug( $exception );
echo '</pre>';
die();
}
// if it's something we handle, handle it
$backtrace = $this->_get_backtrace( $exception );
if ( $exception instanceof $this->_exception_class ) {
// check if it's a plugin instead of core
$disable_addon = $this->is_caused_by_addon( $exception );
$message = method_exists( $exception, 'get_html_message' )
? $exception->get_html_message()
: $exception->getMessage();
$message = '<p>' . $message . '</p>';
if ( $exception->display_backtrace() ) {
$message .= $backtrace;
}
if ( null !== $disable_addon ) {
include_once ABSPATH . 'wp-admin/includes/plugin.php';
// deactivate the plugin. Fire handlers to hide options.
deactivate_plugins( $disable_addon );
global $ai1ec_registry;
$ai1ec_registry->get( 'notification.admin' )
->store(
$this->get_disabled_line( $disable_addon ) . $message,
'error',
2,
array( Ai1ec_Notification_Admin::RCPT_ADMIN ),
true
);
$this->redirect( $exception->get_redirect_url() );
} else {
// check if it has a methof for deatiled html
$this->soft_deactivate_plugin( $message );
}
}
// if it's a PHP error in our plugin files, deactivate and redirect
else if ( $exception instanceof $this->_error_exception_class ) {
$this->soft_deactivate_plugin(
$exception->getMessage() . $backtrace
);
}
// if another handler was set, let it handle the exception
if ( is_callable( $this->_prev_ex_handler ) ) {
call_user_func( $this->_prev_ex_handler, $exception );
}
}
/**
* Throws an Ai1ec_Error_Exception if the error comes from our plugin
*
* @param int $errno Error level as integer
* @param string $errstr Error message raised
* @param string $errfile File in which error was raised
* @param string $errline Line in which error was raised
* @param array $errcontext Error context symbols table copy
*
* @throws Ai1ec_Error_Exception If error originates from within Ai1EC
*
* @return boolean|void Nothing when error is ours, false when no
* other handler exists
*/
public function handle_error(
$errno,
$errstr,
$errfile,
$errline,
$errcontext = array()
) {
// if the error is not in our plugin, let PHP handle things.
$position = strpos( $errfile, AI1EC_PLUGIN_NAME );
if ( false === $position ) {
if ( is_callable( $this->_prev_er_handler ) ) {
return call_user_func_array(
$this->_prev_er_handler,
func_get_args()
);
}
return false;
}
// do not disable plugin in production if the error is rather low
if (
isset( $this->_nonfatal_errors[$errno] ) && (
! defined( 'AI1EC_DEBUG' ) || false === AI1EC_DEBUG
)
) {
$message = sprintf(
'All-in-One Event Calendar: %s @ %s:%d #%d',
$errstr,
$errfile,
$errline,
$errno
);
return error_log( $message, 0 );
}
// let's get the plugin folder
$tail = substr( $errfile, $position );
$exploded = explode( DIRECTORY_SEPARATOR, $tail );
$plugin_dir = $exploded[0];
// if the error doesn't belong to core, throw the plugin exception to trigger disabling
// of the plugin in the exception handler
if ( AI1EC_PLUGIN_NAME !== $plugin_dir ) {
$exc = implode(
array_map(
array( $this, 'return_first_char' ),
explode( '-', $plugin_dir )
)
);
// all plugins should implement an exception based on this convention
// which is the same convention we use for constants, only with just first letter uppercase
$exc = str_replace( 'aioec', 'Ai1ec', $exc ) . '_Exception';
if ( class_exists( $exc ) ) {
$message = sprintf(
'All-in-One Event Calendar: %s @ %s:%d #%d',
$errstr,
$errfile,
$errline,
$errno
);
throw new $exc( $message );
}
}
throw new Ai1ec_Error_Exception(
$errstr,
$errno,
0,
$errfile,
$errline
);
}
public function return_first_char( $name ) {
return $name[0];
}
/**
* Perform what's needed to deactivate the plugin softly
*
* @param string $message Error message to be displayed to admin
*
* @return void Method does not return
*/
protected function soft_deactivate_plugin( $message ) {
add_option( self::DB_DEACTIVATE_MESSAGE, $message );
$this->redirect();
}
/**
* Perform what's needed to reactivate the plugin
*
* @return boolean Success
*/
public function reactivate_plugin() {
return delete_option( self::DB_DEACTIVATE_MESSAGE );
}
/**
* Get message to be displayed to admin if any
*
* @return string|boolean Error message or false if plugin is not disabled
*/
public function get_disabled_message() {
global $wpdb;
$row = $wpdb->get_row(
$wpdb->prepare(
"SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1",
self::DB_DEACTIVATE_MESSAGE
)
);
if ( is_object( $row ) ) {
return $row->option_value;
} else { // option does not exist, so we must cache its non-existence
return false;
}
}
/**
* Add an admin notice
*
* @param string $message Message to be displayed to admin
*
* @return void Method does not return
*/
public function show_notices( $message ) {
// save the message to use it later
$this->_message = $message;
add_action( 'admin_notices', array( $this, 'render_admin_notice' ) );
}
/**
* Render HTML snipped to be displayed as a notice to admin
*
* @hook admin_notices When plugin is soft-disabled
*
* @return void Method does not return
*/
public function render_admin_notice() {
$redirect_url = esc_url( add_query_arg(
self::DB_REACTIVATE_PLUGIN,
'true',
get_admin_url()
) );
$label = __(
'All-in-One Event Calendar has been disabled due to an error:',
AI1EC_PLUGIN_NAME
);
$message = '<div class="message error">';
$message .= '<p><strong>' . $label . '</strong></p>';
$message .= $this->_message;
$message .= ' <a href="' . $redirect_url .
'" class="button button-primary ai1ec-dismissable">' .
__(
'Try reactivating plugin',
AI1EC_PLUGIN_NAME
);
$message .= '</a>';
$message .= '<p></p></div>';
echo $message;
}
/**
* Redirect the user either to the front page or the dashbord page
*
* @return void Method does not return
*/
protected function redirect( $suggested_url = null ) {
$url = ai1ec_get_site_url();
if ( is_admin() ) {
$url = null !== $suggested_url
? $suggested_url
: ai1ec_get_admin_url();
}
Ai1ec_Http_Response_Helper::redirect( $url );
}
/**
* Had to add it as var_dump was locking my browser.
*
* Taken from http://www.leaseweblabs.com/2013/10/smart-alternative-phps-var_dump-function/
*
* @param mixed $variable
* @param int $strlen
* @param int $width
* @param int $depth
* @param int $i
* @param array $objects
*
* @return string
*/
public function var_debug(
$variable,
$strlen = 400,
$width = 25,
$depth = 10,
$i = 0,
&$objects = array()
) {
$search = array( "\0", "\a", "\b", "\f", "\n", "\r", "\t", "\v" );
$replace = array( '\0', '\a', '\b', '\f', '\n', '\r', '\t', '\v' );
$string = '';
switch ( gettype( $variable ) ) {
case 'boolean' :
$string .= $variable ? 'true' : 'false';
break;
case 'integer' :
$string .= $variable;
break;
case 'double' :
$string .= $variable;
break;
case 'resource' :
$string .= '[resource]';
break;
case 'NULL' :
$string .= "null";
break;
case 'unknown type' :
$string .= '???';
break;
case 'string' :
$len = strlen( $variable );
$variable = str_replace(
$search,
$replace,
substr( $variable, 0, $strlen ),
$count );
$variable = substr( $variable, 0, $strlen );
if ( $len < $strlen ) {
$string .= '"' . $variable . '"';
} else {
$string .= 'string(' . $len . '): "' . $variable . '"...';
}
break;
case 'array' :
$len = count( $variable );
if ( $i == $depth ) {
$string .= 'array(' . $len . ') {...}';
} elseif ( ! $len) {
$string .= 'array(0) {}';
} else {
$keys = array_keys( $variable );
$spaces = str_repeat( ' ', $i * 2 );
$string .= "array($len)\n" . $spaces . '{';
$count = 0;
foreach ( $keys as $key ) {
if ( $count == $width ) {
$string .= "\n" . $spaces . " ...";
break;
}
$string .= "\n" . $spaces . " [$key] => ";
$string .= $this->var_debug(
$variable[$key],
$strlen,
$width,
$depth,
$i + 1,
$objects
);
$count ++;
}
$string .= "\n" . $spaces . '}';
}
break;
case 'object':
$id = array_search( $variable, $objects, true );
if ( $id !== false ) {
$string .= get_class( $variable ) . '#' . ( $id + 1 ) . ' {...}';
} else if ( $i == $depth ) {
$string .= get_class( $variable ) . ' {...}';
} else {
$id = array_push( $objects, $variable );
$array = ( array ) $variable;
$spaces = str_repeat( ' ', $i * 2 );
$string .= get_class( $variable ) . "#$id\n" . $spaces . '{';
$properties = array_keys( $array );
foreach ( $properties as $property ) {
$name = str_replace( "\0", ':', trim( $property ) );
$string .= "\n" . $spaces . " [$name] => ";
$string .= $this->var_debug(
$array[$property],
$strlen,
$width,
$depth,
$i + 1,
$objects
);
}
$string .= "\n" . $spaces . '}';
}
break;
}
if ( $i > 0 ) {
return $string;
}
$backtrace = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS );
do {
$caller = array_shift( $backtrace );
} while (
$caller &&
! isset( $caller['file'] )
);
if ( $caller ) {
$string = $caller['file'] . ':' . $caller['line'] . "\n" . $string;
}
echo nl2br( str_replace( ' ', '&nbsp;', htmlentities( $string ) ) );
}
/**
* Get HTML code with backtrace information for given exception.
*
* @param Exception $exception
*
* @return string HTML code.
*/
protected function _get_backtrace( $exception ) {
$backtrace = '';
$trace = nl2br( $exception->getTraceAsString() );
$ident = sha1( $trace );
if ( ! empty( $trace ) ) {
$request_uri = '';
if ( isset( $_SERVER['REQUEST_URI'] ) ) {
// Remove all whitespaces
$request_uri = preg_replace( '/\s+/', '', $_SERVER['REQUEST_URI'] );
// Convert request URI and strip tags
$request_uri = strip_tags( htmlspecialchars_decode( $request_uri ) );
// Limit URL to 100 characters
$request_uri = substr($request_uri, 0, 100);
}
$button_label = __( 'Toggle error details', AI1EC_PLUGIN_NAME );
$title = __( 'Error Details:', AI1EC_PLUGIN_NAME );
$backtrace = <<<JAVASCRIPT
<script type="text/javascript">
jQuery( function($) {
$( "a[data-rel='$ident']" ).click( function() {
jQuery( "#ai1ec-error-$ident" ).slideToggle( "fast" );
return false;
});
});
</script>
<blockquote id="ai1ec-error-$ident" style="display: none;">
<strong>$title</strong>
<p>$trace</p>
<p>Request Uri: $request_uri</p>
</blockquote>
<a href="#" data-rel="$ident" class="button">$button_label</a>
JAVASCRIPT;
}
return $backtrace;
}
}

View File

@@ -0,0 +1,68 @@
<?php
/**
* A factory class for events.
*
* @author Time.ly Network, Inc.
* @since 2.0
* @package Ai1EC
* @subpackage Ai1EC.Factory
*/
class Ai1ec_Factory_Event extends Ai1ec_Base {
/**
* @var bool whether the theme is legacy
*/
protected $_legacy;
/**
* Public constructor
*
* @param Ai1ec_Registry_Object $registry
*/
public function __construct( Ai1ec_Registry_Object $registry ) {
parent::__construct( $registry );
$this->_legacy = $registry->get( 'theme.loader' )->is_legacy_theme();
}
/**
* Factory method for events
*
* @param string $data
* @param string $instance
*
* @return Ai1ec_Event
*/
public function create_event_instance(
Ai1ec_Registry_Object $registry,
$data = null,
$instance = false
) {
$use_backward_compatibility = $registry->get(
'compatibility.check'
)->use_backward_compatibility();
if (
$use_backward_compatibility &&
true === $this->_legacy
) {
return new Ai1ec_Event_Legacy(
$registry,
$data,
$instance
);
}
$class_name = 'Ai1ec_Event';
if (
$use_backward_compatibility &&
'Ai1ec_Event' === $class_name
) {
$class_name = 'Ai1ec_Event_Compatibility';
}
return new $class_name(
$registry,
$data,
$instance
);
}
}

View File

@@ -0,0 +1,294 @@
<?php
/**
* A factory class for html elements
*
* @author Time.ly Network, Inc.
* @since 2.0
* @package Ai1EC
* @subpackage Ai1EC.Factory
*/
class Ai1ec_Factory_Html extends Ai1ec_Base {
/**
* @var boolean
*/
protected $pretty_permalinks_enabled = false;
/**
* @var string
*/
protected $page;
/**
* The contructor method.
*
* @param Ai1ec_Registry_Object $registry
*/
public function __construct(
Ai1ec_Registry_Object $registry
) {
parent::__construct( $registry );
$app = $registry->get( 'bootstrap.registry.application' );
$this->page = $app->get( 'calendar_base_page' );
$this->pretty_permalinks_enabled = $app->get( 'permalinks_enabled' );
}
/**
* Creates an instance of the class which generates href for links.
*
* @param array $args
* @param string $type
*
* @return Ai1ec_Href_Helper
*/
public function create_href_helper_instance( array $args, $type = 'normal' ) {
$href = new Ai1ec_Html_Element_Href( $args, $this->page );
$href->set_pretty_permalinks_enabled( $this->pretty_permalinks_enabled );
switch ( $type ) {
case 'category':
$href->set_is_category( true );
break;
case 'tag':
$href->set_is_tag( true );
break;
case 'author':
$href->set_is_author( true );
break;
default:
break;
}
return $href;
}
/**
* Create the html element used as the UI control for the datepicker button.
* The href must keep only active filters.
*
* @param array $args Populated args for the view
* @param int|string|null $initial_date The datepicker's initially set date
* @param string $title Title to display in datepicker button
* @param string $title_short Short names in title
* @return Ai1ec_Generic_Html_Tag
*/
public function create_datepicker_link(
array $args, $initial_date = null, $title = '', $title_short = ''
) {
$settings = $this->_registry->get( 'model.settings' );
$date_system = $this->_registry->get( 'date.system' );
$date_format_pattern = $date_system->get_date_pattern_by_key(
$settings->get( 'input_date_format' )
);
if ( null === $initial_date ) {
// If exact_date argument was provided, use its value to initialize
// datepicker.
if ( isset( $args['exact_date'] ) &&
$args['exact_date'] !== false &&
$args['exact_date'] !== null ) {
$initial_date = $args['exact_date'];
}
// Else default to today's date.
else {
$initial_date = $date_system->current_time();
}
}
// Convert initial date to formatted date if required.
if ( Ai1ec_Validation_Utility::is_valid_time_stamp( $initial_date ) ) {
$initial_date = $date_system->format_date(
$initial_date,
$settings->get( 'input_date_format' )
);
}
$href_args = array(
'action' => $args['action'],
'cat_ids' => $args['cat_ids'],
'tag_ids' => $args['tag_ids'],
'exact_date' => "__DATE__",
);
$href_args = apply_filters(
'ai1ec_date_picker_href_args',
$href_args,
$args
);
$data_href = $this->create_href_helper_instance( $href_args );
$attributes = array(
'data-date' => $initial_date,
'data-date-format' => $date_format_pattern,
'data-date-weekstart' => $settings->get( 'week_start_day' ),
'href' => '#',
'data-href' => $data_href->generate_href(),
'data-lang' => str_replace( '_', '-', get_locale() ),
);
$loader = $this->_registry->get( 'theme.loader' );
$file = $loader->get_file( 'date-icon.png' );
$args = array(
'attributes' => $attributes,
'data_type' => $args['data_type'],
'icon_url' => $file->get_url(),
'text_date' => __( 'Choose a date using calendar', AI1EC_PLUGIN_NAME ),
'title' => $title,
'title_short' => $title_short,
);
return $loader->get_file( 'datepicker_link.twig', $args );
}
/**
* Creates a select2 Multiselect.
*
* @param array $args The arguments for the select.
* @param array $options The options of the select
* @param array $view_args The args used in the front end.
*
* @return Ai1ec_File_Twig
*
* @staticvar $cached_flips Maps of taxonomy identifiers.
* @staticvar $checkable_types Map of types and taxonomy identifiers.
*/
public function create_select2_multiselect(
array $args,
array $options,
array $view_args = null
) {
// if no data is present and we are in the frontend, return a blank
// element.
if ( empty( $options ) && null !== $view_args ) {
return $this->_registry->get( 'html.element.legacy.blank' );
}
static $cached_flips = array();
static $checkable_types = array(
'category' => 'cat_ids',
'tag' => 'tag_ids',
'author' => 'auth_ids',
);
$use_id = isset( $args['use_id'] );
$options_to_add = array();
foreach ( $options as $term ) {
$option_arguments = array();
$color = false;
if ( $args['type'] === 'category' ) {
$color = $this->_registry->get( 'model.taxonomy' )
->get_category_color( $term->term_id );
}
if ( $color ) {
$option_arguments['data-color'] = $color;
}
if ( null !== $view_args ) {
// create the href for ajax loading
$href = $this->create_href_helper_instance(
$view_args,
$args['type']
);
$href->set_term_id( $term->term_id );
$option_arguments['data-href'] = $href->generate_href();
// check if the option is selected
$type_to_check = '';
// first let's check the correct type
if ( isset( $checkable_types[$args['type']] ) ) {
$type_to_check = $checkable_types[$args['type']];
}
// let's flip the array. Just once for performance sake,
// the categories doesn't change in the same request
if ( ! isset( $cached_flips[$type_to_check] ) ) {
$cached_flips[$type_to_check] = array_flip(
$view_args[$type_to_check]
);
}
if ( isset( $cached_flips[$type_to_check][$term->term_id] ) ) {
$option_arguments['selected'] = 'selected';
}
}
if ( true === $use_id ) {
$options_to_add[] = array(
'text' => $term->name,
'value' => $term->term_id,
'args' => $option_arguments,
);
} else {
$options_to_add[] = array(
'text' => $term->name,
'value' => $term->name,
'args' => $option_arguments,
);
}
}
$select2_args = array(
'multiple' => 'multiple',
'data-placeholder' => $args['placeholder'],
'class' => 'ai1ec-select2-multiselect-selector span12'
);
if ( isset( $args['class'] ) ) {
$select2_args['class'] .= ' ' . $args['class'];
}
$container_class = false;
if ( isset( $args['type'] ) ) {
$container_class = 'ai1ec-' . $args['type'] . '-filter';
}
$loader = $this->_registry->get( 'theme.loader' );
$select2 = $loader->get_file(
'select2_multiselect.twig',
array(
'name' => $args['name'],
'id' => $args['id'],
'container_class' => $container_class,
'select2_args' => $select2_args,
'options' => $options_to_add,
),
true
);
return $select2;
}
/**
* Creates a select2 input.
*
* @param array $args The arguments of the input.
*
* @return Ai1ec_File_Twig
*/
public function create_select2_input( array $args ) {
if( ! isset ( $args['name'] ) ) {
$args['name'] = $args['id'];
}
// Get tags.
$tags = get_terms(
'events_tags',
array(
'orderby' => 'name',
'hide_empty' => 0,
)
);
// Build tags array to pass as JSON.
$tags_json = array();
foreach ( $tags as $term ) {
$tags_json[] = $term->name;
}
$tags_json = json_encode( $tags_json );
$tags_json = _wp_specialchars( $tags_json, 'single', 'UTF-8' );
$loader =$this->_registry->get( 'theme.loader' );
$select2_args = array(
'data-placeholder' => __( 'Tags (optional)', AI1EC_PLUGIN_NAME ),
'class' => 'ai1ec-tags-selector span12',
'data-ai1ec-tags' => $tags_json
);
$select2 = $loader->get_file(
'select2_input.twig',
array(
'name' => $args['name'],
'id' => $args['id'],
'select2_args' => $select2_args,
),
true
);
return $select2;
}
}

View File

@@ -0,0 +1,110 @@
<?php
/**
* A factory class for caching strategy.
*
* @author Time.ly Network, Inc.
* @since 2.0
* @package Ai1EC
* @subpackage Ai1EC.Factory
*/
class Ai1ec_Factory_Strategy extends Ai1ec_Base {
/**
* create_cache_strategy_instance method
*
* Method to instantiate new cache strategy object
*
* @param string $cache_dirs Cache directory to use
* @param array $skip_small_bits Set to true, to ignore small entities
* cache engines, as APC [optional=false]
*
* @return Ai1ec_Cache_Strategy Instantiated writer
*/
public function create_cache_strategy_instance(
$cache_dirs = null,
$skip_small_entities_cache = false
) {
$engine = null;
$name = '';
if ( false === $skip_small_entities_cache && Ai1ec_Cache_Strategy_Apc::is_available() ) {
$engine = $this->_registry->get( 'cache.strategy.apc' );
} else if (
false === AI1EC_DISABLE_FILE_CACHE &&
null !== $cache_dirs &&
$cache_dir = $this->_get_writable_cache_dir( $cache_dirs )
) {
$engine = $this->_registry->get( 'cache.strategy.file', $cache_dir );
} else {
$engine = $this->_registry->get(
'cache.strategy.db',
$this->_registry->get( 'model.option' )
);
}
return $engine;
}
/**
* create_persistence_context method
*
* @param string $key_for_persistance
* @param string $cache_dirs
* @param bool $skip_small_entities_cache
*
* @return Ai1ec_Persistence_Context Instance of persistance context
*/
public function create_persistence_context(
$key_for_persistance,
$cache_dirs = null,
$skip_small_entities_cache = false
) {
return new Ai1ec_Persistence_Context(
$key_for_persistance,
$this->create_cache_strategy_instance( $cache_dirs, $skip_small_entities_cache )
);
}
/**
* Get a writable directory if possible, falling back on wp_contet dir
*
* @param array $cache_dirs
* @return boolean|string
*/
protected function _get_writable_cache_dir( $cache_dirs ) {
$writable_folder = false;
foreach ( $cache_dirs as $cache_dir ) {
if ( $this->_is_cache_dir_writable( $cache_dir['path'] ) ) {
$writable_folder = $cache_dir;
break;
}
}
return $writable_folder;
}
/**
* _is_cache_dir_writable method
*
* Check if given cache directory is writable.
*
* @param string $directory A path to check for writability
*
* @return bool Writability
*/
protected function _is_cache_dir_writable( $directory ) {
static $cache_directories = array();
if ( ! isset( $cache_directories[$directory] ) ) {
$cache_directories[$directory] = apply_filters(
'ai1ec_is_cache_dir_writable',
null,
$directory
);
if ( null === $cache_directories[$directory] ) {
$filesystem = $this->_registry->get( 'filesystem.checker' );
$cache_directories[$directory] = $filesystem->is_writable(
$directory
);
}
}
return $cache_directories[$directory];
}
}

View File

@@ -0,0 +1,156 @@
<?php
/**
* A helper class for Filesystem checks.
*
* @author Time.ly Network, Inc.
* @since 2.0
* @package Ai1EC
* @subpackage Ai1EC.Filesystem
*/
class Ai1ec_Filesystem_Checker {
public function __construct() {
include_once ABSPATH . 'wp-admin/includes/file.php';
}
/**
* check if the path is writable. To make the check .
*
* @param string $path
* @return boolean
*/
public function is_writable( $path ) {
global $wp_filesystem;
// try without credentials
$writable = WP_Filesystem( false, $path );
// We consider the directory as writable if it uses the direct transport,
// otherwise credentials would be needed
if ( true === $writable ) {
return true;
}
// if the user has FTP and sockets defined
if (
$this->is_ftp_or_sockets( $wp_filesystem->method ) &&
$this->are_ftp_constants_defined()
) {
$creds = request_filesystem_credentials( '', $wp_filesystem->method, false, $path );
$writable = WP_Filesystem( $creds, $path );
if ( true === $writable ) {
return true;
}
}
if (
$this->is_ssh( $wp_filesystem->method ) &&
$this->are_ssh_constants_defined()
) {
$creds = request_filesystem_credentials( '', $wp_filesystem->method, false, $path );
$writable = WP_Filesystem( $creds, $path );
if ( true === $writable ) {
return true;
}
}
return false;
}
/**
* Check if method is ssh
*
* @param strin $method
* @return boolean
*/
public function is_ssh( $method ) {
return 'ssh2' === $method;
}
/**
* Check if method is ftp or sockets
*
* @param string $method
* @return boolean
*/
public function is_ftp_or_sockets( $method ) {
return 'ftpext' === $method ||
'ftpsockets' === $method;
}
/**
* Check if credentials for ssh are defined
*
* @return boolean
*/
public function are_ssh_constants_defined() {
return defined('FTP_HOST') &&
defined('FTP_PUBKEY') &&
defined('FTP_PRIKEY');
}
/**
* Check if credentials for ftp are defined
*
* @return boolean
*/
public function are_ftp_constants_defined() {
return defined('FTP_HOST') &&
defined('FTP_USER') &&
defined('FTP_PASS');
}
/**
* Creates a file using $wp_filesystem.
*
* @param string $file
* @param string $content
*/
public function put_contents( $file, $content ) {
global $wp_filesystem;
return $wp_filesystem->put_contents(
$file,
$content
);
}
/**
* Get the content folder from Wordpress if available
*
* @return string the folder to use or ''
*/
public function get_ai1ec_static_dir_if_available() {
global $wp_filesystem;
// reset the filesystem to it's standard.
WP_Filesystem();
$content_dir = $wp_filesystem->wp_content_dir() . DIRECTORY_SEPARATOR
. 'uploads' . DIRECTORY_SEPARATOR;
$static_dir = trailingslashit( $content_dir . 'ai1ec_static' );
if (
! $wp_filesystem->is_dir( $static_dir ) &&
! $wp_filesystem->mkdir( $static_dir )
) {
return '';
}
return $static_dir;
}
/**
* Check if specified file exists
*
* @return boolean
*/
public function check_file_exists( $file, $check_is_empty ) {
try {
if ( ! file_exists( $file ) ) {
return false;
} else {
if ( $check_is_empty && 0 == filesize( $file ) ) {
return false;
} else {
return true;
}
}
} catch ( Exception $e ) {
}
return false;
}
}

View File

@@ -0,0 +1,186 @@
<?php
/**
* Miscellaneous file system related functions.
*
* @author Time.ly Network Inc.
* @since 2.2
*
* @package AI1EC
* @subpackage AI1EC.Lib.Filesystem
*/
class Ai1ec_Filesystem_Misc extends Ai1ec_Base {
/**
* Builds directory hashmap.
*
* @param array|string $paths Paths for hashmap generation. It accepts
* string or array of paths. Elements in
* hashmaps are not overwritten.
* @param array $exclusions List of excluded file names.
*
* @return array Hashmap.
*/
public function build_dirs_hashmap( $paths, $exclusions = array() ) {
if ( ! is_array( $paths ) ) {
$paths = array( $paths );
}
$hashmap = array();
foreach ( $paths as $path ) {
if ( file_exists( $path ) ) {
$hashmap += $this->build_dir_hashmap( $path, $exclusions );
}
}
ksort( $hashmap );
return $hashmap;
}
/**
* Builds hashmap for given directory.
*
* @param string $directory Directory for hashmap creation.
* @param array $exclusions List of excluded file names.
*
* @return array Hashmap.
*/
public function build_dir_hashmap( $directory, $exclusions = array() ) {
$directory_iterator = new RecursiveDirectoryIterator(
$directory
);
$recursive_iterator = new RecursiveIteratorIterator(
$directory_iterator
);
$files = new RegexIterator(
$recursive_iterator,
'/^.+\.(less|css|php)$/i',
RegexIterator::GET_MATCH
);
$hashmap = array();
foreach ( $files as $file ) {
$file_info = new SplFileInfo( $file[0] );
$file_path = $file_info->getPathname();
if ( in_array( $file_info->getFilename(), $exclusions ) ) {
continue;
}
$key = str_replace(
array( $directory, '/' ),
array( '', '\\' ),
$file_path
);
$hashmap[ $key ] = array(
'size' => $file_info->getSize(),
'sha1' => sha1_file( $file_path ),
);
}
ksort( $hashmap );
return $hashmap;
}
/**
* Returns hashmap for current theme.
*
* @return mixed|null Hashmap or null if none.
*
* @throws Ai1ec_Bootstrap_Exception
*/
public function get_current_theme_hashmap() {
$cur_theme = $this->_registry->get( 'model.option' )->get( 'ai1ec_current_theme' );
if ( ! $cur_theme || ( isset( $cur_theme['stylesheet'] ) && 'saas' === $cur_theme['stylesheet'] ) ) {
return null;
}
$file_location = $cur_theme['theme_dir'] . DIRECTORY_SEPARATOR . 'less.sha1.map.php';
if ( ! file_exists( $file_location ) || ! is_readable( $file_location ) || ! @file_get_contents( $file_location ) ) {
// Delete theme options
$this->_registry->get( 'model.option' )->delete( 'ai1ec_current_theme' );
return null;
}
return require $file_location;
}
/**
* Builds file hashmap for current theme.
*
* @return array Hashmap.
*
* @throws Ai1ec_Bootstrap_Exception
* @throws Ai1ec_Invalid_Argument_Exception
*/
public function build_current_theme_hashmap() {
$paths = $this->_registry->get( 'theme.loader' )->get_paths();
return $this->build_dirs_hashmap(
array_keys(
$paths['theme']
),
array(
'ai1ec_parsed_css.css',
'less.sha1.map.php',
'index.php',
)
);
}
/**
* Returns theme structrure for one of core themes.
*
* @param string $stylesheet Theme stylesheet. Expected one of
* ['plana','vortex','umbra','gamma'].
*
* @return array Theme structure
*
* @throws Ai1ec_Invalid_Argument_Exception
*/
public function build_theme_structure( $stylesheet ) {
$themes = array( 'plana', 'vortex', 'umbra', 'gamma' );
if ( ! in_array( $stylesheet, $themes ) ) {
throw new Ai1ec_Invalid_Argument_Exception(
'Theme ' . $stylesheet . ' compilation is not supported.'
);
}
$root = AI1EC_PATH . DIRECTORY_SEPARATOR . 'public' .
DIRECTORY_SEPARATOR . AI1EC_THEME_FOLDER;
return array(
'theme_root' => $root,
'theme_dir' => $root . DIRECTORY_SEPARATOR . $stylesheet,
'theme_url' => AI1EC_URL . '/public/' . AI1EC_THEME_FOLDER . '/' . $stylesheet,
'stylesheet' => $stylesheet,
'legacy' => false,
);
}
/**
* Compares files hashmaps. If $src key doesn't exist in $dst, it's just
* ommited. This is intended for LESS compilation check. Current theme
* may contain more LESS files than base one, what does not matter as
* other files should be changed accordingly.
*
* @param array $src Source hashmap. Should be computed from current
* theme contents.
* @param array $dst Base hashmap. Should be taken from less.sha1.map.php
* file.
*
* @return bool Comparision result. True if they are equal.
*/
public function compare_hashmaps( array $src, array $dst ) {
foreach ( $src as $key => $value ) {
if ( ! isset( $dst[ $key ] ) ) {
continue;
}
$dst_value = $dst[ $key ];
if ( $dst_value !== $value ) {
return false;
}
}
return true;
}
}

View File

@@ -0,0 +1,241 @@
<?php
/**
* Define global functions
*
* @author Time.ly Network Inc.
* @since 2.0
*
* @package AI1EC
* @subpackage AI1EC.Lib
*/
/**
* Always return false for action/filter hooks
*
* @return boolean
*/
function ai1ec_return_false() {
return false;
}
/**
* Executed after initialization of Front Controller.
*
* @return void
*/
function ai1ec_start() {
ob_start();
}
/**
* Executed before script shutdown, when WP core objects are present.
*
* @return void
*/
function ai1ec_stop() {
if ( ob_get_level() ) {
echo ob_get_clean();
}
}
/**
* Create `<pre>` wrapped variable dump.
*
* @param mixed $var Arbitrary value to dump.
*
* @return void
*/
function ai1ec_dump( $var ) {
if ( ! defined( 'AI1EC_DEBUG' ) || ! AI1EC_DEBUG ) {
return null;
}
echo '<pre>';
var_dump( $var );
echo '</pre>';
exit( 0 );
}
/**
* Indicate deprecated function.
*
* @param string $function Name of called function.
*
* @return void
*/
function ai1ec_deprecated( $function ) {
trigger_error(
'Function \'' . $function . '\' is deprecated.',
E_USER_WARNING
);
}
/* (non-PHPdoc)
* @see admin_url()
*/
function ai1ec_admin_url( $path = '', $scheme = 'admin' ) {
if ( ai1ec_is_ssl_forced() ) {
$scheme = 'https';
}
return admin_url( $path, $scheme );
}
/* (non-PHPdoc)
* @see get_admin_url()
*/
function ai1ec_get_admin_url( $blog_id = null, $path = '', $scheme = 'admin' ) {
if ( ai1ec_is_ssl_forced() ) {
$scheme = 'https';
}
return get_admin_url( $blog_id, $path, $scheme );
}
/* (non-PHPdoc)
* @see get_site_url()
*/
function ai1ec_get_site_url( $blog_id = null, $path = '', $scheme = null ) {
if ( ai1ec_is_ssl_forced() ) {
$scheme = 'https';
}
return get_site_url( $blog_id, $path, $scheme );
}
/* (non-PHPdoc)
* @see site_url()
*/
function ai1ec_site_url( $path = '', $scheme = null ) {
if ( ai1ec_is_ssl_forced() ) {
$scheme = 'https';
}
return site_url( $path, $scheme );
}
/* (non-PHPdoc)
* @see network_admin_url()
*/
function ai1ec_network_admin_url( $path = '', $scheme = 'admin' ) {
if ( ai1ec_is_ssl_forced() ) {
$scheme = 'https';
}
return network_admin_url( $path, $scheme );
}
/**
* Returns whether SSL URLs are forced or not.
*
* @return bool Result.
*/
function ai1ec_is_ssl_forced() {
return (
is_admin() &&
(
class_exists( 'WordPressHTTPS' ) ||
(
defined( 'FORCE_SSL_ADMIN' ) &&
true === FORCE_SSL_ADMIN
)
)
);
}
/**
* Check if an string is empty.
* @return bool result
*/
function ai1ec_is_blank( $value ) {
if ( null === $value || false === isset( $value ) ) {
return true;
} else {
if ( is_string( $value ) ) {
return strlen( trim( $value ) ) <= 0;
} else if ( is_array( $value ) ) {
return count( $value ) <= 0;
} else {
return false;
}
}
}
/*
* Date parser for PHP <= 5.2
*
* Source: http://stackoverflow.com/questions/6668223/php-date-parse-from-format-alternative-in-php-5-2
*
* Modified to always populate hour, minute and second.
*
*/
if ( ! function_exists( 'date_parse_from_format' ) ) {
function date_parse_from_format( $format, $date ) {
// reverse engineer date formats
$keys = array(
'Y' => array( 'year', '\d{4}' ),
'y' => array( 'year', '\d{2}' ),
'm' => array( 'month', '\d{2}' ),
'n' => array( 'month', '\d{1,2}' ),
'M' => array( 'month', '[A-Z][a-z]{3}' ),
'F' => array( 'month', '[A-Z][a-z]{2,8}' ),
'd' => array( 'day', '\d{2}' ),
'j' => array( 'day', '\d{1,2}' ),
'D' => array( 'day', '[A-Z][a-z]{2}' ),
'l' => array( 'day', '[A-Z][a-z]{6,9}' ),
'u' => array( 'hour', '\d{1,6}' ),
'h' => array( 'hour', '\d{2}' ),
'H' => array( 'hour', '\d{2}' ),
'g' => array( 'hour', '\d{1,2}' ),
'G' => array( 'hour', '\d{1,2}' ),
'i' => array( 'minute', '\d{2}' ),
's' => array( 'second', '\d{2}' )
);
// convert format string to regex
$regex = '';
$chars = str_split( $format );
foreach ( $chars AS $n => $char ) {
$lastChar = isset( $chars[$n - 1] ) ? $chars[$n - 1] : '';
$skipCurrent = '\\' == $lastChar;
if ( !$skipCurrent && isset( $keys[$char] ) ) {
$regex .= '(?P<' . $keys[$char][0] . '>' . $keys[$char][1] . ')';
} else if ( '\\' == $char ) {
$regex .= $char;
} else {
$regex .= preg_quote( $char );
}
}
$dt = array();
// now try to match it
if ( preg_match( '#^' . $regex . '$#', $date, $dt ) ) {
foreach ( $dt AS $k => $v ) {
if ( is_int( $k ) ) {
unset( $dt[$k] );
}
}
if ( ! checkdate( $dt['month'], $dt['day'], $dt['year'] ) ) {
$dt['error_count'] = 1;
} else {
$dt['error_count'] = 0;
}
if ( ! isset( $dt['hour'] ) ) {
$dt['hour'] = 0;
}
if ( ! isset( $dt['minute'] ) ) {
$dt['minute'] = 0;
}
if ( ! isset( $dt['second'] ) ) {
$dt['second'] = 0;
}
} else {
$dt['error_count'] = 1;
}
$dt['errors'] = array();
$dt['fraction'] = '';
$dt['warning_count'] = 0;
$dt['warnings'] = array();
$dt['is_localtime'] = 0;
$dt['zone_type'] = 0;
$dt['zone'] = 0;
$dt['is_dst'] = '';
return $dt;
}
}

View File

@@ -0,0 +1,268 @@
<?php
/**
* This class handles generations of href for links.
*
* @author Time.ly Network, Inc.
* @instantiator Ai1ec_Factory_Html.create_href_helper_instance
* @since 2.0
* @package Ai1EC
* @subpackage Ai1EC.Html
*/
class Ai1ec_Html_Element_Href {
/**
* @var array the parameters that are used in the urls
*/
private $used_paramaters = array(
'action',
'page_offset',
'month_offset',
'oneday_offset',
'week_offset',
'time_limit',
'exact_date',
'cat_ids',
'auth_ids',
'post_ids',
'tag_ids',
'instance_ids',
'events_limit',
'request_format',
'no_navigation'
);
/**
* @var boolean
*/
private $is_category;
/**
* @var boolean
*/
private $is_tag;
/**
* @var boolean
*/
private $is_author;
/**
* @var boolean
*/
private $is_custom_filter;
/**
* @var array the arguments to parse
*/
private $args;
/**
* @var int
*/
private $term_id;
/**
* @var string
*/
private $calendar_page;
/**
* @var boolean
*/
private $pretty_permalinks_enabled;
/**
* @var string
*/
private $uri_particle = null;
/**
* @param boolean $pretty_permalinks_enabled
*/
public function set_pretty_permalinks_enabled( $pretty_permalinks_enabled ) {
$this->pretty_permalinks_enabled = $pretty_permalinks_enabled;
if ( $pretty_permalinks_enabled ) {
$this->calendar_page = trim( (string)$this->calendar_page, '/' )
. '/';
}
}
/**
* @param number $term_id
*/
public function set_term_id( $term_id ) {
$this->term_id = $term_id;
}
public function __construct( array $args, $calendar ) {
$this->args = $args;
$this->calendar_page = $calendar;
if ( isset( $args['_extra_used_parameters'] ) ) {
$this->used_paramaters = array_merge(
$this->used_paramaters,
$args['_extra_used_parameters']
);
}
$this->used_paramaters = array_merge(
$this->used_paramaters,
apply_filters(
'ai1ec_view_args_for_view',
array()
)
);
}
/**
* @param boolean $is_category
*/
public function set_is_category( $is_category ) {
$this->is_category = $is_category;
}
/**
* @param boolean $is_tag
*/
public function set_is_tag( $is_tag ) {
$this->is_tag = $is_tag;
}
/**
* @param boolean $is_author
*/
public function set_is_author( $is_author ) {
$this->is_author = $is_author;
}
/**
* Generate the correct href for the view.
* This takes into account special filters for categories and tags
*
* @return string
*/
public function generate_href() {
$href = '';
$to_implode = array();
foreach ( $this->used_paramaters as $key ) {
if ( ! empty( $this->args[$key] ) ) {
$value = $this->args[$key];
if( is_array( $this->args[$key] ) ) {
$value = implode( ',', $this->args[$key] );
}
$to_implode[$key] = $key . Ai1ec_Uri::DIRECTION_SEPARATOR .
$value;
}
}
if (
$this->is_category ||
$this->is_tag ||
$this->is_author ||
$this->is_custom_filter
) {
$to_implode = $this->add_or_remove_category_from_href(
$to_implode
);
}
if ( $this->pretty_permalinks_enabled ) {
$href .= implode( '/', $to_implode );
if ( ! empty( $href ) ) {
$href .= '/';
}
} else {
$href .= $this->get_param_delimiter_char( $this->calendar_page );
$href .= 'ai1ec=' . implode( '|', $to_implode );
}
$full_url = $this->calendar_page . $href;
// persist the `lang` parameter if present
if ( isset( $_REQUEST['lang'] ) ) {
$full_url = esc_url_raw( add_query_arg( 'lang', $_REQUEST['lang'], $full_url ) );
}
return $full_url;
}
/**
* Sets that class is used for custom filter.
*
* @param bool $value Expected true or false.
* @param string $uri_particle URI particle identifier.
*
* @return void Method does not return.
*/
public function set_custom_filter( $value, $uri_particle = null ) {
$this->is_custom_filter = $value;
$this->uri_particle = $uri_particle;
}
/**
* Perform some extra manipulation for filter href. Basically if the current
* category is part of the filter, the href will not contain it (because
* clicking on it will actually mean "remove that one from the filter")
* otherwise it will be preserved.
*
* @param array $to_implode
* @return array
*/
private function add_or_remove_category_from_href( array $to_implode ) {
$array_key = $this->uri_particle;
if ( null === $this->uri_particle ) {
$array_key = $this->_current_array_key();
}
// Let's copy the origina cat_ids or tag_ids so we do not affect it
$copy = array();
if ( isset( $this->args[$array_key] ) ) {
$copy = (array)$this->args[$array_key];
}
$key = array_search( $this->term_id, $copy );
// Let's check if we are already filtering for tags / categorys
if( isset( $to_implode[$array_key] ) ) {
if( $key !== false ) {
unset( $copy[$key] );
} else {
$copy[] = $this->term_id;
}
if( empty( $copy ) ) {
unset( $to_implode[$array_key] );
} else {
$to_implode[$array_key] = $array_key . Ai1ec_Uri::DIRECTION_SEPARATOR .
implode( ',', $copy );
}
} else {
$to_implode[$array_key] = $array_key . Ai1ec_Uri::DIRECTION_SEPARATOR . $this->term_id;
}
return $to_implode;
}
/**
* Match current argument key
*
* @return string Name of current argument key
*/
protected function _current_array_key() {
$map = array(
'category' => 'cat',
'tag' => 'tag',
'author' => 'auth',
);
$use_name = '';
foreach ( $map as $value => $name ) {
if ( $this->{'is_' . $value} ) {
$use_name = $name;
break;
}
}
return $use_name . '_ids';
}
/**
* Returns the delimiter character to use if a new query string parameter is
* going to be appended to the URL.
*
* @param string $url URL to parse
*
* @return string
*/
public static function get_param_delimiter_char( $url ) {
return strpos( $url, '?' ) === false ? '?' : '&';
}
}

View File

@@ -0,0 +1,39 @@
<?php
/**
* Interface for HTML elements.
*
* In this context element is a complex collection of HTML tags
* rendered to suit specific needs.
*
* @author Time.ly Network, Inc.
* @since 2.0
* @package Ai1EC
* @subpackage Ai1EC.Html
*/
interface Ai1ec_Html_Element_Interface {
/**
* Set attribute for renderable element.
*
* Attributes are object specific.
*
* @param string $attribute Name of attribute to set.
* @param mixed $value Value to set for attribute.
*
* @return Ai1ec_Html_Element_Interface Instance of self for chaining.
*/
public function set( $attribute, $value );
/**
* Generate HTML snippet for inclusion in page.
*
* @param string $snippet Particle to append to result.
*
* @return string HTML snippet.
*
* @throws Ai1ec_Html_Exception If rendering may not be completed.
*/
public function render( $snippet = '' );
}

View File

@@ -0,0 +1,172 @@
<?php
/**
* Base html element.
*
* @author Time.ly Network, Inc.
* @since 2.0
* @package Ai1EC
* @subpackage Ai1EC.Html
*/
abstract class Ai1ec_Html_Element extends Ai1ec_Base implements Ai1ec_Renderable {
/**
*
* @var string
*/
protected $id;
/**
*
* @var array
*/
protected $classes = array();
/**
* @var array
*/
protected $attributes = array();
/**
*
* @var Ai1ec_Template_Adapter
*/
protected $template_adapter;
/**
* Adds the passed attribute name & value to the link's attributes.
*
* @param string $name
* @param string|array $value
*/
public function set_attribute( $name, $value ) {
$value = ( array ) $value;
// Let's check if we have a value
if ( isset( $this->attributes[$name] ) ) {
// Let's check if it's an array
$this->attributes[$name] = array_unique(
array_merge( $this->attributes[$name], $value )
);
} else {
$this->attributes[$name] = $value;
}
}
/**
*
* @param string $name
* @return array|NULL
*/
public function get_attribute( $name ) {
if ( isset( $this->attributes[$name] ) ) {
return $this->attributes[$name];
} else {
return null;
}
}
/**
* Adds the given name="value"-formatted attribute expression to the link's
* set of attributes.
*
* @param string $expr Attribute name-value pair in name="value" format
*/
public function set_attribute_expr( $expr ) {
preg_match( '/^([\w\-_]+)=[\'"]([^\'"]*)[\'"]$/', $expr, $matches );
$name = $matches[1];
$value = $matches[2];
$this->set_attribute( $name, $value );
}
public function __construct( Ai1ec_Registry_Object $registry ) {
$this->_registry = $registry;
$this->template_adapter = $registry->get( 'html.helper' );
}
/**
* Magic method that renders the object as html
*
* @return string
*/
public function __toString() {
return $this->render_as_html();
}
/**
*
* @param $id string
*/
public function set_id( $id ) {
$this->id = $id;
}
/**
* Adds an element to the class array
*
* @param string $class
*/
public function add_class( $class ) {
$this->classes[] = $class;
}
/**
* Creates the markup to be used to create classes
*
* @return string
*/
protected function create_class_markup() {
if ( empty( $this->classes ) ) {
return '';
}
$classes = $this->template_adapter->escape_attribute(
implode( ' ', $this->classes )
);
return "class='$classes'";
}
/**
* Creates the markup for an attribute
*
* @param string $attribute_name
* @param string $attribute_value
* @return string
*/
protected function create_attribute_markup(
$attribute_name,
$attribute_value
) {
if (empty( $attribute_value )) {
return '';
}
$attribute_value = $this->template_adapter->escape_attribute( $attribute_value );
return "$attribute_name='$attribute_value'";
}
/**
* Renders the markup for the attributes of the tag
*
* @return string
*/
protected function render_attributes_markup() {
$html = array();
foreach ( $this->attributes as $name => $values ) {
$values = $this->template_adapter->escape_attribute(
implode( ' ', $values )
);
$html[] = "$name='$values'";
}
return implode( ' ', $html );
}
/**
* Return the content as html instead of echoing it.
*
* @return string
*/
public function render_as_html() {
$this->_registry->get( 'compatibility.ob' )->start();
$this->render();
return $this->_registry->get( 'compatibility.ob' )->get_clean();
}
}

View File

@@ -0,0 +1,17 @@
<?php
/**
* Basic interface for the composite.
*
* @author Time.ly Network, Inc.
* @since 2.0
* @package Ai1EC
* @subpackage Ai1EC.Html
*/
interface Ai1ec_Renderable {
/**
* This is the main function, it just renders the method for the element,
* taking care of childrens ( if any )
*/
public function render();
}

View File

@@ -0,0 +1,132 @@
<?php
/**
* A class that renders bootstrap modals.
*
* @instantiator new
* @author Time.ly Network, Inc.
* @since 2.0
* @package Ai1EC
* @subpackage Ai1EC.Html
*/
class Ai1ec_Bootstrap_Modal extends Ai1ec_Html_Element {
/**
* @var string
*/
private $delete_button_text;
/**
* @var string
*/
private $keep_button_text;
/**
* @var string
*/
private $body_text;
/**
* @var string
*/
private $header_text;
/**
* @param string $modal_text
*/
public function __construct( Ai1ec_Registry_Object $registry, $modal_text ) {
$this->body_text = $modal_text;
parent::__construct( $registry );
}
/**
* @param string $delete_button_text
*/
public function set_delete_button_text( $delete_button_text ) {
$this->delete_button_text = $delete_button_text;
}
/**
* @param string $keep_button_text
*/
public function set_keep_button_text( $keep_button_text ) {
$this->keep_button_text = $keep_button_text;
}
/**
* @param string $body_text
*/
public function set_body_text( $body_text ) {
$this->body_text = $body_text;
}
/**
* @param string $header_text
*/
public function set_header_text( $header_text ) {
$this->header_text = $header_text;
}
/**
* @return string
*/
private function render_id_if_present() {
return isset( $this->id ) ? "id='{$this->id}'" : '';
}
/**
* @return string
*/
private function render_header_if_present() {
return isset( $this->header_text ) ?
'<h2>' . $this->header_text . '</h2>'
: '';
}
/**
* @return string
*/
private function render_keep_button_if_present() {
return isset( $this->keep_button_text ) ? "<a href='#' class='ai1ec-btn keep ai1ec-btn-primary ai1ec-btn-lg'>{$this->keep_button_text}</a>" : '';
}
/**
* @return string
*/
private function render_remove_button_if_present() {
return isset( $this->delete_button_text ) ? "<a href='#' class='ai1ec-btn remove ai1ec-btn-danger ai1ec-btn-lg'>{$this->delete_button_text}</a>" : '';
}
/**
* @return string
*/
public function render() {
$header = $this->render_header_if_present();
$id = $this->render_id_if_present();
$remove_event_button = $this->render_remove_button_if_present();
$keep_event_button = $this->render_keep_button_if_present();
$body = $this->body_text;
$classes = implode( ' ', $this->classes );
$html = <<<HTML
<div class="ai1ec-modal $classes ai1ec-fade timely" $id>
<div class="ai1ec-modal-dialog">
<div class="ai1ec-modal-content">
<div class="ai1ec-modal-header">
<button type="button" class="ai1ec-close" data-dismiss="ai1ec-modal"
aria-hidden="true">×</button>
$header
</div>
<div class="ai1ec-modal-body">
$body
</div>
<div class="ai1ec-modal-footer">
$remove_event_button
$keep_event_button
</div>
</div>
</div>
</div>
HTML;
echo $html;
}
}

View File

@@ -0,0 +1,41 @@
<?php
/**
* Missing class setting-renderer description.
*
* @author Time.ly Network Inc.
* @since 2.2
*
* @package AI1EC
* @subpackage AI1EC.
*/
class Ai1ec_Html_Setting_Renderer extends Ai1ec_Base {
/**
* Renders single setting.
*
* @param array $setting Setting structure.
*
* @return string Rendered content.
*
* @throws Ai1ec_Bootstrap_Exception
*/
public function render( array $setting ) {
$renderer_name = $setting['renderer']['class'];
$renderer = null;
try {
$renderer = $this->_registry->get(
'html.element.setting.' . $renderer_name,
$setting
);
} catch ( Ai1ec_Bootstrap_Exception $exception ) {
$renderer = $this->_registry->get(
'html.element.setting.input',
$setting
);
}
return $renderer->render();
}
}

View File

@@ -0,0 +1,101 @@
<?php
/**
* Abstract class to accelerate settings page snippets development.
*
* @author Time.ly Network, Inc.
* @since 2.0
* @package Ai1EC
* @subpackage Ai1EC.Html
*/
abstract class Ai1ec_Html_Element_Settings extends Ai1ec_Base
implements Ai1ec_Html_Element_Interface {
/**
* @var Ai1ec_Html_Helper Instance of HTML helper.
*/
protected $_html = NULL;
/**
* @var array Map of arbitrary arguments passed to an element.
*/
protected $_args = array();
/**
* Constructor accepts system as injectable and requests HTML helper.
*
* @param Ai1ec_Registry_Object $system Injected system argument.
*
* @return void Constructor does not return.
*/
public function __construct(
Ai1ec_Registry_Object $registry,
array $args
) {
parent::__construct( $registry );
$this->_args = $args;
$this->_html = $registry->get( 'html.helper' );
}
/**
* Set value within current object scope
*
* Value name is formed as {$attribute} with underscore ('_') prefixed.
*
* @param string $attribute Name of attribute to set.
* @param mixed $value Value to set for attribute.
*
* @return Ai1ec_Html_Element_Settings Instance of self.
*/
public function set( $attribute, $value ) {
$this->{'_' . $attribute} = $value;
return $this;
}
/**
* Override to include any initialization logics.
*
* @return void Method output is ignored.
*/
protected function _initialize() {
}
/**
* Generate settings output line.
*
* @param string $output Generated output to finalize.
* @param bool $wrap Whether content should be wrapped with div or not.
*
* @return string Finalized HTML snippet.
*/
public function render( $output = '', $wrap = true, $hidden = false ) {
if ( isset( $this->_args['renderer']['condition'] ) ) {
$condition = $this->_args['renderer']['condition'];
if ( is_bool( $condition ) ) {
$render = $condition;
} else {
$callback = explode( ':', $this->_args['renderer']['condition'] );
try {
$render = $this->_registry->dispatch(
$callback[0],
$callback[1]
);
} catch (Ai1ec_Bootstrap_Exception $exception) {
$render = '';
}
}
if ( ! $render ) {
return '';
}
}
if ( ! $wrap ) {
return $output;
}
if ( $hidden ) {
return '<div class="ai1ec-form-group ai1ec-hidden">' . $output . '</div>';
} else {
return '<div class="ai1ec-form-group">' . $output . '</div>';
}
}
}

View File

@@ -0,0 +1,48 @@
<?php
/**
* Renderer of settings page html.
*
* @author Time.ly Network, Inc.
* @instantiator new
* @since 2.0
* @package Ai1EC
* @subpackage Ai1EC.Html
*/
class Ai1ec_Html_Setting_Cache extends Ai1ec_Html_Element_Settings {
/* (non-PHPdoc)
* @see Ai1ec_Html_Element_Settings::render()
*/
public function render( $output = '', $wrap = true, $hidden = false ) {
$args = $this->get_twig_cache_args();
$loader = $this->_registry->get( 'theme.loader' );
$file = $loader->get_file( 'setting/twig_cache.twig', $args, true );
return parent::render( $file->get_content(), $wrap, $hidden );
}
/**
* Returns data for Twig template.
*
* @return array Data for template
*/
public function get_twig_cache_args() {
$args = array(
'cache_available' => (
AI1EC_CACHE_UNAVAILABLE !== $this->_args['value'] &&
! empty( $this->_args['value'] )
),
'id' => $this->_args['id'],
'label' => $this->_args['renderer']['label'],
'text' => array(
'refresh' => Ai1ec_I18n::__( 'Check again' ),
'nocache' => Ai1ec_I18n::__( 'Templates cache is not writable' ),
'okcache' => Ai1ec_I18n::__( 'Templates cache is writable' ),
'rescan' => Ai1ec_I18n::__( 'Checking...' ),
'title' => Ai1ec_I18n::__( 'Performance Report' ),
),
);
return $args;
}
}

View File

@@ -0,0 +1,124 @@
<?php
/**
* Renderer of settings page Calendar page selection snippet.
*
* @author Time.ly Network, Inc.
* @since 2.0
* @package Ai1EC
* @subpackage Ai1EC.Html
*/
class Ai1ec_Html_Element_Calendar_Page_Selector
extends Ai1ec_Html_Element_Settings {
/**
* @var string HTML id attribute for selector.
*/
const ELEMENT_ID = 'calendar_page_id';
/**
* @var array Map of pages defined in system, use `get_pages()` WP call.
*/
protected $_pages = array();
/**
* Set attributes for element.
*
* Currently recognized attributes:
* - 'pages' - {@see self::$_pages} for details;
* - 'selected' - {@see self::$_selected} for details.
*
* @param string $attribute Name of attribute to set.
* @param mixed $value Value to set for attribute.
*
* @return Ai1ec_Html_Element_Calendar_Page_Selector Instance of self.
*/
public function set( $attribute, $value ) {
// any validation may be provided here
return parent::set( $attribute, $value );
}
/**
* Generate HTML snippet for inclusion in settings page.
*
* @param string $snippet Particle to append to result.
*
* @return string HTML snippet for page selection.
*/
public function render( $snippet = '', $wrap = true, $hidden = false ) {
$output = '<label class="ai1ec-control-label ai1ec-col-sm-5" for="' .
self::ELEMENT_ID . '">' . Ai1ec_I18n::__( 'Calendar page' ) . '</label>'
. '<div class="ai1ec-col-sm-7">' .
$this->_get_pages_selector() . $this->_get_page_view_link() . '</div>';
return parent::render( $output, $wrap, $hidden );
}
/**
* Generate link to open selected page in new window.
*
* @return string HTML snippet.
*/
protected function _get_page_view_link() {
if ( empty( $this->_args['value'] ) ) {
return '';
}
$post = get_post( $this->_args['value'] );
if ( empty( $post->ID ) ) {
return '';
}
$args = array(
'view' => Ai1ec_I18n::__( 'View' ),
'link' => get_permalink( $post->ID ),
'title' => apply_filters(
'the_title',
$post->post_title,
$post->ID
),
);
return $this->_registry->get( 'theme.loader' )
->get_file( 'setting/calendar-page-selector.twig', $args, true )
->get_content();
}
/**
* Generate dropdown selector to choose page.
*
* @return string HTML snippet.
*/
protected function _get_pages_selector() {
$html = '<select id="' . self::ELEMENT_ID .
'" class="ai1ec-form-control" name="' . self::ELEMENT_ID . '">';
$list = $this->_get_pages();
foreach ( $list as $key => $value ) {
$html .= '<option value="' . $this->_html->esc_attr( $key ) . '"';
if ( $this->_args['value'] === $key ) {
$html .= ' selected="selected"';
}
$html .= '>' . $this->_html->esc_html( $value ) . '</option>';
}
$html .= '</select>';
return $html;
}
/**
* Make a map of page IDs and titles for selection snippet.
*
* @return array Map of page keys and titles.
*/
protected function _get_pages() {
$pages = get_pages();
if ( ! is_array( $pages ) ) {
$pages = array();
}
$output = array(
'__auto_page:Calendar' => Ai1ec_I18n::__(
'- Auto-Create New Page -'
),
);
foreach ( $pages as $key => $value ) {
$output[$value->ID] = $value->post_title;
}
return $output;
}
}

View File

@@ -0,0 +1,36 @@
<?php
/**
* Renderer of settings page checkbox option.
*
* @author Time.ly Network, Inc.
* @instantiator new
* @since 2.0
* @package Ai1EC
* @subpackage Ai1EC.Html
*/
class Ai1ec_Html_Settings_Checkbox extends Ai1ec_Html_Element_Settings {
/* (non-PHPdoc)
* @see Ai1ec_Html_Element_Settings::render()
*/
public function render( $output = '', $wrap = true, $hidden = false ) {
$attributes = array(
'class' => 'checkbox',
);
if ( true === $this->_args['value'] ) {
$attributes['checked'] = 'checked';
}
$args = $this->_args;
$args['attributes'] = $attributes;
$loader = $this->_registry->get( 'theme.loader' );
$file = $loader->get_file(
'setting/checkbox.twig',
$args,
true
);
return parent::render( $file->get_content(), $wrap, $hidden );
}
}

View File

@@ -0,0 +1,28 @@
<?php
/**
* Renderer of settings page custom option.
*
* @author Time.ly Network, Inc.
* @instantiator new
* @since 2.4
* @package Ai1EC
* @subpackage Ai1EC.Html
*/
class Ai1ec_Html_Setting_Custom extends Ai1ec_Html_Element_Settings {
/* (non-PHPdoc)
* @see Ai1ec_Html_Element_Settings::render()
*/
public function render( $output = '', $wrap = true, $hidden = false ) {
$label = $this->_args['renderer']['label'];
$content = $this->_args['renderer']['content'];
$loader = $this->_registry->get( 'theme.loader' );
$file = $loader->get_file( 'setting/custom.twig', array(
'label' => $label,
'content' => $content
), true );
return parent::render( $file->get_content(), $wrap, $hidden );
}
}

View File

@@ -0,0 +1,60 @@
<?php
/**
* Renderer of settings page Enabled views selection snippet.
*
* @author Time.ly Network, Inc.
* @since 2.0
* @package Ai1EC
* @subpackage Ai1EC.Html
*/
class Ai1ec_Html_Element_Enabled_Views
extends Ai1ec_Html_Element_Settings {
/* (non-PHPdoc)
* @see Ai1ec_Html_Element_Settings::render()
*/
public function render( $output = '', $wrap = true, $hidden = false ) {
$this->_convert_values();
$args = array(
'views' => $this->_args['value'],
'label' => $this->_args['renderer']['label'],
'text_enabled' => __( 'Enabled', AI1EC_PLUGIN_NAME ),
'text_default' => __( 'Default', AI1EC_PLUGIN_NAME ),
'text_desktop' => __( 'Desktop', AI1EC_PLUGIN_NAME ),
'text_mobile' => __( 'Mobile', AI1EC_PLUGIN_NAME ),
);
$loader = $this->_registry->get( 'theme.loader' );
return $loader->get_file( 'setting/enabled-views.twig', $args, true )
->get_content();
}
/**
* Convert values to bo used in rendering
*/
protected function _convert_values() {
foreach( $this->_args['value'] as &$view ) {
$view['enabled'] = $view['enabled'] ?
'checked="checked"' :
'';
$view['default'] = $view['default'] ?
'checked="checked"' :
'';
// Use mobile settings if available, else fall back to desktop settings.
$view['enabled_mobile'] = isset( $view['enabled_mobile'] ) ?
( $view['enabled_mobile'] ?
'checked="checked"' :
'' ) :
$view['enabled'];
$view['default_mobile'] = isset( $view['default_mobile'] ) ?
( $view['default_mobile'] ?
'checked="checked"' :
'' ) :
$view['default'];
$view['longname'] = translate_nooped_plural(
$view['longname'],
1
);
}
}
}

Some files were not shown because too many files have changed in this diff Show More