Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

425 lines (378 sloc) 14.44 kb
<?php
/**
* @package Habari
*
*/
/**
* Habari Themes class
*
*/
class Themes
{
private static $all_themes = null;
private static $all_data = null;
/**
* Returns the theme dir and path information
* @return array An array of Theme data
**/
public static function get_all()
{
if ( !isset( self::$all_themes ) ) {
$dirs = array( HABARI_PATH . '/system/themes/*' , HABARI_PATH . '/3rdparty/themes/*', HABARI_PATH . '/user/themes/*' );
if ( Site::is( 'multi' ) ) {
$dirs[] = Site::get_dir( 'config' ) . '/themes/*';
}
$themes = array();
foreach ( $dirs as $dir ) {
$themes = array_merge( $themes, Utils::glob( $dir, GLOB_ONLYDIR | GLOB_MARK ) );
}
$themes = array_filter( $themes, function($a) {return file_exists( $a . "/theme.xml" );} );
$themefiles = array_map( 'basename', $themes );
self::$all_themes = array_combine( $themefiles, $themes );
}
return self::$all_themes;
}
/**
* Returns all theme information -- dir, path, theme.xml, screenshot url
* @return array An array of Theme data
**/
public static function get_all_data()
{
if ( !isset( self::$all_data ) ) {
foreach ( self::get_all() as $theme_dir => $theme_path ) {
$themedata = array();
$themedata['dir'] = $theme_dir;
$themedata['path'] = $theme_path;
$themedata['theme_dir'] = $theme_path;
$themedata['info'] = simplexml_load_file( $theme_path . '/theme.xml' );
if ( $themedata['info']->getName() != 'pluggable' || (string) $themedata['info']->attributes()->type != 'theme' ) {
$themedata['screenshot'] = Site::get_url( 'admin_theme' ) . "/images/screenshot_default.png";
$themedata['info']->description = '<span class="error">' . _t( 'This theme is a legacy theme that is not compatible with Habari ' ) . Version::get_habariversion() . '. <br><br>Please update your theme.</span>';
$themedata['info']->license = '';
}
else {
foreach ( $themedata['info'] as $name=>$value ) {
$themedata[$name] = (string) $value;
}
if ( $screenshot = Utils::glob( $theme_path . '/screenshot.{png,jpg,gif}', GLOB_BRACE ) ) {
$themedata['screenshot'] = Site::get_url( 'habari' ) . dirname( str_replace( HABARI_PATH, '', $theme_path ) ) . '/' . basename( $theme_path ) . "/" . basename( reset( $screenshot ) );
}
else {
$themedata['screenshot'] = Site::get_url( 'admin_theme' ) . "/images/screenshot_default.png";
}
}
self::$all_data[$theme_dir] = $themedata;
}
}
return self::$all_data;
}
/**
* Returns the name of the active or previewed theme
*
* @params boolean $nopreview If true, return the real active theme, not the preview
* @return string the current theme or previewed theme's directory name
*/
public static function get_theme_dir( $nopreview = false )
{
if ( !$nopreview && isset( $_SESSION['user_theme_dir'] ) ) {
$theme_dir = $_SESSION['user_theme_dir'];
}
else {
$theme_dir = Options::get( 'theme_dir' );
}
$theme_dir = Plugins::filter( 'get_theme_dir', $theme_dir );
return $theme_dir;
}
/**
* Returns the active theme's full directory path.
* @params boolean $nopreview If true, return the real active theme, not the preview
* @return string The full path to the active theme directory
*/
private static function get_active_theme_dir( $nopreview = false )
{
$theme_dir = self::get_theme_dir( $nopreview );
$themes = Themes::get_all();
// If our active theme directory has gone missing, iterate through the others until we find one we can use and activate it.
if ( !isset( $themes[$theme_dir] ) ) {
$theme_exists = false;
foreach ( $themes as $themedir ) {
if ( file_exists( Utils::end_in_slash( $themedir ) . 'theme.xml' ) ) {
$theme_dir = basename( $themedir );
EventLog::log( _t( "Active theme directory no longer available. Falling back to '{$theme_dir}'" ), 'err', 'theme', 'habari' );
Options::set( 'theme_dir', basename( $themedir ) );
$fallback_theme = Themes::create();
Plugins::act_id( 'theme_activated', $fallback_theme->plugin_id(), $theme_dir, $fallback_theme );
$theme_exists = true;
// Refresh to the newly "activated" theme to ensure it takes the options that have just been set above and doesn't produce any errors.
Utils::redirect();
break;
}
}
if ( !$theme_exists ) {
die( _t( 'There is no valid theme currently installed.' ) );
}
}
return $themes[$theme_dir];
}
/**
* Returns the active theme information from the database
* @params boolean $nopreview If true, return the real active theme, not the preview
* @return QueryRecord An array of Theme data
**/
public static function get_active( $nopreview = false )
{
$theme = array();
$theme['theme_dir'] = Themes::get_active_theme_dir( $nopreview );
$data = simplexml_load_file( Utils::end_in_slash( $theme['theme_dir'] ) . 'theme.xml' );
foreach ( $data as $name=>$value ) {
$theme[$name] = (string) $value;
}
$theme['xml'] = $data;
return $theme;
}
private static function get_by_name($name) {
$themes = self::get_all_data();
foreach($themes as $theme) {
if($name == $theme['info']->name) {
return $theme;
}
}
return false;
}
/**
* Returns theme information for the active theme -- dir, path, theme.xml, screenshot url
* @params boolean $nopreview If true, return the real active theme, not the preview
* @return array An array of Theme data
*/
public static function get_active_data( $nopreview = false )
{
$all_data = Themes::get_all_data();
$active_theme_dir = basename( Themes::get_active_theme_dir( $nopreview ) );
$active_data = $all_data[$active_theme_dir];
return $active_data;
}
/**
* Ensure that a theme meets requirements for activation/preview
* @static
* @param string $theme_dir the directory of the theme
* @return bool True if the theme meets all requirements
*/
public static function validate_theme( $theme_dir )
{
$all_themes = Themes::get_all_data();
// @todo Make this a closure in php 5.3
$theme_names = Utils::array_map_field($all_themes, 'name');
$theme_data = $all_themes[$theme_dir];
$ok = true;
if(isset($theme_data['info']->parent) && !in_array((string)$theme_data['info']->parent, $theme_names)) {
Session::error(_t('This theme requires the parent theme named "%s" to be present prior to activation.', array($theme_data['info']->parent)));
$ok = false;
}
if(isset($theme_data['info']->requires)) {
$provided = Plugins::provided();
foreach($theme_data['info']->requires->feature as $requirement) {
if(!isset($provided[(string)$requirement])) {
Session::error(_t('This theme requires the feature "<a href="%2$s">%1$s</a>" to be present prior to activation.', array((string)$requirement, $requirement['url'])));
$ok = false;
}
}
}
return $ok;
}
/**
* function activate_theme
* Updates the database with the name of the new theme to use
* @param string the name of the theme
**/
public static function activate_theme( $theme_name, $theme_dir )
{
$ok = Themes::validate_theme($theme_dir);
$ok = Plugins::filter( 'activate_theme', $ok, $theme_name ); // Allow plugins to reject activation
if($ok) {
$old_active_theme = Themes::create();
Plugins::act_id( 'theme_deactivated', $old_active_theme->plugin_id(), $old_active_theme->name, $old_active_theme ); // For the theme itself to react to its deactivation
Plugins::act( 'theme_deactivated_any', $old_active_theme->name, $old_active_theme ); // For any plugin to react to its deactivation
Options::set( 'theme_name', $theme_name );
Options::set( 'theme_dir', $theme_dir );
$new_active_theme = Themes::create();
// Set version of theme if it wasn't installed before
$versions = Options::get( 'pluggable_versions' );
if(!isset($versions[get_class($new_active_theme)])) {
$versions[get_class($new_active_theme)] = $new_active_theme->get_version();
Options::set( 'pluggable_versions', $versions );
}
// Run activation hooks for theme activation
Plugins::act_id( 'theme_activated', $new_active_theme->plugin_id(), $theme_name, $new_active_theme ); // For the theme itself to react to its activation
Plugins::act( 'theme_activated_any', $theme_name, $new_active_theme ); // For any plugin to react to its activation
EventLog::log( _t( 'Activated Theme: %s', array( $theme_name ) ), 'notice', 'theme', 'habari' );
}
return $ok;
}
/**
* Sets a theme to be the current user's preview theme
*
* @param string $theme_name The name of the theme to preview
* @param string $theme_dir The directory of the theme to preview
*/
public static function preview_theme( $theme_name, $theme_dir )
{
$ok = Themes::validate_theme($theme_dir);
if($ok) {
$_SESSION['user_theme_name'] = $theme_name;
$_SESSION['user_theme_dir'] = $theme_dir;
// Execute the theme's activated action
$preview_theme = Themes::create();
Plugins::act_id( 'theme_activated', $preview_theme->plugin_id(), $theme_name, $preview_theme );
EventLog::log( _t( 'Previewed Theme: %s', array( $theme_name ) ), 'notice', 'theme', 'habari' );
}
return $ok;
}
/**
* Cancel the viewing of any preview theme
*/
public static function cancel_preview()
{
if ( isset( $_SESSION['user_theme_name'] ) ) {
// Execute the theme's deactivated action
$preview_theme = Themes::create();
Plugins::act_id( 'theme_deactivated', $preview_theme->plugin_id() );
EventLog::log( _t( 'Canceled Theme Preview: %s', array( $_SESSION['user_theme_name'] ) ), 'notice', 'theme', 'habari' );
unset( $_SESSION['user_theme_name'] );
unset( $_SESSION['user_theme_dir'] );
}
}
/**
* Returns a named Theme descendant.
* If no parameter is supplied, then
* load the active theme from the database.
*
* If no theme option is set, a fatal error is thrown
*
* @param name ( optional ) override the default theme lookup
* @param template_engine ( optional ) specify a template engine
* @param theme_dir ( optional ) specify a theme directory
**/
public static function create( $name = null, $template_engine = null, $theme_dir = null )
{
static $bound = array();
$hash = md5(serialize(array($name, $template_engine, $theme_dir)));
if(isset($bound[$hash])) {
return $bound[$hash];
}
// If the name is not supplied, load the active theme
if(empty($name)) {
$themedata = self::get_active();
if ( empty( $themedata ) ) {
die( _t( 'Theme not installed.' ) );
}
}
// Otherwise, try to load the named theme from user themes that are present
else {
$themedata = self::get_by_name( $name );
}
// If a theme wasn't found by name, create a blank object
if(!$themedata) {
$themedata = array();
$themedata['name'] = $name;
$themedata['version'] = 0;
}
// If a specific template engine was supplied, use it
if(!empty($template_engine)) {
$themedata['template_engine'] = $template_engine;
}
// If a theme directory was supplied, use the directory that was supplied
if(!empty($theme_dir)) {
$themedata['theme_dir'] = $theme_dir;
}
// Set the default theme file
$themefile = 'theme.php';
if(isset($themedata['info']->class['file']) && (string)$themedata['info']->class['file'] != '') {
$themefile = (string)$themedata->xml->class['file'];
}
// Convert themedata to QueryRecord for legacy purposes
// @todo: Potentially break themes by sending an array to the constructor instead of this QueryRecord
$themedata = new QueryRecord($themedata);
// Deal with parent themes
if(isset($themedata->parent)) {
// @todo If the parent theme doesn't exist, provide a useful error
$parent_data = self::get_by_name( $themedata->parent );
$parent_themefile = 'theme.php';
if(isset($parent_data['info']->class['file']) && (string)$parent_data['info']->class['file'] != '') {
$parent_themefile = (string)$parent_data['info']->class['file'];
}
include_once($parent_data['theme_dir'] . $parent_themefile);
$themedata->parent_theme_dir = Utils::single_array($parent_data['theme_dir']);
$themedata->theme_dir = array_merge(Utils::single_array($themedata->theme_dir), $themedata->parent_theme_dir);
}
$primary_theme_dir = $themedata->theme_dir;
$primary_theme_dir = is_array($primary_theme_dir) ? reset($primary_theme_dir) : $primary_theme_dir;
// Include the theme class file
if ( file_exists( $primary_theme_dir . $themefile ) ) {
include_once( $primary_theme_dir . $themefile );
}
if ( isset( $themedata->class ) ) {
$classname = $themedata->class;
}
else {
$classname = self::class_from_filename( $primary_theme_dir . $themefile );
}
// the final fallback, for the admin "theme"
if ( $classname == '' ) {
$classname = 'Theme';
}
$created_theme = new $classname( $themedata );
$created_theme->upgrade();
Plugins::act_id( 'init_theme', $created_theme->plugin_id(), $created_theme );
Plugins::act( 'init_theme_any', $created_theme );
$bound[$hash] = $created_theme;
return $created_theme;
}
public static function class_from_filename( $file, $check_realpath = false )
{
if ( $check_realpath ) {
$file = realpath( $file );
}
$theme_classes = self::get_theme_classes();
foreach ( $theme_classes as $theme ) {
$class = new ReflectionClass( $theme );
$classfile = str_replace( '\\', '/', $class->getFileName() );
if ( $classfile == $file ) {
return $theme;
}
}
// if we haven't found the plugin class, try again with realpath resolution:
if ( $check_realpath ) {
// really can't find it
return false;
}
else {
return self::class_from_filename( $file, true );
}
}
/**
* Get a list of classes that extend Theme
* @static
* @return array List of string names of classes that extend Theme
*/
public static function get_theme_classes()
{
$classes = get_declared_classes();
foreach($classes as $class) {
$parents = class_parents( $class, false );
if(count($parents) > 0) {
$class_parents[$class] = $parents;
}
}
$theme_classes = array();
do {
$delta = count($theme_classes);
foreach($class_parents as $class => $parents) {
if(count(array_intersect($theme_classes + array('Theme'), $parents))>0) {
$theme_classes[$class] = $class;
}
}
} while($delta != count($theme_classes));
return $theme_classes;
}
}
?>
Jump to Line
Something went wrong with that request. Please try again.