<?php
#Visit our homepage at: http://cmsmadesimple.org

/**
 * This file contails the classes and methods to define the LayoutTemplate functionality
 * @package CMS
 * @license GPL
 */

/**
 * A class to represent a smarty template.
 *
 * @package CMS
 * @license GPL
 * @since 2.0
 * @author Robert Campbell <calguy1000@gmail.com>
 */
class CmsLayoutTemplate
{

    /**
     * @ignore
     */
    private $_data = array('listable'=>true);

    /**
     * @ignore
     */
    private $_addt_editors;

    /**
     * @ignore
     */
    private $_design_assoc;

    /**
     * @ignore
     */
    private static $_obj_cache;

    /**
     * @ignore
     */
    private static $_name_cache;

    /**
     * @ignore
     */
    private static $_lock_cache;

    /**
     * @ignore
     */
    private static $_lock_cache_loaded;

    /**
     * @internal
     * @ignore
     */
    protected static function _mgr()
    {
        return CmsApp::get_instance()->get_template_manager();
    }

    /**
     * @ignore
     */
    public function __clone()
    {
        if( isset($this->_data['id']) ) unset($this->_data['id']);
        $this->_data['type_dflt'] = false;
    }

    /**
     * Get the integer id of this template
     *
     * @return int
     */
    public function get_id()
    {
        if( isset($this->_data['id']) ) return $this->_data['id'];
    }

    /**
     * Get the template name
     *
     * @return string
     */
    public function get_name()
    {
        if( isset($this->_data['name']) ) return $this->_data['name'];
    }

    /**
     * Set the name of the template
     *
     * The template name cannot be empty, can only consist of a few characters in the name
     * and must be unique
     *
     * @throws CmsInvalidDataException
     * @param string $str
     */
    public function set_name($str)
    {
        if( !CmsAdminUtils::is_valid_itemname($str)) throw new CmsInvalidDataException("Invalid characters in name: $str");
        $this->_data['name'] = $str;
    }

    /**
     * Get the content of the template
     *
     * @return string
     */
    public function get_content()
    {
        if( isset($this->_data['content']) ) return $this->_data['content'];
    }

    /**
     * Set the content of the template
     *
     * @param string $str Smarty template text
     */
    public function set_content($str)
    {
        $str = trim($str);
        if( !$str ) $str = '{* Empty Smarty Template *}';
        $this->_data['content'] = $str;
    }

    /**
     * Get the description for the template
     *
     * @return string
     */
    public function get_description()
    {
        if( isset($this->_data['description']) ) return $this->_data['description'];
    }

    /**
     * Set the description for the template
     *
     * @param string $str
     */
    public function set_description($str)
    {
        $str = trim($str);
        $this->_data['description'] = $str;
    }

    /**
     * Get the type id for the template
     *
     * @return int
     */
    public function get_type_id()
    {
        if( isset($this->_data['type_id']) ) return $this->_data['type_id'];
    }

    /**
     * Set the type id for the template
     *
        * @throws \CmsLogicException
     * @param mixed $a Either an instance of CmsLayoutTemplateType object, an integer type id, or a string template type identifier
     * @see CmsLayoutTemplateType
     */
    public function set_type($a)
    {
        $n = null;
        if( is_object($a) && is_a($a,'CmsLayoutTemplateType') ) {
            $n = $a->get_id();
        }
        else if( is_numeric($a) && (int)$a > 0 ) {
            $n = (int)$a;
        }
        else if( is_string($a) && strlen($a) ) {
            $type = CmsLayoutTemplateType::load($a);
            $n = $type->get_id();
        }
        else {
            throw new \CmsLogicException('Invalid data passed to '.__METHOD__);
        }

        $this->_data['type_id'] = (int) $n;
    }

    /**
     * Get the flag indicating that this template is the default template for its type
     *
     * @return bool
     * @see CmsLayoutTemplateType
     */
    public function get_type_dflt()
    {
        if( isset($this->_data['type_dflt']) ) return $this->_data['type_dflt'];
    }

    /**
     * Set the flag that indicates that this template is the default template for its type
     * Only one template can be the default for a template type
     *
     * @param bool $flag
     * @see CmsLayoutTemplateType
     */
    public function set_type_dflt($flag)
    {
        $n = (bool)$flag;
        $this->_data['type_dflt'] = $n;
    }

    /**
     * Get the category id for this template
     * A template is not required to be associated with a category
     *
     * @param int
     */
    public function get_category_id()
    {
        if( isset($this->_data['category_id']) ) return (int)$this->_data['category_id'];
    }

    /**
     * Get the category for this template
     * A template is not required to be associated with a category
     *
     * @param CmsLayoutTemplateCategory
     * @see CmsLayoutTemplateCategory
     */
    public function &get_category()
    {
        $n = $this->get_category_id();
        if( $n > 0 ) return CmsLayoutTemplateCategory::load($n);
    }

    /**
     * Set the category for a template
     *
        * @throws \CmsLogicException
     * @param mixed $a Either a CmsLayoutTemplateCategory object, a category name (string) or category id (int)
     * @see CmsLayoutTemplateCategory
     */
    public function set_category($a)
    {
        if( !$a ) return;
        $n = null;
        if( is_object($a) && is_a($a,'CmsLayoutTemplateCategory') ) {
            $n = $a->get_id();
        }
        else if( is_numeric($a) && (int) $a > 0 ) {
            $n = $a;
        }
        else if( is_string($a) && strlen($a) ) {
            $cat = CmsLayoutTemplateCategory::load($a);
            $n = $cat->get_id();
        }
        else {
            throw new \CmsLogicException('Invalid data passed to '.__METHOD__);
        }

        $this->_data['category_id'] = (int) $n;
    }

    /**
     * Get a list of the design id's that this template is associated with
     *
     * @return array Array of integers
     */
    public function get_designs()
    {
        return $this->_design_assoc;
    }

    /**
     * Set the list of designs that this template is associated with
     *
     * @throws CmsInvalidDataException
     * @param array $x Array of integers.
     */
    public function set_designs($x)
    {
        if( !is_array($x) ) return;

        foreach( $x as $y ) {
            if( !is_numeric($y) || (int) $y < 1 ) throw new CmsInvalidDataException('Invalid data in design list.  Expect array of integers');
        }

        $this->_design_assoc = $x;
    }

    /**
	 * Associate a new design with this template
	 *
     * @throws CmsLogicException
	 * @param mixed $a A CmsLayoutCollection object, an integer design id, or a string design name.
	 * @see CmsLayoutCollection
	 */
    public function add_design($a)
    {
        $n = null;
        if( is_object($a) && is_a($a,'CmsLayoutCollection') ) {
            $n = $a->get_id();
        }
        else if( is_numeric($a) && (int)$a > 0 ) {
            $n = $a;
        }
        else if( (is_string($a) && strlen($a)) || (int)$a > 0 ) {
            $design = CmsLayoutCollection::load($a);
            $n = $design->get_id();
        }
        else {
            throw new \CmsLogicException('Invalid data passed to '.__METHOD__);
        }

        if( !is_array($this->_design_assoc) ) $this->get_designs();
        $this->_design_assoc[] = (int) $n;
    }

    /**
     * Remove a design from the list of associated designs
     *
        * @throws CmsLogicException
     * @param mixed $a A CmsLayoutCollection object, an integer design id, or a string design name.
     * @see CmsLayoutCollection
     */
    public function remove_design($a)
    {
        if( !is_array($this->_design_assoc) || count($this->_design_assoc) == 0 ) return;

        $n = null;
        if( is_object($a) && is_a($a,'CmsLayoutCollection') ) {
            $n = $a->get_id();
        }
        else if( is_numeric($a) && (int)$a > 0 ) {
            $n = $a;
        }
        else if( (is_string($a) && strlen($a)) ) {
            $design = CmsLayoutCollection::load($a);
            $n = $design->get_id();
        }
        else {
            throw new \CmsLogicException('Invalid data passed to '.__METHOD__);
        }

        $designs = $this->get_designs();
        if( in_array($n,$designs) ) {
            $t = array();
            foreach( $designs as $one ) {
                if( $n == $one ) continue;
                $t[] = $one;
            }
            $this->_design_assoc = $t;
        }
    }

    /**
     * Get the integer owner id of this template
     *
     * @return int
     */
    public function get_owner_id()
    {
        if( isset($this->_data['owner_id']) ) return $this->_data['owner_id'];
        return -1;
    }

    /**
     * Set the owner id
     *
        * @throws CmsInvalidDataException
     * @param mixed $a An integer admin user id, a string admin username, or an instance of a User object
     * @see User
     */
    public function set_owner($a)
    {
        $n = null;
        if( is_numeric($a) && $a > 0 ) {
            $n = (int)$a;
        }
        else if( is_string($a) && strlen($a) ) {
            // load the user by name.
            $ops = UserOperations::get_instance();
            $ob = $ops->LoadUserByUsername($a);
            if( is_object($a) && is_a($a,'User') ) $n = $a->id;
        }
        else if( is_object($a) && is_a($a,'User') ) {
            $n = $a->id;
        }

        if( $n < 1 ) throw new CmsInvalidDataException('Owner id must be valid in '.__METHOD__);
        $this->_data['owner_id'] = (int) $n;
    }

    /**
     * Get the date that this template was initially stored in the database
     * The created date can not be externally modified
     *
     * @return int
     */
    public function get_created()
    {
        if( isset($this->_data['created']) ) return $this->_data['created'];
    }

    /**
     * Get the date that this template was last saved to the database
     * The modified date cannot be externally modified
     *
     * @return int
     */
    public function get_modified()
    {
        if( isset($this->_data['modified']) ) return $this->_data['modified'];
    }

    /**
     * Get a list of user id's other than the owner that are allowed edit functionality to this template
     *
     * @return array Array of integer user ids
     */
    public function get_additional_editors()
    {
        if( is_array($this->_addt_editors) && count($this->_addt_editors) ) return $this->_addt_editors;
    }

    /**
     * @ignore
     */
    private static function _resolve_user($a)
    {
        if( is_numeric($a) && $a > 0 ) return $a;
        if( is_string($a) && strlen($a) ) {
            $ops = UserOperations::get_instance();
            $ob = $ops->LoadUserByUsername($a);
            if( is_object($a) && is_a($a,'User') ) return $a->id;
        }
        if( is_object($a) && is_a($a,'User') ) return $a->id;
        throw new \CmsLogicException('Could not resolve '.$a.' to a user id');
    }

    /**
     * Set the admin user accounts (other than the owner) that are allowed to edit this template object
     *
     * @throws CmsInvalidDataException
     * @param mixed $a Accepts an array of strings (usernames) or an array of integers (user ids, and negative group ids)
     */
    public function set_additional_editors($a)
    {
        if( !is_array($a) ) {
            if( is_string($a) || (is_numeric($a) && $a > 0) ) {
                // maybe a single value...
                $res = self::_resolve_user($a);
                $this->_addt_editors = array($res);
            }
        }
        else {
            $tmp = array();
            for( $i = 0; $i < count($a); $i++ ) {
                if( !is_numeric($a[$i]) ) continue;
                $tmp2 = (int)$a[$i];  // intentional cast to int, may have negative values.
                if( $tmp2 != 0 ) {
                    $tmp[] = $tmp2;
                }
                else if( is_string($a[$i]) ) {
                    $tmp[] = self::_resolve_user($a[$i]);
                }
            }
            $this->_addt_editors = $tmp;
        }
    }

    /**
     * Test wether the user specified has edit ability for this template object
     *
     * @param mixed $a Either a username (string) or an integer user id.
     * @return bool
     */
    public function can_edit($a)
    {
        $res = self::_resolve_user($a);
        if( $res == $this->get_owner_id() ) return TRUE;
        if( is_array($this->_addt_editors) && count($this->_addt_editors) && !in_array($res,$this->_addt_editors) ) return TRUE;
        return FALSE;
    }

    /**
     * Get wether this template is listable in public template lists.
     *
     * @return bool
     * @since 2.1
     */
    public function get_listable()
    {
        return (bool) $this->_data['listable'];
    }

    /**
     * Get wether this template is listable in public template lists.
     *
     * @return bool
     * @since 2.1
     */
    public function is_listable()
    {
        return $this->get_listable();
    }

    /**
     * Get wether this template is listable in public template lists.
     *
     * @param bool $flag The value for the listable attribute.
     * @return bool
     * @since 2.1
     */
    public function set_listable($flag)
    {
        $this->_data['listable'] = cms_to_bool($flag);
    }

    /**
     * Process the template through smarty
     *
     * @return string
     */
    public function process()
    {
        $smarty = \Smarty_CMS::get_instance();
        return $smarty->fetch('cms_template:id='.$this->get_id());
    }

    /**
     * Validate that the data is complete enough to save to the database
     *
     * @throws CmsInvalidDataException
     */
    public function validate()
    {
        if( !$this->get_name() ) throw new CmsInvalidDataException('Each template must have a name');
        if( endswith($this->get_name(),'.tpl') ) throw new CmsInvalidDataException('Invalid name for a database template');
        if( !CmsAdminUtils::is_valid_itemname($this->get_name()) ) {
            throw new CmsInvalidDataException('There are invalid characters in the template name');
        }

        if( !$this->get_content() ) throw new CmsInvalidDataException('Each template must have some content');
        if( $this->get_type_id() <= 0 ) throw new CmsInvalidDataException('Each template must be associated with a type');
    }

    /**
     * Save this template object to the database
        *
        * @deprecated
        * @see LayoutTemplateManager::save_template()
     */
    public function save()
    {
	    $res = self::_mgr()->save_template($this);
	    if( ($val = $res->get_id()) && !$this->get_id() ) $this->_data['id'] = $val;
	    return $res;
    }

    /**
     * Delete this template object from the database
        *
        * @deprecated
        * @see CMSMS\LayoutTemplateManager::delete_template()
     */
    public function delete()
    {
        if( !$this->get_id() ) return;
        return self::_mgr()->delete_template($this);
    }

    /**
     * @ignore
     */
    private static function get_locks()
    {
        if( !self::$_lock_cache_loaded ) {
            $tmp = CmsLockOperations::get_locks('template');
            if( is_array($tmp) && count($tmp) ) {
                self::$_lock_cache = array();
                foreach( $tmp as $one ) {
                    self::$_lock_cache[$one['oid']] = $one;
                }
            }
            self::$_lock_cache_loaded = TRUE;
        }
        return self::$_lock_cache;
    }

    /**
     * Get any applicable lock for this template object
     *
     * @returns CmsLock
     * @see CmsLock
     */
    public function get_lock()
    {
        $locks = self::get_locks();
        if( isset($locks[$this->get_id()]) ) return $locks[$this->get_id()];
    }

    /**
     * Test if this object currently has a lock
     *
     * @return bool
     */
    public function locked()
    {
        $lock = $this->get_lock();
        if( is_object($lock) ) return TRUE;
        return FALSE;
    }

    /**
     * Tests if any lock associated with this object is expired
     *
     * @return booln
     */
    public function lock_expired()
    {
        $lock = $this->get_lock();
        if( !is_object($lock) ) return FALSE;
        return $lock->expired();
    }

    /**
     * @internal
     * @ignore
     */
    public function _get_array()
    {
        return $this->_data;
    }

    /**
        * @internal
     * @ignore
     */
    public static function _load_from_data($row,$design_list = null,$user_list = null)
    {
        $ob = new CmsLayoutTemplate();
        $ob->_data = $row;
        $fn = $ob->get_content_filename();
        if( is_file($fn) && is_readable($fn) ) {
            $ob->_data['content'] = file_get_contents($fn);
            $ob->_data['modified'] = filemtime($fn);
        }
        if( is_array($design_list) ) $ob->_design_assoc = $design_list;
        if( is_array($user_list) ) $ob->_addt_editors = $user_list;

        self::$_obj_cache[$ob->get_id()] = $ob;
        self::$_name_cache[$ob->get_name()] = $ob->get_id();
        return $ob;
    }

    /**
     * Load a bulk list of templates
     *
     * @deprecated
     * @see CMSMS\LayoutTemplateManager::load_bulk_templates()
     * @param int[] $list Array of integer template ids
     * @param bool $deep Optionally load attached data.
     * @return array $list Array of CmsLayoutTemplate objects
     */
    public static function load_bulk(array $list)
    {
        return self::_mgr()->load_bulk_templates($list);
    }

    /**
     * Load a specific template
     *
        * @deprecated
        * @see CMSMS\LayoutTemplateManager::load_template()
        * @throws CmsDataNotFoundException
     * @param mixed $a Either an integer template id, or a template name (string)
     * @return CmsLayoutTemplate
     */
    public static function load($a)
    {
        return self::_mgr()->load_template($a);
    }

    /**
     * Get a list of the templates owned by a specific user
     *
        * @deprecated
        * @see CMSMS\LayoutTemplateManager::get_owned_templates()
        * @throws CmsInvalidDataException
     * @param mixed $a An integer user id, or a string user name
     * @return array Array of integer template ids
     */
    public static function get_owned_templates($a)
    {
        return self::_mgr()->get_owned_templates($a);
    }

    /**
     * Perform an advanced query on templates
     *
        * @deprecated
     * @see CmsLayoutTemplateQuery
     * @param array $params
     */
    public static function template_query($params)
    {
        $query = new CmsLayoutTemplateQuery($params);
        $out = self::_mgr()->load_bulk_templates($query->GetMatchedTemplateIds());

        if( isset($params['as_list']) && !empty($out) ) {
            $tmp2 = array();
            foreach( $out as $one ) {
                $tmp2[$one->get_id()] = $one->get_name();
            }
            return $tmp2;
        }
        return $out;
    }

    /**
     * Get a list of the templates that a specific user can edit
     *
        * @deprecated
        * @see CMSMS\LayoutTemplateManager::get_owned_templates()
        * @throws CmsInvalidDataException
     * @param mixed $a An integer userid or a string username
     */
    public static function get_editable_templates($a)
    {
        return self::_mgr()->get_editable_templates($a);
    }

    /**
     * Test if the user specified can edit the specified template
     * This is a convenience method that loads the template, and then tests if the specified user has edit ability to it.
     *
     * @param mixed $tpl An integer template id, or a string template name
     * @param mixed $userid An integer user id, or a string user name.  If no userid is specified the currently logged in userid is used
     * @return bool
     * @see CmsLayoutTemplate::load()
     */
    public static function user_can_edit($tpl,$userid = null)
    {
        if( is_null($userid) ) $userid = get_userid();

        // get the template
        // scan owner/additional uers
        $obj = self::load($tpl);
        if( $obj->get_owner_id() == $userid ) return TRUE;

        // get the user groups
        $addt_users = $obj->get_additional_editors();
        if( is_array($addt_users) && count($addt_users) ) {
            if( in_array($userid,$addt_users) ) return TRUE;

            $grouplist = UserOperations::get_instance()->GetMemberGroups();
            if( is_array($grouplist) && count($grouplist) ) {
                foreach( $addt_users as $one ) {
                    if( $one < 0 && in_array($one*-1,$grouplist) ) return TRUE;
                }
            }
        }

        return FALSE;
    }

    /**
     * Create a new template of the specific type
     *
     * @throws CmsInvalidDataException
     * @param mixed $t A CmsLayoutTemplateType object, An integer template type id, or a string template type identifier
     * @return CmsLayoutTemplate
     */
    public static function &create_by_type($t)
    {
        $t2 = null;
        if( is_int($t) || is_string($t) ) {
            $t2 = CmsLayoutTemplateType::load($t);
        }
        else if( is_object($t) && is_a($t,'CmsLayoutTemplateType') ) {
            $t2 = $t;
        }

        if( !$t2 ) throw new CmsInvalidDataException('Invalid data passed to CmsLayoutTemplate::create_by_type()');

        return $t2->create_new_template();
    }

    /**
     * Load the default template of a specified type
     *
     * @throws CmsInvalidDataException
     * @throws CmsDataNotFoundException
     * @param mixed $t A CmsLayoutTemplateType object, An integer template type id, or a string template type identifier
     * @return CmsLayoutTemplate
     */
    public static function load_dflt_by_type($t)
    {
        return self::_mgr()->load_default_template_by_type($t);
    }

    /**
     * Load all templates of a specific type
     *
     * @throws CmsDataNotFoundException
     * @param CmsLayoutTemplateType $type
     * @return array Array of CmsLayoutTemplate objects
     */
    public static function load_all_by_type(CmsLayoutTemplateType $type)
    {
        return self::_mgr()->load_templates_by_type($type);
    }

    /**
     * Process a template through smarty given the template name
     * @param string $name
     * @return string
     */
    public static function process_by_name($name)
    {
        $smarty = \Smarty_CMS::get_instance();
        return $smarty->fetch('cms_template:name='.$name);
    }

    /**
     * Process the default template of a specified type
     *
     * @param mixed $t A CmsLayoutTemplateType object, An integer template type id, or a string template type identifier
     * @return string
     */
    public static function process_dflt($t)
    {
        $tpl = self::load_dflt_by_type($t);
        return $smarty->fetch('cms_template:id='.$tpl->get_id());
    }

    /**
     * Generate a unique name for a template
     *
     * @throws CmsInvalidDataException
        * @throws CmsLogicException
     * @param string $prototype A prototype template name
     * @param string $prefix An optional name prefix.
     */
    public static function generate_unique_name($prototype,$prefix = null)
    {
        return self::_mgr()->generate_unique_template_name($prototype,$prefix);
    }

    /**
     * Return the template type object for this template.
     *
     * @return CmsLayoutTemplateType
     * @since 2.2
     */
    public function &get_type()
    {
        $obj = null;
        $tid = $this->get_type_id();
        if( $tid > 0 ) $obj = \CmsLayoutTemplateType::load($this->get_type_id());
        return $obj;
    }

    /**
     * Get the usage string (if any) for this template.
     *
     * @return string A sample usage string for this template.
     * @since 2.2
     */
    public function get_usage_string()
    {
        try {
            $type = $this->get_type();
            return $type->get_usage_string($this->get_name());
        }
        catch( CmsDataNotFoundException $e ) {
            // nothing here.
        }
    }

    /**
     * Get the filename that will be used to read template contents from file.
     *
     * @since 2.2
     * @return string
     * @deprecated
     */
    public function get_content_filename()
    {
        return self::_mgr()->get_template_filename($this);
    }

    /**
     * Does this template have an associated file.
     *
     * @since 2.2
     * @return bool
     * @deprecated
     */
    public function has_content_file()
    {
        return self::_mgr()->template_has_file($this);
    }
} // end of class

#
# EOF
#

