Seditio Source
Root |
./othercms/b2evolution_7.2.3/inc/chapters/model/_chapter.class.php
<?php
/**
 * This file implements the Chapter class.
 *
 * This file is part of the evoCore framework - {@link http://evocore.net/}
 * See also {@link https://github.com/b2evolution/b2evolution}.
 *
 * @license GNU GPL v2 - {@link http://b2evolution.net/about/gnu-gpl-license}
 *
 * @copyright (c)2003-2020 by Francois Planque - {@link http://fplanque.com/}
 * Parts of this file are copyright (c)2005-2006 by PROGIDISTRI - {@link http://progidistri.com/}.
 *
 * @package evocore
 */
if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' );


/**
 * Chapter Class
 *
 * @package evocore
 */
class Chapter extends DataObject
{
   
/**
     * Name of Chapter
     *
     * @var string
     * @access protected
     */
   
var $name;

    var
$parent_ID;
   
/**
     * To display parent name in form
     */
   
var $parent_name;

   
/**
     * Category children list
     */
   
var $children = array();

    var
$children_sorted = false;

   
/**
     * @var integer
     */
   
var $blog_ID;
   
/**
     * The Blog of the Item (lazy filled, use {@link get_Blog()} to access it.
     * @access protected
     * @var Blog
     */
   
var $Blog;

    var
$urlname;
    var
$description;
    var
$order;
    var
$meta;
    var
$lock;
    var
$ityp_ID = NULL;

   
/**
     * Default Item Type
     * @var object|NULL|false
     */
   
var $ItemType = NULL;

   
/**
     * Lazy filled
     * @var Chapter
     */
   
var $parent_Chapter;

   
/**
     * Date when items or comments were added/edited/deleted for this Chapter last time (timestamp)
     * @see Chapter::update_last_touched_date()
     * @var integer
     */
   
var $last_touched_ts;

   
/**
     * The sub categories sort order.
     *
     * Possible values:
     *   'parent' - same as in case of parent,
     *   'alpha' - sort alphabetically,
     *   'manual' - sort manually
     *
     * @var string
     */
   
var $subcat_ordering;

   
/**
     * Category image
      */
   
var $image_file_ID;

   
/**
     * Social media image
     */
   
var $social_media_image_file_ID;

   
/**
     * Constructor
     *
     * @param table Database row
      * @param integer|NULL subset to use for new object
     */
   
function __construct( $db_row = NULL, $subset_ID = NULL )
    {
       
// Call parent constructor:
       
parent::__construct( 'T_categories', 'cat_', 'cat_ID' );

        if(
is_null( $db_row ) )
        {    
// We are creating an object here:
           
$this->set( 'blog_ID', $subset_ID );
        }
        else
        {    
// We are loading an object:
           
$this->ID = $db_row->cat_ID;
           
$this->name = $db_row->cat_name;
           
$this->parent_ID = $db_row->cat_parent_ID;
           
$this->blog_ID = $db_row->cat_blog_ID;
           
$this->image_file_ID = $db_row->cat_image_file_ID;
           
$this->social_media_image_file_ID = $db_row->cat_social_media_image_file_ID;
           
$this->urlname = $db_row->cat_urlname;
           
$this->description = $db_row->cat_description;
           
$this->order = $db_row->cat_order;
           
$this->subcat_ordering = $db_row->cat_subcat_ordering;
           
$this->meta = $db_row->cat_meta;
           
$this->lock = $db_row->cat_lock;
           
$this->last_touched_ts = $db_row->cat_last_touched_ts;        // When Chapter received last visible change (edit, item, comment, etc.)
           
$this->ityp_ID = isset( $db_row->cat_ityp_ID ) ? $db_row->cat_ityp_ID : NULL;
        }
    }


   
/**
     * Get this class db table config params
     *
     * @return array
     */
   
static function get_class_db_config()
    {
        static
$chapter_db_config;

        if( !isset(
$chapter_db_config ) )
        {
           
$chapter_db_config = array_merge( parent::get_class_db_config(),
                array(
                   
'dbtablename'        => 'T_categories',
                   
'dbprefix'           => 'cat_',
                   
'dbIDname'           => 'cat_ID',
                )
            );
        }

        return
$chapter_db_config;
    }


   
/**
     * Get delete restriction settings
     *
     * @return array
     */
   
static function get_delete_restrictions()
    {
        return array(
                array(
'table'=>'T_categories', 'fk'=>'cat_parent_ID', 'msg'=>T_('%d sub categories'),
                   
'class'=>'Chapter', 'class_path'=>'chapters/model/_chapter.class.php' ),
                array(
'table'=>'T_items__item', 'fk'=>'post_main_cat_ID', 'msg'=>T_('%d posts within category through main cat'),
                   
'class'=>'Item', 'class_path'=>'items/model/_item.class.php' ),
                array(
'table'=>'T_postcats', 'fk'=>'postcat_cat_ID', 'msg'=>T_('%d posts within category through extra cat') ),
            );
    }


   
/**
     * Compare two Chapters based on the parent/blog sort category setting
     *
     * @param Chapter A
     * @param Chapter B
     * @return number -1 if A < B, 1 if A > B, 0 if A == B
     */
   
static function compare_chapters( $a_Chapter, $b_Chapter )
    {
        if(
$a_Chapter == NULL || $b_Chapter == NULL ) {
           
debug_die('Invalid category objects received to compare.');
        }

        if(
$a_Chapter->ID == $b_Chapter->ID )
        {
// The two chapters are the same
           
return 0;
        }

        if(
$a_Chapter->blog_ID != $b_Chapter->blog_ID )
        {
// Sort based on the ordering between different blogs
           
$a_Chapter->load_Blog();
           
$b_Chapter->load_Blog();
            return
Blog::compare_blogs($a_Chapter->Blog, $b_Chapter->Blog);
        }

       
$ChapterCache= & get_ChapterCache();
        if(
$a_Chapter->parent_ID != $b_Chapter->parent_ID )
        {
// Two chapters from the same blog, but different parrents
            // Compare those parents of these chapters which have a common parent Chapter or they are both root Chapters.
           
$path_to_root_a = array_reverse( $ChapterCache->get_chapter_path( $a_Chapter->blog_ID, $a_Chapter->ID ) );
           
$path_to_root_b = array_reverse( $ChapterCache->get_chapter_path( $b_Chapter->blog_ID, $b_Chapter->ID ) );
           
$index = 0;
            while( isset(
$path_to_root_a[$index] ) && isset( $path_to_root_b[$index] ) )
            {
                if(
$path_to_root_a[$index] != $path_to_root_b[$index] )
                {
// The first different parent on the same level was found, compare parent objects
                   
$parent_a_Chapter = $ChapterCache->get_by_ID( $path_to_root_a[$index] );
                   
$parent_b_Chapter = $ChapterCache->get_by_ID( $path_to_root_b[$index] );
                    return
self::compare_chapters( $parent_a_Chapter, $parent_b_Chapter );
                }
               
$index++;
            }

           
// One of the chapters is a parent of the other, the parent is considered greater than the other
           
return isset( $path_to_root_a[$index] ) ? 1 : -1;
        }

        if( empty(
$a_Chapter->parent_ID ) )
        {
           
$a_Chapter->load_Blog();
           
$cat_order = $a_Chapter->Blog->get_setting('category_ordering');
        }
        else
        {
           
$parent_Chapter = $ChapterCache->get_by_ID( $a_Chapter->parent_ID );
           
$cat_order = $parent_Chapter->get_subcat_ordering();
        }

        switch(
$cat_order )
        {
            case
'alpha':
               
$result = strcmp( $a_Chapter->name, $b_Chapter->name );
                break;

            case
'manual':
                if(
$a_Chapter->order === NULL )
                {
// NULL values are greater than any number
                   
$result = ( $b_Chapter->order !== NULL ) ? 1 : 0;
                    break;
                }

                if(
$b_Chapter->order === NULL )
                {
// NULL values are greater than any number, so a is lower than b
                   
$result = -1;
                    break;
                }

               
$result = ( $a_Chapter->order > $b_Chapter->order ) ? 1 : ( ( $a_Chapter->order < $b_Chapter->order ) ? -1 : 0 );
                break;

            default:
               
debug_die("Invalid category ordering value!");
        }

        if(
$result == 0 )
        {
// In case if the order fields are equal order by ID
           
$result = $a_Chapter->ID > $b_Chapter->ID ? 1 : -1;
        }

        return
$result;
    }


   
/**
     * Sort chapter childer
     */
   
function sort_children()
    {
        if(
$this->children_sorted )
        {
// Category children list is already sorted
           
return;
        }

       
// Sort children list
       
uasort( $this->children, array( 'Chapter','compare_chapters' ) );
    }


   
/**
     * Get children/sub-chapters of this category
     *
     * @param boolean set to true to sort children, leave false otherwise
     * @return array of Chapter - children of this Chapter
     */
   
function get_children( $sorted = false )
    {
        if(
$sorted )
        {
// sort childrens if required
           
$this->sort_children();
        }

        return
$this->children;
    }


   
/**
     * Load data from Request form fields.
     *
     * @return boolean true if loaded data seems valid.
     */
   
function load_from_request()
    {
       
param_string_not_empty( 'cat_name', T_('Please enter a name.') );
       
$this->set_from_Request( 'name' );

        if(
param( 'cat_parent_ID', 'integer', -1 ) !== -1 )
        {    
// Set parent ID:
           
$this->set_from_Request( 'parent_ID' );
        }

       
// Check image file
       
param( 'cat_image_file_ID', 'integer' );
       
$this->set_from_Request( 'image_file_ID' );

       
// Check social media boilerplate image
       
param( 'cat_social_media_image_file_ID', 'integer' );
       
$this->set_from_Request( 'social_media_image_file_ID' );

       
// Check url name
       
param( 'cat_urlname', 'string' );
       
// Replace special chars/umlauts:
       
load_funcs( 'locales/_charset.funcs.php' );
       
set_param( 'cat_urlname', replace_special_chars( get_param( 'cat_urlname' ) ) );
       
param_check_regexp( 'cat_urlname', '#^[^a-z0-9]*[0-9]*[^a-z0-9]*$#i', T_('All slugs must contain at least 1 non-numeric character.'), NULL, false, false );
       
$this->set_from_Request( 'urlname' );

       
// Check description
       
param( 'cat_description', 'string' );
       
$this->set_from_Request( 'description' );

       
$cat_Blog = & $this->get_Blog();
        if(
$cat_Blog && $cat_Blog->get_setting('category_ordering') == 'manual' )
        {
// Manual ordering
           
param( 'cat_order', 'integer', NULL );
           
$this->set_from_Request( 'order', 'cat_order', true );
        }

       
// Sort sub-categories
       
param( 'cat_subcat_ordering', 'string' );
       
$this->set_from_Request( 'subcat_ordering' );

       
// Meta category
       
$cat_meta = param( 'cat_meta', 'integer', 0 );
        if(
$this->has_posts() && $cat_meta )
        {    
// Display error message if we want make the meta category from category with posts
           
global $Messages;
           
$Messages->add( sprintf( T_('The category &laquo;%s&raquo; cannot be set as meta category. You must remove the posts it contains first.'), $this->dget('name') ) );
        }
        else
        {    
// Save the category as 'Meta' only if it has no posts
           
$this->set_from_Request( 'meta' );
        }

       
// Locked category
       
param( 'cat_lock', 'integer', 0 );
       
$this->set_from_Request( 'lock' );

       
// Default Item Type:
       
param( 'cat_ityp_ID', 'integer', NULL );
       
$this->set_from_Request( 'ityp_ID' );

        return !
param_errors_detected();
    }


   
/**
     *
     */
   
function & get_parent_Chapter()
    {
        if( ! isset(
$this->parent_Chapter ) )
        {    
// Not resoleved yet!
           
if( empty( $this->parent_ID ) )
            {
               
$this->parent_Chapter = NULL;
            }
            else
            {
               
$ChapterCache = & get_ChapterCache();
               
$this->parent_Chapter = & $ChapterCache->get_by_ID( $this->parent_ID, false );
            }
        }

        return
$this->parent_Chapter;
    }


   
/**
     * Get URL path (made of URL names) back to the root
     */
   
function get_url_path()
    {
       
$r = $this->urlname.'/';

       
$parent_Chapter = & $this->get_parent_Chapter();
        if( !
is_null( $parent_Chapter ) )
        {    
// Recurse:
           
$r = $parent_Chapter->get_url_path().$r;
        }

        return
$r;
    }


   
/**
     * Generate the URL to access the category.
     *
     * @param string|NULL 'param_num', 'subchap', 'chapters'
     * @param string|NULL url to use
     * @param integer category page to link to, default:1
     * @param integer|NULL number of posts per page (used for param_num only)
     * @param string glue between url params
     */
   
function get_permanent_url( $link_type = NULL, $blogurl = NULL, $paged = 1, $chapter_posts_per_page = NULL, $glue = '&amp;' )
    {
        global
$DB, $cacheweekly, $Settings;

        if( empty(
$link_type ) )
        {    
// Use default from settings:
           
$this->get_Blog();
           
$link_type = $this->Blog->get_setting( 'chapter_links' );
        }

        if( empty(
$blogurl ) )
        {
           
$this->get_Blog();
           
$blogurl = $this->Blog->gen_blogurl();
        }

        switch(
$link_type )
        {
            case
'param_num':
               
$r = url_add_param( $blogurl, 'cat='.$this->ID, $glue );
                if( empty(
$chapter_posts_per_page) )
                {    
// Use default from Blog
                   
$this->get_Blog();
                   
$chapter_posts_per_page = $this->Blog->get_setting( 'chapter_posts_per_page' );
                }
                if( !empty(
$chapter_posts_per_page) && $chapter_posts_per_page != $this->Blog->get_setting( 'posts_per_page' ) )
                {    
// We want a specific post per page count:
                   
$r = url_add_param( $r, 'posts='.$chapter_posts_per_page, $glue );
                }
                break;

            case
'subchap':
               
$this->get_Blog();
               
$category_prefix = $this->Blog->get_setting('category_prefix');
                if( !empty(
$category_prefix ) )
                {
                   
$r = url_add_tail( $blogurl, '/'.$category_prefix.'/'.$this->urlname.'/' );
                }
                else
                {
                   
$r = url_add_tail( $blogurl, '/'.$this->urlname.'/' );
                }
                break;

            case
'chapters':
            default:
               
$this->get_Blog();
               
$category_prefix = $this->Blog->get_setting('category_prefix');
                if( !empty(
$category_prefix ) )
                {
                   
$r = url_add_tail( $blogurl, '/'.$category_prefix.'/'.$this->get_url_path() );
                }
                else
                {
                   
$r = url_add_tail( $blogurl, '/'.$this->get_url_path() );
                }
                break;
        }

        if(
$paged > 1 )
        {
           
$r = url_add_param( $r, 'paged='.$paged, $glue );
        }

        return
$r;
    }

   
/**
     * Get the Blog object for the Chapter.
     *
     * @return Blog
     */
   
function & get_Blog()
    {
        if(
is_null($this->Blog) )
        {
           
$this->load_Blog();
        }

        return
$this->Blog;
    }


   
/**
     * Load the Blog object for the Chapter, without returning it.
     */
   
function load_Blog()
    {
        if(
is_null($this->Blog) )
        {
           
$BlogCache = & get_BlogCache();
           
$this->Blog = & $BlogCache->get_by_ID( $this->blog_ID );
        }
    }


   
/**
     * Get sub-category ordering
     *
     * @param boolean actual ordering - set to false to get raw setting value with default fallback to 'parent'
     * @return string
     *   - the setting value if actual_ordering param is false
     *   - the actual ordering value computed recursively from parents/blog if the actual_ordering param is true
     */
   
function get_subcat_ordering( $actual_ordering = true )
    {
       
$setting_value = empty( $this->subcat_ordering ) ? 'parent' : $this->subcat_ordering;
        if( !
$actual_ordering )
        {
            return
$setting_value;
        }

        switch(
$setting_value ) {
            case
'alpha':
            case
'manual':
                return
$this->subcat_ordering;

            case
'parent':
                return
$this->get_parent_subcat_ordering();

            default:
               
debug_die('Unhandled sub-category ordering value was detected');
        }
    }


   
/**
     * Get blog's category ordering value in case of root categories, parent Chapter subcat ordering otherwise
     *
     * @return string parent subcat ordering
     */
   
function get_parent_subcat_ordering()
    {
        if( empty(
$this->parent_ID ) )
        {
// Return the default blog setting
           
$this->load_Blog();
            return
$this->Blog->get_setting( 'category_ordering' );
        }

       
$this->get_parent_Chapter();
        return
$this->parent_Chapter->get_subcat_ordering();
    }


   
/**
     * Insert object into DB based on previously recorded changes.
     *
     * @return boolean true on success
     */
   
function dbinsert()
    {
        global
$DB, $localtimenow;

       
load_funcs( 'items/model/_item.funcs.php' );

        if(
$this->ID != 0 ) die( 'Existing object cannot be inserted!' );

       
// Start transaction because of urltitle validation
       
$DB->begin( 'SERIALIZABLE' );

       
// validate url title / slug
       
$this->set( 'urlname', urltitle_validate( $this->urlname, $this->name, $this->ID, false, $this->dbprefix.'urlname', $this->dbIDname, $this->dbtablename) );

       
$this->set_param( 'last_touched_ts', 'date', date( 'Y-m-d H:i:s', $localtimenow ) );

        if(
parent::dbinsert() )
        {
// The chapter was inserted successful
           
$DB->commit();
            return
true;
        }

       
// Could not insert the chapter object
       
$DB->rollback();
        return
false;
    }

   
/**
     * Update the DB based on previously recorded changes
     *
     * @return boolean true on success
     */
   
function dbupdate()
    {
        global
$DB;

       
// Start transaction because of urltitle validation
       
$DB->begin( 'SERIALIZABLE' );

       
// validate url title / slug
       
if( empty($this->urlname) || isset($this->dbchanges['cat_urlname']) )
        {
// Url title has changed or is empty
           
$this->set( 'urlname', urltitle_validate( $this->urlname, $this->name, $this->ID, false, $this->dbprefix.'urlname', $this->dbIDname, $this->dbtablename) );
        }

        if(
count( $this->dbchanges ) > 0 && !isset( $this->dbchanges['last_touched_ts'] ) )
        {
// Update last_touched_ts field only if it wasn't updated yet and the datemodified will be updated for sure.
           
global $localtimenow;
           
$this->set_param( 'last_touched_ts', 'date', date( 'Y-m-d H:i:s', $localtimenow ) );
        }

        if(
parent::dbupdate() === false )
        {
// The update was unsuccessful
           
$DB->rollback();
            return
false;
        }

       
// The chapter was updated successful
       
$DB->commit();

       
// BLOCK CACHE INVALIDATION:
       
$chapter_Blog = $this->get_Blog();
       
BlockCache::invalidate_key( 'cont_coll_ID', $chapter_Blog->ID ); // Content has changed
       
BlockCache::invalidate_key( 'cat_ID', $this->ID ); // Chaper has changed

       
return true;
    }


   
/**
     * Get a member param by its name
     *
     * @param mixed Name of parameter
     * @return mixed Value of parameter
     */
   
function get( $parname )
    {
        switch(
$parname )
        {
            case
'subcat_ordering':
                return
$this->get_subcat_ordering( false );
        }

        return
parent::get( $parname );
    }


   
/**
     * Get name of Chapter
     *
     * @param string Output format, see {@link format_to_output()}
     * @return string
     */
   
function get_name( $format = 'htmlbody' )
    {
        return
$this->dget( 'name', $format );
    }


   
/**
     * Check if this category has at least one post
     *
     * @return boolean
     */
   
function has_posts()
    {
        global
$DB;

        if(
$this->ID == 0 )
        {    
// New category has no posts
           
return false;
        }

        if( !isset(
$this->count_posts ) )
        {
           
$SQL = new SQL( 'Check if category has posts' );
           
$SQL->SELECT( 'COUNT( postcat_post_ID )' );
           
$SQL->FROM( 'T_postcats' );
           
$SQL->WHERE( 'postcat_cat_ID = '.$DB->quote( $this->ID ) );
           
$count_posts = $DB->get_var( $SQL );
           
$this->count_posts = $count_posts;
        }

        return (
$this->count_posts > 0 );
    }


   
/**
     * Update field last_touched_ts
     */
   
function update_last_touched_date()
    {
        global
$localtimenow;

       
$this->set_param( 'last_touched_ts', 'date', date( 'Y-m-d H:i:s', $localtimenow ) );
       
$this->dbupdate();
    }


   
/**
     * Returns a permalink link to the Category
     *
     * Note: If you only want the permalink URL, use {@link Item::get_permanent_url()}
     *
     * @param array
     */
   
function get_permanent_link( $params = array() )
    {
       
// Make sure we are not missing any param:
       
$params = array_merge( array(
               
'text'        => '#name',    // possible special values: ...
               
'title'       => '',
               
'class'       => '',
               
'nofollow'    => false,
            ),
$params );


       
// Get item permanent URL:
       
$url = $this->get_permanent_url();

       
$text = $params['text'];
        switch(
$text )
        {
            case
'#linkicon':
               
$text = get_icon( 'permalink' );
                break;

            case
'#text':
               
$text = T_('Permalink');
                break;

            case
'#linkicon+text':
               
$text = get_icon( 'permalink' ).T_('Permalink');
                break;

            case
'#expandicon':
               
$text = get_icon( 'file_message' );
                break;

            case
'#name':
               
$text = format_to_output( $this->get( 'name' ) );
                break;

            case
'#expandicon+name':
               
$text = get_icon( 'expand' ).format_to_output( $this->get( 'name' ) );
                break;

            case
'#open+arrow':
               
$text = T_('Open').' &raquo;';
                break;

            case
'#view+arrow':
               
$text = T_('View').' &raquo;';
                break;
        }

       
$title = $params['title'];
        if(
$title == '#' )
        {    
// Use default title for link:
           
$title = T_('Permanent link to category');
        }

       
$class = $params['class'];

       
// Build a permanent link to Item:
       
$r = '<a href="'.$url.'"'
               
.( empty( $title ) ? '' : ' title="'.format_to_output( $title, 'htmlattr' ).'"' )
                .( empty(
$class ) ? '' : ' class="'.format_to_output( $class, 'htmlattr' ).'"' )
                .(
$params['nofollow'] ? ' rel="nofollow"' : '' )
            .
'>'
               
.str_replace( '$name$', format_to_output( $this->get( 'name' ) ), $text )
            .
'</a>';

        return
$r;
    }


   
/**
     * Get last touched date (datetime) of Chapter
     *
     * @param string date/time format: leave empty to use locale default date format
     * @param boolean true if you want GMT
     */
   
function get_last_touched_date( $format = '', $useGM = false )
    {
        if( empty(
$format ) )
        {
            return
mysql2date( locale_datefmt(), $this->last_touched_ts, $useGM );
        }

        return
mysql2date( $format, $this->last_touched_ts, $useGM );
    }


   
/**
     * Get URL to edit a chapter if user has edit rights.
     *
     * @param array Params:
     *  - 'redirect_page': redirect to page: 'front', 'manual', 'list'
     *  - 'glue' : Glue string between url params
     * @return string|FALSE URL
     */
   
function get_edit_url( $params = array() )
    {
       
$params = array_merge( array(
               
'redirect_page' => '',
               
'glue'          => '&amp;',
            ),
$params );

        if( !
is_logged_in( false ) )
        {
// User is not logged in
           
return false;
        }

        if( !
$this->ID )
        {
// New chapter
           
return false;
        }

        if( !
check_user_perm( 'admin', 'restricted' ) ||
            !
check_user_perm( 'blog_cats', '', false, $this->blog_ID ) )
        {
// User has no right to edit this chapter
           
return false;
        }

        global
$admin_url;
       
$url = $admin_url.'?ctrl=chapters'.$params['glue']
            .
'action=edit'.$params['glue']
            .
'cat_ID='.$this->ID.$params['glue']
            .
'blog='.$this->blog_ID;
        if( !empty(
$params['redirect_page'] ) )
        {
           
$url .= $params['glue'].'redirect_page='.$params['redirect_page'];
        }

        return
$url;
    }


   
/**
     * Provide link to edit a chapter if user has edit rights
     *
     * @param array Params:
     *  - 'before': to display before link
     *  - 'after':    to display after link
     *  - 'text': link text
     *  - 'title': link title
     *  - 'class': CSS class name
     *  - 'redirect_page': redirect to page: 'front', 'manual', 'list'
     *  - 'glue' : Glue string between url params
     * @return string|FALSE Link tag
     */
   
function get_edit_link( $params = array() )
    {
       
$edit_url = $this->get_edit_url( $params );
        if( !
$edit_url )
        {
            return
false;
        }

       
// Make sure we are not missing any param:
       
$params = array_merge( array(
               
'before'        => '',
               
'after'         => '',
               
'text'          => '#',
               
'title'         => '#',
               
'class'         => '',
               
'redirect_page' => '',
               
'glue'          => '&amp;',
            ),
$params );


        if(
$params['text'] == '#' ) $params['text'] = get_icon( 'edit' ).' '.T_('Edit...');
        if(
$params['title'] == '#' ) $params['title'] = T_('Edit this chapter...');

       
$r = $params['before'];
       
$r .= '<a href="'.$edit_url;
       
$r .= '" title="'.$params['title'].'"';
        if( !empty(
$params['class'] ) ) $r .= ' class="'.$params['class'].'"';
       
$r .=  '>'.$params['text'].'</a>';
       
$r .= $params['after'];

        return
$r;
    }


   
/**
     * Get name of this chapter as path of all parent chapters
     *
     * @param array Params
     * @return string
     */
   
function get_path_name( $params = array() )
    {
       
$params = array_merge( array(
               
'separator' => ' / ',
            ) );

       
$path_name = $this->get_name();

       
$parent_Chapter = $this->get_parent_Chapter();

        while(
$parent_Chapter )
        {    
// Append all parent chapters name:
           
$path_name = $parent_Chapter->get_name().$params['separator'].$path_name;

           
// Get next parent Chapter:
           
$parent_Chapter = $parent_Chapter->get_parent_Chapter();
        }

        return
$path_name;
    }


   
/**
     * Set param value
     *
     * By default, all values will be considered strings
     *
     * @param string parameter name
     * @param mixed parameter value
     * @param boolean true to set to NULL if empty value
     * @return boolean true, if a value has been set; false if it has not changed
     */
   
function set( $parname, $parvalue, $make_null = false )
    {
        switch(
$parname )
        {
             case
'parent_ID':
                return
$this->set_param( $parname, 'string', $parvalue, true );

            case
'image_file_ID':
                return
$this->set_param( $parname, 'integer', $parvalue, true );

            case
'ityp_ID':
                if(
$this->get( 'meta' ) )
                {    
// Don't allow default Item Type for meta category because it cannot has items:
                   
$parvalue = NULL;
                }
                elseif( (
$parvalue === 0 || $parvalue === '0' )&&
                       
$this->ID > 0 &&
                        (
$cat_Blog = & $this->get_Blog() ) &&
                       
$cat_Blog->get_default_cat_ID() == $this->ID )
                {    
// Force "No default type" of default category to "Same as collection default":
                   
$parvalue = NULL;
                }
                return
$this->set_param( $parname, 'integer', $parvalue, true );

            case
'name':
            case
'urlname':
            case
'description':
            default:
                return
$this->set_param( $parname, 'string', $parvalue, $make_null );
        }
    }


   
/**
     * Add a child
     *
     * @param object Chapter
     */
   
function add_child_category( & $Chapter )
    {
        if( !isset(
$this->children[ $Chapter->ID ] ) )
        {    
// Add only if it was not added yet:
           
$this->children[ $Chapter->ID ] = & $Chapter;
        }
    }


   
/**
     * Get File of a first found image by positions
     *
     * @return object|NULL File
     */
   
function & get_image_File()
    {
       
// Try to get a file by ID:
       
$FileCache = & get_FileCache();
        if( ! (
$cat_image_File = & $FileCache->get_by_ID( $this->get( 'image_file_ID' ), false, false ) ) )
        {    
// This chapter has no image file or it is broken:
           
$r = NULL;
            return
$r;
        }

        if( !
$cat_image_File->is_image() )
        {    
// The file must be an image:
           
$r = NULL;
            return
$r;
        }

        return
$cat_image_File;
    }


   
/**
     * Get URL of a first found image by positions
     *
     * @param array Parameters
     * @return string|NULL Image URL or NULL if it doesn't exist
     */
   
function get_image_url( $params = array() )
    {
       
$params = array_merge( array(
               
'size' => 'original',
            ),
$params );

        if( ! (
$image_File = & $this->get_image_File() ) )
        {    
// Wrong image file:
           
return NULL;
        }

       
// Get image URL for requested size:
       
$img_attribs = $image_File->get_img_attribs( $params['size'] );
    }


   
/**
     * Get image tag of this chapter
     *
     * @param array Params
     * @return string HTML code of <img /> tag or empty string if this chapter has no correct image file
     */
   
function get_image_tag( $params = array() )
    {
       
$params = array_merge( array(
               
'before'        => '',        // HTML code before image tag
               
'before_classes'=> '',        // Allow injecting additional classes into 'before'
               
'before_legend' => '',        // HTML code before image legeng(info under image tag image desc is not empty)
               
'after_legend'  => '',        // HTML code after image legeng
               
'after'         => '',        // HTML code after image tag
               
'size'          => 'crop-48x48', // Image thumbnail size
               
'sizes'         => NULL,    // simplified sizes= attribute for browser to select correct size from srcset=. Sample value: (max-width: 430px) 400px, (max-width: 670px) 640px, (max-width: 991px) 720px, (max-width: 1199px) 698px, 848px
               
'link_to'       => '',        // URL for a link ; '#category_url' for category URL ; 'original' for full image file url ; Empty for no link
               
'link_title'    => $this->get( 'description' ), // Title of the link, can be text or #title# or #desc#
               
'link_rel'      => '',        // Value for attribute "rel", usefull for jQuery libraries selecting on rel='...', e-g: 'lightbox[cat'.$this->ID.']'
               
'class'         => '',        // Image class
               
'align'         => '',        // Image align
               
'alt'           => $this->get( 'name' ), // Image alt
               
'desc'          => '#',        // Image description, used in legeng under image tag, '#' - use current description of the file
               
'size_x'        => 1,        // Use '2' to build 2x sized thumbnail that can be used for Retina display
               
'tag_size'      => NULL,    // Override "width" & "height" attributes on img tag. Allows to increase pixel density for retina/HDPI screens.
                                             // Example: ( $tag_size = '160' ) => width="160" height="160"
                                             // ( $tag_size = '160x320' ) => width="160" height="320"
                                             // NULL - use size defined by the thumbnail
                                             // 'none' - don't use attributes "width" & "height"
               
'placeholder'   => '',        // HTML to be displayed if no image; possible codes: #folder_icon
           
), $params );

        if( ! empty(
$params['before_classes'] ) )
        {    
// Inject additional classes into 'before':
           
$params['before'] = update_html_tag_attribs( $params['before'], array( 'class' => $params['before_classes'] ) );
        }

       
// Try to get a file by ID:
       
$FileCache = & get_FileCache();
       
$cat_image_File = & $FileCache->get_by_ID( $this->get( 'image_file_ID' ), false, false );
        if( !
$cat_image_File // This chapter has no image file or it is broken
           
|| ! $cat_image_File->is_image() ) // The file is NOT an image
       
{    // Display placeholder:
           
$placeholder_html = $params['placeholder'];
            switch(
$placeholder_html )
            {
                case
'#folder_icon';
                   
$placeholder_html = '<div class="evo_image_block evo_img_placeholder"><a href="$url$" class="evo_img_placeholder"><i class="fa fa-folder-o"></i></a></div>';
                    break;
            }
            return
str_replace( '$url$', $this->get_permanent_url(), $placeholder_html );
        }

        if(
$params['link_to'] == '#category_url' )
        {    
// We want to link to the category URL:
           
$params['link_to'] = $this->get_permanent_url();
        }

        return
$cat_image_File->get_tag( $params['before'],
               
$params['before_legend'],
               
$params['after_legend'],
               
$params['after'],
               
$params['size'],
               
$params['link_to'],
               
$params['link_title'],
               
$params['link_rel'],
               
$params['class'],
               
$params['align'],
               
$params['alt'],
               
$params['desc'],
               
'',
               
$params['size_x'],
               
$params['tag_size'],
               
'',
               
true,
               
$params['sizes'] );
    }


   
/**
     * Get CSS property for background with image of this Item
     *
     * @param array Params
     * @return string
     */
   
function get_background_image_css( $params = array() )
    {
       
$params = array_merge( array(
               
'position' => '#cover_and_teaser_all',
               
'size'     => 'fit-1280x720',
               
'size_2x'  => 'fit-2560x1440',
            ),
$params );

        if( ! (
$image_File = & $this->get_image_File( $params ) ) )
        {    
// Don't provide css for wrong image file:
           
return '';
        }

        return
$image_File->get_background_image_css( $params );
    }


   
/**
     * Get default Item Type of this Chapter
     *
     * @param boolean TRUE to return default Item Type object of collection when this category uses same it as collection default
     * @return object|false|NULL Default Item Type,
     *                           NULL - Same as collection default (only when $use_collection_item_type == false, otherwise real Item Type object what is used as default for collection),
     *                           FALSE - No default type or Item Type is not found in DB or Item Type is not enabled for chapter's collection
     */
   
function & get_ItemType( $load_coll_default_item_type = false )
    {
        if(
$this->get( 'ityp_ID' ) === NULL && ! $load_coll_default_item_type )
        {    
// Item Type is same as collection default,
            // Return NULL because no request to get real Item Type object:
           
$r = NULL;
            return
$r;
        }

        if(
$this->ItemType === NULL )
        {    
// Load Item Type into cache:
           
if( $this->get( 'ityp_ID' ) === NULL && $load_coll_default_item_type )
            {    
// Try to get real Item Type object of this category's collection:
               
if( ( $cat_Blog = & $this->get_Blog() ) &&
                    (
$coll_ItemType = & $cat_Blog->get_default_ItemType() ) )
                {    
// Use real Item Type object:
                   
$this->ItemType = $coll_ItemType;
                }
                else
                {    
// Impossible to get default Item Type by some unknown reason:
                   
$this->ItemType = false;
                }
            }
            elseif( (
$this->get( 'ityp_ID' ) > 0 ) &&
                    (
$ItemTypeCache = & get_ItemTypeCache() ) &&
                    (
$cat_ItemType = & $ItemTypeCache->get_by_ID( $this->get( 'ityp_ID' ), false, false ) ) &&
                    (
$cat_ItemType->is_enabled( $this->get( 'blog_ID' ) ) ) )
            {    
// Default Item Type is found in DB and it is enabled for chapter's collection:
               
$this->ItemType = $cat_ItemType;
            }
            else
            {    
// No default type or Item Type is not found in DB or Item Type is not enabled for chapter's collection:
               
$this->ItemType = false;
            }
        }

        return
$this->ItemType;
    }
}

?>