Seditio Source
Root |
./othercms/ips_4.3.4/applications/cms/sources/Templates/Templates.php
<?php
/**
 * @brief        Templates Model
 * @author        <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
 * @copyright    (c) Invision Power Services, Inc.
 * @license        https://www.invisioncommunity.com/legal/standards/
 * @package        Invision Community
 * @subpackage    Content
 * @since        25 Feb 2014
 */

namespace IPS\cms;

/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
   
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
    exit;
}

/**
 * @brief    Template Model
 */
class _Templates extends \IPS\Patterns\ActiveRecord
{
   
/**
     * @brief    [ActiveRecord] Multiton Store
     */
   
protected static $multitons;
   
   
/**
     * @brief    [ActiveRecord] Database Prefix
     */
   
public static $databasePrefix = 'template_';
   
   
/**
     * @brief    [ActiveRecord] ID Database Table
     */
   
public static $databaseTable = 'cms_templates';
   
   
/**
     * @brief    [ActiveRecord] ID Database Column
     */
   
public static $databaseColumnId = 'id';
   
   
/**
     * @brief    [ActiveRecord] Database ID Fields
     */
   
protected static $databaseIdFields = array( 'template_key', 'template_id' );
   
   
/**
     * @brief    [ActiveRecord] Multiton Map
     */
   
protected static $multitonMap    = array();
       
   
/**
     * @brief    Retusn all types
     */
   
const RETURN_ALL = 1;
   
   
/**
     * @brief    Returns block templates
     */
   
const RETURN_BLOCK = 2;
   
   
/**
     * @brief    Return page templates
     */
   
const RETURN_PAGE = 4;
   
   
/**
     * @brief    Return database templates
     */
   
const RETURN_DATABASE = 8;

   
/**
     * @brief    Return just css type
     */
   
const RETURN_ONLY_CSS = 16;

   
/**
     * @brief    Return just js type
     */
   
const RETURN_ONLY_JS = 32;

   
/**
     * @brief    Return just template type
     */
   
const RETURN_ONLY_TEMPLATE = 64;

   
/**
     * @brief    Return just contents of cms_templates ignoring IN_DEV and DESIGNERS' MODE
     */
   
const RETURN_DATABASE_ONLY = 128;

   
/**
     * @brief    Return both IN_DEV and database templates
     */
   
const RETURN_DATABASE_AND_IN_DEV = 256;
   
   
/**
     * @brief    Default database template group names
     */
   
public static $databaseDefaults = array(
       
'featured'   => 'category_articles',
       
'form'         => 'form',
       
'display'    => 'display',
       
'listing'    => 'listing',
       
'categories' => 'category_index'
   
);
   
   
/**
     * Ensure that the template is calling the correct groups
     *
     * @param    string    $group    Group to load templates from
     * @return  void
     */
   
public static function fixTemplateTags( $group )
    {
       
$templates = iterator_to_array( \IPS\Db::i()->select( '*', 'cms_templates', array( array( 'template_group=?', $group ) ) )->setKeyField('template_title') );
       
        foreach(
$templates as $template )
        {
           
$save = array();
           
           
/* Make sure template tags call the correct group */
           
if ( mb_stristr( $template['template_content'], '{template' ) )
            {
               
preg_match_all( '/\{([a-z]+?=([\'"]).+?\\2 ?+)}/', $template['template_content'], $matches, PREG_SET_ORDER );

               
/* Work out the plugin and the values to pass */
               
foreach( $matches as $index => $array )
                {
                   
preg_match_all( '/(.+?)=' . $array[ 2 ] . '(.+?)' . $array[ 2 ] . '\s?/', $array[ 1 ], $submatches );

                   
$plugin = array_shift( $submatches[ 1 ] );
                    if (
$plugin == 'template' )
                    {
                       
$value   = array_shift( $submatches[ 2 ] );
                       
$options = array();

                        foreach (
$submatches[ 1 ] as $k => $v )
                        {
                           
$options[ $v ] = $submatches[ 2 ][ $k ];
                        }

                        if ( isset(
$options['app'] ) and $options['app'] == 'cms' and isset( $options['location'] ) and $options['location'] == 'database' and isset( $options['group'] ) and $options['group'] != $template['template_original_group'] )
                        {
                            if (
in_array( $value, array_keys( $templates ) ) )
                            {
                               
$options['group'] = $group;

                               
$replace = '{template="' . $value . '" app="' . $options['app'] . '" location="' . $options['location'] . '" group="' . $options['group'] . '" params="' . ( isset($options['params']) ? $options['params'] : NULL ) . '"}';
                               
$save['template_content'] = str_replace( $matches[$index][0], $replace, $template['template_content'] );
                            }
                        }
                       
                        if (
count( $save ) )
                        {
                            \
IPS\Db::i()->update( 'cms_templates', $save, array( 'template_id=?', $template['template_id'] ) );
                        }
                    }
                }
            }
        }
    }
   
   
/**
     * Load Record
     * Overloaded so we can force loading by key by default but still retain the template_id field as the primary key so
     * save still updates the primary ID.
     *
     * @see        \IPS\Db::build
     * @param    int|string    $id                    ID
     * @param    string        $idField            The database column that the $id parameter pertains to (NULL will use static::$databaseColumnId)
     * @param    mixed        $extraWhereClause    Additional where clause(s) (see \IPS\Db::build for details)
     * @return    static
     * @throws    \InvalidArgumentException
     * @throws    \OutOfRangeException
     */
   
public static function load( $id, $idField=NULL, $extraWhereClause=NULL )
    {
        if ( !
is_numeric( $id ) and $idField === NULL )
        {
           
$idField = 'template_key';
        }
       
        if ( !
is_numeric( $id ) and ( \IPS\IN_DEV or \IPS\Theme::designersModeEnabled() ) )
        {
           
$templates = \IPS\cms\Theme::i()->getRawTemplates( NULL, NULL, NULL, \IPS\cms\Theme::RETURN_AS_OBJECT );

            if ( isset(
$templates[ $id ] ) )
            {
                return
$templates[ $id ];
            }
        }
           
        try
        {
            return
parent::load( $id, $idField, $extraWhereClause );
        }
        catch( \
OutOfRangeException $ex )
        {
            throw
$ex;
        }
    }
   
   
/**
     * Make a group_name readable (Group Name)
     *
     * @param    string    $name        Group name from the database
     * @return    string
     */
   
public static function readableGroupName( $name )
    {
        if (
$name === 'js' )
        {
            return
'JS';
        }
        else if (
$name === 'css' )
        {
            return
'CSS';
        }

        return
ucwords( str_replace( array( '-', '_' ), ' ', $name ) );
    }
   
   
/**
     * Get all template group
     *
     * @param    int|constant    $returnType        Determines the content returned
     * @return    array
     */
   
public static function getGroups( $returnType=1 )
    {
       
$where  = array();
       
$return = array();
       
$locations = NULL;
       
        if (
is_string( $returnType ) )
        {
            switch(
$returnType )
            {
                case
'all':
                   
$returnType = self::RETURN_ALL;
                break;
                case
'block':
                   
$returnType = self::RETURN_BLOCK;
                break;
                case
'page':
                   
$returnType = self::RETURN_PAGE;
                break;
                case
'database':
                   
$returnType = self::RETURN_DATABASE;
                break;
            }
        }
       
        if (
$returnType & self::RETURN_ALL )
        {
           
$where[] = array( 'template_location !=?', NULL );
        }
        else
        {
           
$locations = array();
           
            if (
$returnType & self::RETURN_BLOCK )
            {
               
$locations[] = 'block';
            }
           
            if (
$returnType & self::RETURN_PAGE )
            {
               
$locations[] = 'page';
            }
           
            if (
$returnType & self::RETURN_DATABASE )
            {
               
$locations[] = 'database';
            }
           
            if ( !
count( $locations ) )
            {
                throw new \
UnexpectedValueException();
            }
           
           
$where[] = array( "template_location IN ('" . implode( "','", $locations ) . "')" );
        }
       
        foreach( \
IPS\Db::i()->select( 'template_group', static::$databaseTable, $where, 'template_group ASC', NULL, 'template_group' ) as $template )
        {
           
$return[ $template ] = $template;
        }
       
        return
$return;
    }
   
   
/**
     * Get all templates
     *
     * @param    int|constant    $returnType        Determines the content returned
     * @return    array
     */
   
public static function getTemplates( $returnType=1 )
    {
       
$where  = array();
       
$return = array();
       
$locations = NULL;

        if ( ( \
IPS\IN_DEV or \IPS\Theme::designersModeEnabled() ) AND ( $returnType & self::RETURN_DATABASE_AND_IN_DEV ) AND ! ( $returnType & self::RETURN_DATABASE_ONLY ) )
        {
           
$flags = \IPS\cms\Theme::RETURN_AS_OBJECT;

            if (
$returnType & self::RETURN_ONLY_TEMPLATE )
            {
               
$flags += \IPS\cms\Theme::RETURN_ONLY_TEMPLATE;
            }
            else if (
$returnType & self::RETURN_ONLY_CSS )
            {
               
$flags += \IPS\cms\Theme::RETURN_ONLY_CSS;
            }
            else if (
$returnType & self::RETURN_ONLY_JS )
            {
               
$flags += \IPS\cms\Theme::RETURN_ONLY_JS;
            }
            else
            {
                if (
$returnType & self::RETURN_BLOCK )
                {
                   
$flags += \IPS\cms\Theme::RETURN_BLOCK;
                }

                if (
$returnType & self::RETURN_PAGE )
                {
                   
$flags += \IPS\cms\Theme::RETURN_PAGE;
                }

                if (
$returnType & self::RETURN_DATABASE )
                {
                   
$flags += \IPS\cms\Theme::RETURN_DATABASE;
                }
            }

           
$return = \IPS\cms\Theme::i()->getRawTemplates( 'cms', NULL, NULL, $flags );
        }

        if ( ! ( \
IPS\IN_DEV or \IPS\Theme::designersModeEnabled() ) OR ( $returnType & self::RETURN_DATABASE_AND_IN_DEV ) OR ( $returnType & self::RETURN_DATABASE_ONLY ) )
        {
            if (
$returnType & self::RETURN_ALL )
            {
               
$where[] = array( 'template_location !=?', NULL );
            }
            else if (
$returnType & self::RETURN_ONLY_TEMPLATE )
            {
               
$where[] = array( 'template_type = ?', 'template' );
            }
            else if (
$returnType & self::RETURN_ONLY_CSS )
            {
               
$where[] = array( 'template_type = ?', 'css' );
            }
            else if (
$returnType & self::RETURN_ONLY_JS )
            {
               
$where[] = array( 'template_type = ?', 'js' );
            }
            else
            {
               
$locations = array();

                if (
$returnType & self::RETURN_BLOCK )
                {
                   
$locations[] = 'block';
                }

                if (
$returnType & self::RETURN_PAGE )
                {
                   
$locations[] = 'page';
                }

                if (
$returnType & self::RETURN_DATABASE )
                {
                   
$locations[] = 'database';
                }

                if ( !
count( $locations ) )
                {
                    throw new \
UnexpectedValueException();
                }

               
$where[] = array( "template_location IN ('" . implode( "','", $locations ) . "')" );
            }

            foreach ( \
IPS\Db::i()->select( '*', static::$databaseTable, $where, 'template_user_edited DESC' ) as $template )
            {
               
/* user_edited version is returned first, so only add to the array if the key isn't already in $return */
               
if ( !isset( $return[ $template['template_key'] ] ) )
                {
                   
$return[ $template['template_key'] ] = static::constructFromData( $template );
                }
            }
        }

        return
$return;
    }
   
   
/**
     * Construct Load Query
     * Overloaded so we return the user_edited version where available
     *
     * @param    int|string    $id                    ID
     * @param    string        $idField            The database column that the $id parameter pertains to
     * @param    mixed        $extraWhereClause    Additional where clause(s)
     * @return    \IPS\Db\Select
     */
   
protected static function constructLoadQuery( $id, $idField, $extraWhereClause )
    {
       
$where = array( array( $idField . '=?', $id ) );
        if(
$extraWhereClause !== NULL )
        {
            if ( !
is_array( $extraWhereClause ) or !is_array( $extraWhereClause[0] ) )
            {
               
$extraWhereClause = array( $extraWhereClause );
            }
           
$where = array_merge( $where, $extraWhereClause );
        }
   
        return static::
db()->select( '*', static::$databaseTable, $where, 'template_user_edited DESC' );
    }

   
/**
     * Generate a tree of templates
     *
     * @param array $templates    Template data from the database
     * @return array
     */
   
public static function buildTree( $templates )
    {
       
$return = array();

        foreach(
$templates as $id => $template )
        {
           
$return[ $template->location ][ $template->group ][ $template->key ] = $template;
        }

        return
$return;
    }
   
   
/**
     * Add a new template
     *
     * @param    array    $template    Template Data
     * @return    object    \IPS\cms\Templates
     */
   
public static function add( $template )
    {
       
$newTemplate = new static;

        foreach(
$template as $_k => $_v )
        {
           
$newTemplate->$_k    = $_v;
        }

       
$newTemplate->_new         = TRUE;
       
$newTemplate->user_created = 1;
       
$newTemplate->user_edited  = 1;
       
$newTemplate->master       = 0;

       
$newTemplate->save();

       
/* Create a unique key */
       
$newTemplate->key = $newTemplate->location . '_' . \IPS\Http\Url\Friendly::seoTitle( $newTemplate->title ) . '_' . $newTemplate->id;

       
/* Make sure there's no double __ in there */
       
foreach( array( 'group', 'title', 'key' ) as $field )
        {
            if (
mb_strstr( $newTemplate->$field, '__' ) )
            {
               
$newTemplate->$field = str_replace( '__', '_', $newTemplate->$field );
            }
        }

       
$newTemplate->save();
       
        return
$newTemplate;
    }
   
   
/**
     * Removes all stored files so they can be rebuilt on the fly
     *
     * @return void
     */
   
public static function deleteCompiledFiles()
    {
        foreach( \
IPS\Db::i()->select( '*', 'cms_templates', array( 'template_file_object IS NOT NULL' ) ) as $template )
        {
            try
            {
                \
IPS\File::get( 'core_Theme', $template['template_file_object'] )->delete();
            }
            catch( \
Exception $ex ) { }
        }
       
        \
IPS\Db::i()->update( 'cms_templates', array( 'template_file_object' => NULL ) );
    }
   
   
/**
     * Is suitable to be used for a custom wrapper?
     *
     * @return boolean
     */
   
public function isSuitableForCustomWrapper()
    {
        if (
$this->location == 'page' and preg_match( '#<html([^>]+?)?>#', $this->content ) )
        {
            if (
preg_match( '#\$html(\s|=|,)#', $this->params ) and preg_match( '#\$title(\s|=|,|$)#', $this->params ) )
            {
                return
true;
            }
        }

        return
false;
    }

   
/**
     * Is suitable to be used for a builder column wrapper?
     *
     * @return boolean
     */
   
public function isSuitableForBuilderWrapper()
    {
        if (
$this->location == 'page' and mb_stristr( $this->content, '{template="widgetContainer"' ) )
        {
            return
true;
        }

        return
false;
    }

   
/**
     * Import templates from an XML file
     *
     * @param   string      $file       File to load from (data/ or tmp/)
     * @param    int|null    $offset     Offset to begin import from
     * @param    int|null    $limit        Number of rows to import
     * @param   boolean     $update       If updating, files written as master templates
     * @return    bool        Rows imported (true) or none imported (false)
     */
   
public static function importXml( $file, $offset=NULL, $limit=NULL, $update=TRUE )
    {
       
$i        = 0;
       
$worked    = false;
   
        if(
file_exists( $file ) )
        {
           
/* First, delete any existing skin data for this app. */
           
if( $offset === NULL OR $offset === 0 )
            {
                if (
$update === TRUE )
                {
                    \
IPS\Db::i()->delete( 'cms_templates', array( 'template_master=1' ) );
                    \
IPS\cms\Theme::deleteCompiledTemplate( 'cms' );
                }
            }

           
/* Open XML file */
           
$xml = new \IPS\Xml\XMLReader;
           
$xml->open( $file );
           
$xml->read();

            while(
$xml->read() )
            {
                if(
$xml->nodeType != \XMLReader::ELEMENT )
                {
                    continue;
                }

               
$i++;

                if (
$offset !== null )
                {
                    if (
$i - 1 < $offset )
                    {
                       
$xml->next();
                        continue;
                    }
                }

                if(
$xml->name == 'template' )
                {
                   
$save = array(
                       
'template_content'        => $xml->readString(),
                       
'template_master'         => 1,
                       
'template_original_group' => $xml->getAttribute('template_group'),
                       
'template_file_object'    => NULL
                   
);

                    foreach( array(
'key', 'title', 'desc', 'location', 'group', 'params', 'app', 'type' ) as $field )
                    {
                       
$save[ 'template_' . $field ] = $xml->getAttribute( 'template_' . $field );
                    }

                    \
IPS\Db::i()->insert( 'cms_templates', $save );
                   
$worked    = true;
                }

                if(
$limit !== null AND $i === ( $limit + $offset ) )
                {
                    break;
                }
            }
        }

        return
$worked;
    }

   
/**
     * Delete
     * Overloaded to protect inheritence
     *
     *  @return void
     */
   
public function delete()
    {
        \
IPS\cms\Theme::deleteCompiledTemplate( 'cms', $this->location, $this->group );

        if (
$this->user_created )
        {
            \
IPS\Db::i()->delete( 'cms_templates', array( 'template_key=? AND template_user_created=?', $this->key, 1 ) );

            if ( isset( static::
$multitons[ $this->id ] ) )
            {
                unset( static::
$multitons[ $this->id ] );
            }
        }
        else
        {
            if (
$this->user_edited )
            {
                \
IPS\Db::i()->delete( 'cms_templates', array( 'template_key=? AND template_user_edited=?', $this->key, 1 ) );

                if ( isset( static::
$multitons[ $this->id ] ) )
                {
                    unset( static::
$multitons[ $this->id ] );
                }
            }
            else
            {
                throw new \
OutOfRangeException('CANNOT_DELETE');
            }
        }
    }

   
/**
     * Get the inherited string
     *
     * @return string
     */
   
public function get__inherited()
    {
        if (
$this->user_created )
        {
            return
'custom';
        }
        elseif (
$this->user_edited )
        {
            return
'changed';
        }

        return
'original';
    }

   
/**
     * Get the file object
     *
     * @return string
     */
   
public function get__file_object()
    {
        if ( !
$this->file_object )
        {
           
$content = $this->content;
           
           
/* Build on demand */
           
if ( $this->type != 'js' and ( mb_stristr( $this->content, "{block=" ) or mb_stristr( $this->content, "{{if" ) or mb_stristr( $this->content, "{media=" ) ) )
            {
               
$functionName = 'css_' . mt_rand();
                \
IPS\Theme::makeProcessFunction( str_replace( '\\', '\\\\', $content ), $functionName );
               
$functionName = "IPS\Theme\\{$functionName}";
               
$content = $functionName();
            }
           
           
$this->file_object = (string) \IPS\File::create( 'cms_Pages', $this->title, $content ?: ' ', 'page_objects', TRUE );
           
parent::save(); # Go to parent save to prevent $this->save() from wiping file objects
       
}

        return
$this->file_object;
    }

   
/**
     * Save
     *
     * @return void
     */
   
public function save()
    {
       
/* Trash file object if appropriate */
       
if ( $this->file_object )
        {
            try
            {
                \
IPS\File::get( 'cms_Pages', $this->file_object )->delete();
            }
            catch ( \
Exception $e )
            {
               
/* Just to be sure nothing is throw, we don't care too much if it's not deleted */
           
}

           
/* Trash all cached page file objects too */
           
\IPS\cms\Pages\Page::deleteCachedIncludes( $this->file_object );

           
$this->file_object = NULL;
        }

       
/* Should we copy this to a new template and then save it? */
       
if ( ! $this->user_edited )
        {
           
/* Infinite loop is only cool as Apple's address */
           
$clone = new \IPS\cms\Templates;
           
           
$clone->_data   = $this->_data;
           
$clone->changed = $this->changed;
           
           
$clone->id = NULL;
           
$clone->user_edited = 1;
           
$clone->master = 0;
           
$clone->_new = TRUE;
           
$clone->save();

           
$key = \strtolower( 'template_cms_' . \IPS\cms\Theme::makeBuiltTemplateLookupHash( 'cms', $clone->location, $clone->group ) . '_' . $clone->group );
        }
        else
        {
           
$key = \strtolower( 'template_cms_' . \IPS\cms\Theme::makeBuiltTemplateLookupHash( 'cms', $this->location, $this->group ) . '_' . $this->group );

           
parent::save();
        }

       
/* Clear store */
       
unset( \IPS\Data\Store::i()->$key );
    }
}