Seditio Source
Root |
./othercms/b2evolution_7.2.3/inc/collections/model/_blog.class.php
<?php
/**
 * This file implements the Blog 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)2004-2006 by Daniel HAHLER - {@link http://thequod.de/contact}.
 * Parts of this file are copyright (c)2005 by Jason Edgecombe.
 *
 * @package evocore
 */
if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' );

load_class( '_core/model/dataobjects/_dataobject.class.php', 'DataObject' );

/**
 * Blog
 *
 * Blog object with params
 *
 * @package evocore
 */
class Blog extends DataObject
{
   
/**
     * Group ID
     * @var integer
     */
   
var $sec_ID;

   
/**
     * Short name for use in navigation menus
     * @var string
     */
   
var $shortname;

   
/**
     * Complete name
     * @var string
     */
   
var $name;

   
/**
     * Tagline to be displayed on template
     * @var string
     */
   
var $tagline;

    var
$shortdesc; // description
   
var $longdesc;

   
/**
     * @var integer
     */
   
var $owner_user_ID;

   
/**
     * Lazy filled
     * @var User
     * @see get_owner_User()
     * @access protected
     */
   
var $owner_User = NULL;

    var
$advanced_perms = 0;

    var
$locale;
    var
$order;
    var
$access_type;

   
/*
     * ?> TODO: we should have an extra DB column that either defines type of blog_siteurl
     * OR split blog_siteurl into blog_siteurl_abs and blog_siteurl_rel (where blog_siteurl_rel could be "blog_sitepath")
     */
   
var $siteurl;
    var
$stub;     // stub file (can be empty/virtual)
   
var $urlname;  // used to identify blog in URLs
   
var $links_blog_ID = 0;    // DEPRECATED
   
var $notes;
    var
$keywords;
    var
$allowtrackbacks = 0;
    var
$allowblogcss = 0;
    var
$allowusercss = 0;
    var
$in_bloglist = 'public';
    var
$media_location = 'default';
    var
$media_subdir = '';
    var
$media_fullpath = '';
    var
$media_url = '';

    var
$normal_skin_ID = NULL;
    var
$mobile_skin_ID = NULL;
    var
$tablet_skin_ID = NULL;
    var
$alt_skin_ID = NULL;
    var
$base_collection_ID = 0;

   
/**
     * The basepath of that collection.
     *
     * Lazy filled by get_basepath()
     *
     * @var string
     */
   
var $basepath;


   
/**
     * The URL to the basepath of that collection.
     * This is supposed to be the same as $baseurl but localized to the domain of the blog/
     *
     * Lazy filled by get_basepath_url()
     *
     * @var string
     */
   
var $basepath_url;


   
/**
     * The domain of that collection.
     *
     * Lazy filled by get_baseurl_root()
     *
     * @var string
     */
   
var $baseurl_root;


   
/**
     * The URL aliases
     *
     * Lazy filled by get_url_aliases()
     *
     * @var array
     */
   
var $url_aliases;
    var
$update_url_aliases = false;


   
/**
     * The domain for cookie of that collection.
     *
     * Lazy filled by get_cookie_domain()
     *
     * @var string
     */
   
var $cookie_domain;


   
/**
     * The path for cookie of that collection.
     *
     * Lazy filled by get_cookie_path()
     *
     * @var string
     */
   
var $cookie_path;


   
/**
     * The htsrv URLs to the basepath of that collection.
     *
     * Lazy filled by get_htsrv_url()
     *
     * @var array: 0 - normal URL, 1 - secure URL
     */
   
var $htsrv_urls;

   
/**
     * Additional settings for the collection.  lazy filled.
      *
     * @see Blog::get_setting()
     * @see Blog::set_setting()
     * @see Blog::load_CollectionSettings()
     * Any non vital params should go into there (this includes many of the above).
     *
     * @var CollectionSettings
     */
   
var $CollectionSettings;


   
/**
     * Lazy filled
     *
     * @var integer
     */
   
var $default_cat_ID;

   
/**
     * @var string Type of blog ( 'std', 'photo', 'group', 'forum', 'manual' )
     */
   
var $type;

   
/**
     * @var array Data of moderators which must be notified about new/edited comments
     */
   
var $comment_moderator_user_data;


   
/**
     * @var array Locales:
     *        Key - Locale key
     *        Value - Collection ID or NULL for locale of this collection
     */
   
var $locales = NULL;


   
/**
     * Constructor
     *
     * @param object DB row
     */
   
function __construct( $db_row = NULL )
    {
        global
$Timer, $Settings;

       
$Timer->start( 'Blog constructor' );

       
// Call parent constructor:
       
parent::__construct( 'T_blogs', 'blog_', 'blog_ID' );

        if(
$db_row == NULL )
        {
            global
$default_locale;
           
// echo 'Creating blank blog';
           
$this->owner_user_ID = 1; // DB default
           
$this->set( 'locale', $default_locale );
           
$this->set_locales( array( $default_locale ) );
           
// Use access type(Collection base URL) from general settings:
           
$this->set( 'access_type', $Settings->get( 'coll_access_type' ) );
            if(
is_logged_in() )
            {    
// Set section what is used as default for group of current user:
               
global $current_User;
               
$user_Group = & $current_User->get_Group();
               
$this->set( 'sec_ID', $user_Group->get_setting( 'perm_default_sec_ID' ) );
            }
        }
        else
        {
           
/**
             * NOTE: Check each new added or renamed field by function isset() below,
             *       Otherwise it may create issues during upgrade process from old to new DB:
             */
           
$this->ID = $db_row->blog_ID;
           
$this->sec_ID = isset( $db_row->blog_sec_ID ) ? $db_row->blog_sec_ID : NULL;
           
$this->shortname = $db_row->blog_shortname;
           
$this->name = $db_row->blog_name;
           
$this->owner_user_ID = $db_row->blog_owner_user_ID;
           
$this->advanced_perms = $db_row->blog_advanced_perms;
           
$this->tagline = $db_row->blog_tagline;
           
$this->shortdesc = isset( $db_row->blog_shortdesc ) ? $db_row->blog_shortdesc : '';    // description
           
$this->longdesc = $db_row->blog_longdesc;
           
$this->locale = $db_row->blog_locale;
           
$this->access_type = $db_row->blog_access_type;
           
$this->siteurl = $db_row->blog_siteurl;
           
$this->urlname = $db_row->blog_urlname;
           
$this->links_blog_ID = $db_row->blog_links_blog_ID; // DEPRECATED
           
$this->notes = $db_row->blog_notes;
           
$this->keywords = $db_row->blog_keywords;
           
$this->allowtrackbacks = $db_row->blog_allowtrackbacks;
           
$this->allowblogcss = $db_row->blog_allowblogcss;
           
$this->allowusercss = $db_row->blog_allowusercss;
           
$this->in_bloglist = $db_row->blog_in_bloglist;
           
$this->media_location = $db_row->blog_media_location;
           
$this->media_subdir = $db_row->blog_media_subdir;
           
$this->media_fullpath = $db_row->blog_media_fullpath;
           
$this->media_url = $db_row->blog_media_url;
           
$this->type = isset( $db_row->blog_type ) ? $db_row->blog_type : 'std';
           
$this->order = isset( $db_row->blog_order ) ? $db_row->blog_order : 0;
           
$this->normal_skin_ID = isset( $db_row->blog_normal_skin_ID ) ? $db_row->blog_normal_skin_ID : NULL; // check by isset() to avoid warnings of deleting all tables before new install
           
$this->mobile_skin_ID = isset( $db_row->blog_mobile_skin_ID ) ? $db_row->blog_mobile_skin_ID : NULL; // NULL means the same as normal_skin_ID value
           
$this->tablet_skin_ID = isset( $db_row->blog_tablet_skin_ID ) ? $db_row->blog_tablet_skin_ID : NULL; // NULL means the same as normal_skin_ID value
           
$this->alt_skin_ID = isset( $db_row->blog_alt_skin_ID ) ? $db_row->blog_alt_skin_ID : NULL; // NULL means the same as normal_skin_ID value
       
}

       
$Timer->pause( 'Blog constructor' );
    }


   
/**
     * Get delete cascade settings
     *
     * @return array
     */
   
static function get_delete_cascades()
    {
        return array(
                array(
'table'=>'T_coll_user_favs', 'fk'=>'cufv_blog_ID', 'msg'=>T_('%d user favorites') ),
                array(
'table'=>'T_coll_settings', 'fk'=>'cset_coll_ID', 'msg'=>T_('%d blog settings') ),
                array(
'table'=>'T_coll_locales', 'fk'=>'cl_coll_ID', 'msg'=>T_('%d extra locales associations') ),
                array(
'table'=>'T_coll_url_aliases', 'fk'=>'cua_coll_ID', 'msg'=>T_('%d URL aliases') ),
                array(
'table'=>'T_coll_user_perms', 'fk'=>'bloguser_blog_ID', 'msg'=>T_('%d user permission definitions') ),
                array(
'table'=>'T_coll_group_perms', 'fk'=>'bloggroup_blog_ID', 'msg'=>T_('%d group permission definitions') ),
                array(
'table'=>'T_subscriptions', 'fk'=>'sub_coll_ID', 'msg'=>T_('%d subscriptions') ),
                array(
'table'=>'T_widget__container', 'fk'=>'wico_coll_ID', 'msg'=>T_('%d widget container'),
                       
'class'=>'WidgetContainer', 'class_path'=>'widgets/model/_widgetcontainer.class.php' ),
                array(
'table'=>'T_hitlog', 'fk'=>'hit_coll_ID', 'msg'=>T_('%d hits') ),
                array(
'table'=>'T_hits__aggregate', 'fk'=>'hagg_coll_ID', 'msg'=>T_('%d hits aggregations') ),
                array(
'table'=>'T_hits__aggregate_sessions', 'fk'=>'hags_coll_ID', 'msg'=>T_('%d sessions aggregations') ),
                array(
'table'=>'T_items__type_coll', 'fk'=>'itc_coll_ID', 'msg'=>T_('%d Post type associations with collections') ),
                array(
'table'=>'T_categories', 'fk'=>'cat_blog_ID', 'msg'=>T_('%d related categories with all of their content recursively'),
                       
'class'=>'Chapter', 'class_path'=>'chapters/model/_chapter.class.php' ),
                array(
'table'=>'T_files', 'fk'=>'file_root_ID', 'and_condition'=>'file_root_type = "collection"', 'msg'=>T_('%d files in this blog file root'),
                       
'class'=>'File', 'class_path'=>'files/model/_file.class.php' ),
                array(
'table'=>'T_temporary_ID', 'fk'=>'tmp_coll_ID', 'msg'=>T_('%d temporary uploaded links') ),
            );
    }


   
/**
     * Get delete restriction settings
     *
     * @return array
     */
   
static function get_delete_restrictions()
    {
        global
$admin_url;

        return array(
                array(
'table' => 'T_settings', 'fk' => 'set_value', 'and_condition' => 'set_name = "default_blog_ID"', 'msg' => sprintf( T_('This collection cannot be deleted because it is used as <a %s>Default collection to display</a>.'), 'href="'.$admin_url.'?ctrl=collections&amp;tab=site_settings"' ) ),
                array(
'table' => 'T_settings', 'fk' => 'set_value', 'and_condition' => 'set_name = "login_blog_ID"', 'msg' => sprintf( T_('This collection cannot be deleted because it is used as <a %s>Collection for login/registration</a>.'), 'href="'.$admin_url.'?ctrl=collections&amp;tab=site_settings"' ) ),
                array(
'table' => 'T_settings', 'fk' => 'set_value', 'and_condition' => 'set_name = "msg_blog_ID"', 'msg' => sprintf( T_('This collection cannot be deleted because it is used as <a %s>Collection for profiles/messaging</a>.'), 'href="'.$admin_url.'?ctrl=collections&amp;tab=site_settings"' ) ),
                array(
'table' => 'T_settings', 'fk' => 'set_value', 'and_condition' => 'set_name = "info_blog_ID"', 'msg' => sprintf( T_('This collection cannot be deleted because it is used as <a %s>Collection for shared content blocks</a>.'), 'href="'.$admin_url.'?ctrl=collections&amp;tab=site_settings"' ) ),
            );
    }


   
/**
     * Compare two Blog based on the common blog order setting
     *
     * @param Blog A
     * @param Blog B
     * @return number -1 if A < B, 1 if A > B, 0 if A == B
     */
   
static function compare_blogs( $a_Blog, $b_Blog )
    {
        global
$Settings;

        if(
$a_Blog->ID == $b_Blog->ID )
        {
            return
0;
        }

       
$order_by = $Settings->get('blogs_order_by');
       
$order_dir = $Settings->get('blogs_order_dir');

        if(
$order_by == 'RAND' )
        {
// In case of Random order we consider every blog as equal
           
return 0;
        }

       
$blog_a_value = $a_Blog->get( $order_by );
       
$blog_b_value = $b_Blog->get( $order_by );
        if(
$blog_a_value == $blog_b_value )
        {
// The compare fields are equal sort based on the ID
           
$blog_a_value = $a_Blog->ID;
           
$blog_b_value = $b_Blog->ID;
        }
       
$result = is_numeric( $blog_a_value ) ? ( $blog_a_value < $blog_b_value ? -1 : 1 ) : strcmp( $blog_a_value, $blog_b_value );

        if(
$order_dir == 'DESC' )
        {
// Change the order direction
           
$result = $result * (-1);
        }

        return
$result;
    }


   
/**
     * Initialize blog setting by kind
     *
     * @param string Kind: 'minisite', 'main', 'std', 'photo', 'group', 'forum', 'manual'
     * @param string Name
     * @param string Short name
     * @param string Url/slug
     */
   
function init_by_kind( $kind, $name = NULL, $shortname = NULL, $urlname = NULL )
    {
       
// Set default tagline for collection of any kind:
       
$this->set( 'tagline', T_('This is the collection\'s tagline.') );

        switch(
$kind )
        {
            case
'minisite':
               
$this->set( 'type', 'minisite' );
               
$this->set( 'name', empty( $name ) ? T_('Mini-Site Title') : $name );
               
$this->set( 'shortname', empty( $shortname ) ? T_('Mini-Site') : $shortname );
               
$this->set( 'urlname', empty( $urlname ) ? 'minisite' : $urlname );
               
$this->set_setting( 'front_disp', 'front' );
                if(
$blog_skin_ID = $this->get_skin_ID( 'normal' ) )
                {
                   
$this->set_setting( 'skin'.$blog_skin_ID.'_section_2_image_file_ID', NULL );
                   
$this->set_setting( 'skin'.$blog_skin_ID.'_section_3_display', 1 );
                   
$this->set_setting( 'skin'.$blog_skin_ID.'_section_3_title_color', '#FFFFFF' );
                   
$this->set_setting( 'skin'.$blog_skin_ID.'_section_3_text_color', '#FFFFFF' );
                   
$this->set_setting( 'skin'.$blog_skin_ID.'_section_3_link_color', '#FFFFFF' );
                   
$this->set_setting( 'skin'.$blog_skin_ID.'_section_3_link_h_color', '#FFFFFF' );
                   
$this->set_setting( 'skin'.$blog_skin_ID.'_section_4_image_file_ID', NULL );
                }
                break;

            case
'main':
               
$this->set( 'type', 'main' );
               
$this->set( 'name', empty( $name ) ? T_('Homepage Title') : $name );
               
$this->set( 'shortname', empty( $shortname ) ? T_('Home') : $shortname );
               
$this->set( 'urlname', empty( $urlname ) ? 'home' : $urlname );
               
$this->set_setting( 'front_disp', 'front' );
               
$this->set_setting( 'in_skin_login', '1' );
                break;

            case
'photo':
               
$this->set( 'type', 'photo' );
               
$this->set( 'name', empty($name) ? T_('Photos') : $name );
               
$this->set( 'shortname', empty($shortname) ? T_('Photos') : $shortname );
               
$this->set( 'urlname', empty($urlname) ? 'photos' : $urlname );
               
$this->set_setting( 'posts_per_page', 12 );
               
$this->set_setting( 'archive_mode', 'postbypost' );
               
$this->set_setting( 'front_disp', 'posts' );

               
// Try to find post type "Photo Album" in DB:
               
global $DB;
               
$photo_album_type_ID = $DB->get_var( 'SELECT ityp_ID
                     FROM T_items__type
                    WHERE ityp_name = "Photo Album"'
);
                if(
$photo_album_type_ID )
                {    
// Set default post type as "Photo Album":
                   
$this->set_setting( 'default_post_type', $photo_album_type_ID );
                }
                if(
is_pro() )
                {    
// Enable tracking of unread content only in PRO version:
                   
$this->set_setting( 'track_unread_content', 1 );
                }
                break;

            case
'group':
               
$this->set( 'type', 'group' );
               
$this->set( 'name', empty($name) ? T_('Tracker Title') : $name );
               
$this->set( 'shortname', empty($shortname) ? T_('Tracker') : $shortname );
               
$this->set( 'urlname', empty($urlname) ? 'tracker' : $urlname );
               
$this->set_setting( 'use_workflow', 1 );
               
$this->set_setting( 'in_skin_editing', '1' );
               
$this->set_setting( 'front_disp', 'front' );
               
$this->set_setting( 'download_enable', 0 );
               
// Try to find post type "Forum Topic" in DB
               
global $DB;
               
$forum_topic_type_ID = $DB->get_var( 'SELECT ityp_ID
                     FROM T_items__type
                    WHERE ityp_name = "Task"'
);
                if(
$forum_topic_type_ID )
                {
// Set default post type as "Forum Topic"
                   
$this->set_setting( 'default_post_type', $forum_topic_type_ID );
                }
                if(
is_pro() )
                {    
// Enable tracking of unread content only in PRO version:
                   
$this->set_setting( 'track_unread_content', 1 );
                }
                break;

            case
'forum':
               
$this->set( 'type', 'forum' );
               
$this->set( 'name', empty($name) ? T_('Forums Title') : $name );
               
$this->set( 'shortname', empty($shortname) ? T_('Forums') : $shortname );
               
$this->set( 'urlname', empty($urlname) ? 'forums' : $urlname );
               
$this->set( 'advanced_perms', 1 );
               
$this->set_setting( 'post_navigation', 'same_category' );
               
$this->set_setting( 'allow_comments', 'registered' );
               
$this->set_setting( 'in_skin_editing', '1' );
               
$this->set_setting( 'posts_per_page', 30 );
               
$this->set_setting( 'allow_html_comment', 0 );
               
$this->set_setting( 'comment_maxlen', '' );
               
$this->set_setting( 'orderby', 'contents_last_updated_ts' );
               
$this->set_setting( 'orderdir', 'DESC' );
               
$this->set_setting( 'enable_goto_blog', 'post' );
               
$this->set_setting( 'front_disp', 'front' );
               
$this->set_setting( 'track_unread_content', 1 );
               
$this->set_setting( 'allow_rating_comment_helpfulness', 1 );
               
$this->set_setting( 'category_ordering', 'manual' );
               
$this->set_setting( 'disp_featured_above_list', 1 );
               
$this->set_setting( 'download_enable', 0 );

               
// Try to find post type "Forum Topic" in DB
               
global $DB;
               
$forum_topic_type_ID = $DB->get_var( 'SELECT ityp_ID
                     FROM T_items__type
                    WHERE ityp_name = "Forum Topic"'
);
                if(
$forum_topic_type_ID )
                {
// Set default post type as "Forum Topic"
                   
$this->set_setting( 'default_post_type', $forum_topic_type_ID );
                }
                break;

            case
'manual':
               
$this->set( 'type', 'manual' );
               
$this->set( 'name', empty($name) ? T_('Manual Title') : $name );
               
$this->set( 'shortname', empty($shortname) ? T_('Manual') : $shortname );
               
$this->set( 'urlname', empty($urlname) ? 'manual' : $urlname );
               
$this->set_setting( 'post_navigation', 'same_category' );
               
$this->set_setting( 'single_links', 'chapters' );
               
$this->set_setting( 'enable_goto_blog', 'post' );
               
$this->set_setting( 'front_disp', 'front' );
               
$this->set_setting( 'category_ordering', 'manual' );
               
$this->set_setting( 'main_content', 'excerpt' );
               
$this->set_setting( 'chapter_content', 'excerpt' );
                if(
is_pro() )
                {    
// Enable tracking of unread content only in PRO version:
                   
$this->set_setting( 'track_unread_content', 1 );
                }

               
// Try to find post type "Manual Page" in DB
               
global $DB;
               
$manual_page_type_ID = $DB->get_var( 'SELECT ityp_ID
                     FROM T_items__type
                    WHERE ityp_name = "Manual Page"'
);
                if(
$manual_page_type_ID )
                {
// Set default post type as "Manual Page"
                   
$this->set_setting( 'default_post_type', $manual_page_type_ID );
                }
                break;

            case
'std':
            default:
                if(
$kind != 'std' )
                {    
// Check if given kind is allowed by system or plugins:
                   
$kinds = get_collection_kinds();
                    if( ! isset(
$kinds[ $kind ] ) )
                    {    
// If unknown kind, restrict this to standard:
                       
$kind = 'std';
                    }
                }
               
$this->set( 'type', $kind );
               
$this->set( 'name', empty($name) ? T_('Blog') : $name );
               
$this->set( 'shortname', empty($shortname) ? T_('Blog') : $shortname );
               
$this->set( 'urlname', empty($urlname) ? 'blog' : $urlname );
                if(
is_pro() )
                {    
// Enable tracking of unread content only in PRO version:
                   
$this->set_setting( 'track_unread_content', 1 );
                }
                break;
        }

        if( empty(
$name) && empty($shortname) && empty($urlname) )
        {    
// Not in installation mode, init custom collection kinds.
           
global $Plugins;

           
// Defines blog settings by its kind.
           
$Plugins->trigger_event( 'InitCollectionKinds', array(
                           
'Blog' => & $this,
                           
'kind' => & $kind,
                        ) );
        }
    }


   
/**
     * Load data from Request form fields.
     *
     * @param array groups of params to load
     * @return boolean true if loaded data seems valid.
     */
   
function load_from_Request( $groups = array() )
    {
        global
$Messages, $default_locale, $DB, $Settings;

       
/**
         * @var User
         */
       
global $admin_url, $current_User;
       
$notifications_mode = $Settings->get( 'outbound_notifications_mode' );

       
// Load collection settings and clear update cascade array
       
$this->load_CollectionSettings();

        if(
param( 'blog_name', 'string', NULL ) !== NULL )
        {
// General params:

           
$this->set_setting( 'collection_logo_file_ID', param( 'collection_logo_file_ID', 'integer', NULL ) );
           
$this->set_setting( 'collection_favicon_file_ID', param( 'collection_favicon_file_ID', 'integer', NULL ) );

           
$this->set_from_Request( 'name' );
           
$this->set( 'shortname', param( 'blog_shortname', 'string', true ) );

           
// Section:
           
$SectionCache = & get_SectionCache();
           
$SectionCache->load_available();
            if(
count( $SectionCache->cache_available ) == 1 )
            {    
// Use single available section:
               
foreach( $SectionCache->cache_available as $available_Section )
                {
                   
$new_sec_ID = $available_Section->ID;
                    break;
                }
            }
            else
            {    
// Get a section from the submitted form:
               
$new_sec_ID = param( 'sec_ID', 'integer', NULL );
               
param_string_not_empty( 'sec_ID', T_('Please select a section.') );
            }
            if(
$new_sec_ID != $this->get( 'sec_ID' ) )
            {    
// If section has been changed to new:
               
if( ! check_user_perm( 'blogs', 'create', false, $new_sec_ID ) )
                {
                   
param_error( 'sec_ID', T_('You don\'t have a permission to create a collection in this section.') );
                }
               
$this->set( 'sec_ID', $new_sec_ID );
            }

           
// Language / locale:
           
if( param( 'blog_locale', 'string', NULL ) !== NULL )
            {
// These settings can be hidden when only one locale is enabled in the system
               
$this->set_from_Request( 'locale' );
               
$this->set_setting( 'locale_source', param( 'blog_locale_source', 'string', 'blog' ) );
               
$this->set_setting( 'post_locale_source', param( 'blog_post_locale_source', 'string', 'post' ) );
               
$this->set_setting( 'new_item_locale_source', param( 'blog_new_item_locale_source', 'string', 'select_coll' ) );

               
$extra_locales = param( 'blog_locale_extra', 'array:string', NULL );
               
$linked_colls = param( 'blog_locale_link_coll', 'array:integer', NULL );
                if(
$extra_locales !== NULL || $linked_colls !== NULL )
                {    
// Set new extra locales and linked collections:
                   
$this->set_locales( $extra_locales, $linked_colls );
                }
            }

           
// Collection permissions:
           
$prev_advanced_perms = $this->get( 'advanced_perms' );
           
$new_advanced_perms = param( 'advanced_perms', 'integer', 0 );
           
$prev_allow_access = $this->get_setting( 'allow_access' );
           
$new_allow_access = param( 'blog_allow_access', 'string', '' );

            if(
get_param( 'action' ) != 'update_confirm' && $this->ID > 0 &&
                (
$prev_allow_access != $new_allow_access || $prev_advanced_perms != $new_advanced_perms ) )
            {    
// One of these settings is changing, try to check if max allowed status will be changed too:
               
$prev_max_allowed_status = $this->get_max_allowed_status();
               
$prev_access_title = $this->get_access_title();
               
$this->set( 'advanced_perms', $new_advanced_perms );
               
$this->set_setting( 'allow_access', $new_allow_access );
               
$new_max_allowed_status = $this->get_max_allowed_status();
               
$new_access_title = $this->get_access_title();

               
$status_orders = get_visibility_statuses( 'ordered-index' );
                if(
$status_orders[ $new_max_allowed_status ] < $status_orders[ $prev_max_allowed_status ] )
                {    
// If max allowed status will be reduced after collection updating:
                   
$count_reduced_status_data = $this->get_count_reduced_status_data( $new_max_allowed_status );
                    if(
$count_reduced_status_data !== false )
                    {    
// If some posts or comment will be updated:
                       
$this->confirmation = array();
                       
$this->confirmation['title'] = sprintf( T_('You changed the access setting of this collection from "%s" to "%s". This will also:'), $prev_access_title, $new_access_title );
                       
$status_titles = get_visibility_statuses();
                        foreach(
$count_reduced_status_data['posts'] as $status_key => $count )
                        {
                           
$this->confirmation['messages'][] = sprintf( T_('change %d posts from "%s" to "%s"'), intval( $count ), $status_titles[ $status_key ], $status_titles[ $new_max_allowed_status ] );
                        }
                        foreach(
$count_reduced_status_data['comments'] as $status_key => $count )
                        {
                           
$this->confirmation['messages'][] = sprintf( T_('change %d comments from "%s" to "%s"'), intval( $count ), $status_titles[ $status_key ], $status_titles[ $new_max_allowed_status ] );
                        }
                    }
                }
            }

           
$this->set( 'advanced_perms', $new_advanced_perms );
           
$this->set_setting( 'allow_access', $new_allow_access );

            if(
$prev_allow_access != $this->get_setting( 'allow_access' ) )
            {    
// If setting "Allow access to" is changed to:
               
switch( $this->get_setting( 'allow_access' ) )
                {    
// Automatically enable/disable moderation statuses:
                   
case 'public':
                       
// Enable "Community" and "Members":
                       
$enable_moderation_statuses = array( 'community', 'protected' );
                       
$enable_comment_moderation_statuses = array( 'community', 'protected', 'review', 'draft' );
                       
$disable_comment_moderation_statuses = array( 'private' );
                        break;
                    case
'users':
                       
// Disable "Community" and Enable "Members":
                       
$disable_moderation_statuses = array( 'community' );
                       
$enable_moderation_statuses = array( 'protected' );
                       
$enable_comment_moderation_statuses = array( 'protected', 'review', 'draft' );
                       
$disable_comment_moderation_statuses = array( 'community', 'private' );
                        break;
                    case
'members':
                       
// Disable "Community" and "Members":
                       
$disable_moderation_statuses = array( 'community', 'protected' );
                       
$enable_comment_moderation_statuses = array( 'review', 'draft' );
                       
$disable_comment_moderation_statuses = array( 'community', 'protected', 'private' );
                        break;
                }
               
$post_moderation_statuses = $this->get_setting( 'post_moderation_statuses' );
               
$post_moderation_statuses = empty( $post_moderation_statuses ) ? array() : explode( ',', $post_moderation_statuses );
               
$comment_moderation_statuses = $this->get_setting( 'moderation_statuses' );
               
$comment_moderation_statuses = empty( $comment_moderation_statuses ) ? array() : explode( ',', $comment_moderation_statuses );
                if( ! empty(
$disable_moderation_statuses ) )
                {    
// Disable moderation statuses:
                   
$post_moderation_statuses = array_diff( $post_moderation_statuses, $disable_moderation_statuses );
                   
$comment_moderation_statuses = array_diff( $comment_moderation_statuses, $disable_moderation_statuses );
                }
                if( ! empty(
$enable_moderation_statuses ) )
                {    
// Enable moderation statuses:
                   
$post_moderation_statuses = array_unique( array_merge( $enable_moderation_statuses, $post_moderation_statuses ) );
                   
$comment_moderation_statuses = array_unique( array_merge( $enable_moderation_statuses, $comment_moderation_statuses ) );
                }

                if( ! empty(
$disable_comment_moderation_statuses ) )
                {
                   
$comment_moderation_statuses = array_diff( $comment_moderation_statuses, $disable_comment_moderation_statuses );
                }
                if( ! empty(
$enable_comment_moderation_statuses ) )
                {
                   
$comment_moderation_statuses = array_unique( array_merge( $enable_comment_moderation_statuses, $comment_moderation_statuses ) );
                }

               
$this->set_setting( 'post_moderation_statuses', implode( ',', $post_moderation_statuses ) );
               
// Force enabled statuses regardless of previous settings
               
$this->set_setting( 'moderation_statuses', implode( ',', $enable_comment_moderation_statuses ) );
            }
            if(
$this->get_setting( 'allow_access' ) != 'public' )
            {    
// Disable site maps, feeds and ping plugins for not public collection:
               
$this->set_setting( 'enable_sitemaps', 0 );
               
$this->set_setting( 'feed_content', 'none' );
               
$this->set_setting( 'ping_plugins', '' );
            }

           
// Lists of collections:
           
$this->set( 'order', param( 'blog_order', 'integer' ) );
            if(
$this->ID > 0 )
            {
               
$default_in_bloglist = 'public';
            }
            else
            {
                switch(
$new_allow_access )
                {
                    case
'public':
                       
$default_in_bloglist = 'public';
                        break;

                    case
'users':
                       
$default_in_bloglist = 'logged';
                        break;

                    case
'members':
                       
$default_in_bloglist = 'member';
                        break;

                    default:
                       
$default_in_bloglist = 'never';
                }
            }
           
$this->set( 'in_bloglist', param( 'blog_in_bloglist', 'string' ), $default_in_bloglist );
        }

        if(
param( 'archive_links', 'string', NULL ) !== NULL )
        {
// Archive link type:
           
$this->set_setting( 'archive_links', get_param( 'archive_links' ) );
        }

        if(
param( 'archive_posts_per_page', 'integer', NULL ) !== NULL )
        {    
// Archive posts per page:
           
$this->set_setting( 'archive_posts_per_page', get_param( 'archive_posts_per_page' ) );
        }

        if(
param( 'chapter_links', 'string', NULL ) !== NULL )
        {
// Chapter link type:
           
$this->set_setting( 'chapter_links', get_param( 'chapter_links' ) );
        }


        if(
param( 'category_prefix', 'string', NULL) !== NULL )
        {
           
$category_prefix = get_param( 'category_prefix' );
            if( !
preg_match( '|^([A-Za-z0-9\-_]+(/[A-Za-z0-9\-_]+)*)?$|', $category_prefix) )
            {
               
param_error( 'category_prefix', T_('Invalid category prefix.') );
            }
           
$this->set_setting( 'category_prefix', $category_prefix);
        }

        if(
param( 'atom_redirect', 'url', NULL ) !== NULL )
        {
           
param_check_url( 'atom_redirect', 'commenting' );
           
$this->set_setting( 'atom_redirect', get_param( 'atom_redirect' ) );

           
param( 'rss2_redirect', 'url', NULL );
           
param_check_url( 'rss2_redirect', 'commenting' );
           
$this->set_setting( 'rss2_redirect', get_param( 'rss2_redirect' ) );
        }

        if(
in_array( 'template', $groups ) )
        {
           
$this->set_setting( 'allow_duplicate', param( 'blog_allow_duplicate', 'integer', 0 ) );
        }

        if(
in_array( 'metadata', $groups ) )
        {
           
// Social media boilerplate
           
$this->set_setting( 'social_media_image_file_ID', param( 'social_media_image_file_ID', 'integer', NULL ) );

            if(
param( 'blog_shortdesc', 'string', NULL ) !== NULL )
            {    
// Description:
               
$this->set_from_Request( 'shortdesc' );
            }

            if(
param( 'blog_longdesc', 'html', NULL ) !== NULL )
            {    
// HTML long description:
               
param_check_html( 'blog_longdesc', T_('Invalid long description') );
               
$this->set( 'longdesc', get_param( 'blog_longdesc' ) );
            }

            if(
param( 'blog_keywords', 'string', NULL ) !== NULL )
            {    
// Keywords:
               
$this->set_from_Request( 'keywords' );
            }

           
// Publisher logo:
           
$this->set_setting( 'publisher_logo_file_ID', param( 'blog_publisher_logo_file_ID', 'integer' ) );

            if(
param( 'blog_publisher_name', 'string', NULL ) !== NULL )
            {    
// Publisher name:
               
$this->set_setting( 'publisher_name', get_param( 'blog_publisher_name' ) );
            }

            if(
param( 'blog_footer_text', 'html', NULL ) !== NULL )
            {
// Blog footer:
               
param_check_html( 'blog_footer_text', T_('Invalid blog footer') );
               
$this->set_setting( 'blog_footer_text', get_param( 'blog_footer_text' ) );
            }

            if(
param( 'single_item_footer_text', 'html', NULL ) !== NULL )
            {
// Blog footer:
               
param_check_html( 'single_item_footer_text', T_('Invalid single post footer') );
               
$this->set_setting( 'single_item_footer_text', get_param( 'single_item_footer_text' ) );
            }

            if(
param( 'xml_item_footer_text', 'html', NULL ) !== NULL )
            {
// Blog footer:
               
param_check_html( 'xml_item_footer_text', T_('Invalid RSS footer') );
               
$this->set_setting( 'xml_item_footer_text', get_param( 'xml_item_footer_text' ) );
            }
        }

        if(
param( 'image_size', 'string', NULL ) !== NULL )
        {
           
$this->set_setting( 'image_size', get_param( 'image_size' ));
        }

        if(
param( 'tag_links', 'string', NULL ) !== NULL )
        {
// Tag page link type:
           
$this->set_setting( 'tag_links', get_param( 'tag_links' ) );
        }

        if(
param( 'tag_prefix', 'string', NULL) !== NULL )
        {
           
$tag_prefix = get_param( 'tag_prefix' );
            if( !
preg_match( '|^([A-Za-z0-9\-_]+(/[A-Za-z0-9\-_]+)*)?$|', $tag_prefix) )
            {
               
param_error( 'tag_prefix', T_('Invalid tag prefix.') );
            }
           
$this->set_setting( 'tag_prefix', $tag_prefix);
        }

       
// Default to "tag", if "prefix-only" is used, but no tag_prefix was provided.
       
if( get_param('tag_links') == 'prefix-only' && ! strlen(param( 'tag_prefix', 'string', NULL)) )
        {
           
$this->set_setting( 'tag_prefix', 'tag' );
        }

       
// Use rel="tag" attribute? (checkbox)
       
$this->set_setting( 'tag_rel_attib', param('tag_rel_attib', 'integer', 0) );


        if(
param( 'chapter_content', 'string', NULL ) !== NULL )
        {
// What kind of content on chapter pages?
           
$this->set_setting( 'chapter_content', get_param( 'chapter_content' ) );
        }
        if(
param( 'tag_content', 'string', NULL ) !== NULL )
        {
// What kind of content on tags pages?
           
$this->set_setting( 'tag_content', get_param( 'tag_content' ) );
        }
        if(
param( 'archive_content', 'string', NULL ) !== NULL )
        {
// What kind of content on archive pages?
           
$this->set_setting( 'archive_content', get_param( 'archive_content' ) );
        }
        if(
param( 'filtered_content', 'string', NULL ) !== NULL )
        {
// What kind of content on filtered pages?
           
$this->set_setting( 'filtered_content', get_param( 'filtered_content' ) );
        }
        if(
param( 'main_content', 'string', NULL ) !== NULL )
        {
// What kind of content on main pages?
           
$this->set_setting( 'main_content', get_param( 'main_content' ) );
        }

       
// Chapter posts per page:
       
$this->set_setting( 'chapter_posts_per_page', param( 'chapter_posts_per_page', 'integer', NULL ), true );
       
// Tag posts per page:
       
$this->set_setting( 'tag_posts_per_page', param( 'tag_posts_per_page', 'integer', NULL ), true );

        if(
param( 'user_prefix', 'string', NULL ) !== NULL )
        {    
// User profile page prefix:
           
param_check_regexp( 'user_prefix', '#^[a-z0-9\-_]*$#i', sprintf( T_('User profile page prefix can contain only letters, digits, %s or %s.'), '<code>-</code>', '<code>_</code>' ) );
           
$this->set_setting( 'user_prefix', get_param( 'user_prefix' ) );
        }

        if(
param( 'user_links', 'string', NULL ) !== NULL )
        {    
// User profile URLs:
           
$this->set_setting( 'user_links', get_param( 'user_links' ) );
        }

        if(
param( 'single_links', 'string', NULL ) !== NULL )
        {
// Single post link type:
           
$this->set_setting( 'single_links', get_param( 'single_links' ) );
        }

        if(
param( 'slug_limit', 'integer', NULL ) !== NULL )
        {
// Limit slug length:
           
$this->set_setting( 'slug_limit', get_param( 'slug_limit' ) );
        }

        if(
param( 'normal_skin_ID', 'integer', NULL ) !== NULL )
        {
// Normal skin ID:
           
$updated_skin_type = 'normal';
           
$updated_skin_ID = get_param( 'normal_skin_ID' );
           
$this->set( 'normal_skin_ID', $updated_skin_ID );
        }

        if(
param( 'mobile_skin_ID', 'integer', NULL ) !== NULL )
        {
// Mobile skin ID:
           
$updated_skin_type = 'mobile';
           
$updated_skin_ID = get_param( 'mobile_skin_ID' );
            if(
$updated_skin_ID == 0 )
            {
// Don't store this empty setting in DB
               
$this->set( 'mobile_skin_ID', NULL );
            }
            else
            {
// Set mobile skin
               
$this->set( 'mobile_skin_ID', $updated_skin_ID );
            }
        }

        if(
param( 'tablet_skin_ID', 'integer', NULL ) !== NULL )
        {
// Tablet skin ID:
           
$updated_skin_type = 'tablet';
           
$updated_skin_ID = get_param( 'tablet_skin_ID' );
            if(
$updated_skin_ID == 0 )
            {
// Don't store this empty setting in DB
               
$this->set( 'tablet_skin_ID', NULL );
            }
            else
            {
// Set tablet skin
               
$this->set( 'tablet_skin_ID', $updated_skin_ID );
            }
        }

        if(
param( 'alt_skin_ID', 'integer', NULL ) !== NULL )
        {
// Alt skin ID:
           
$updated_skin_type = 'alt';
           
$updated_skin_ID = get_param( 'alt_skin_ID' );
            if(
$updated_skin_ID == 0 )
            {
// Don't store this empty setting in DB
               
$this->set( 'alt_skin_ID', NULL );
            }
            else
            {
// Set alt skin
               
$this->set( 'alt_skin_ID', $updated_skin_ID );
            }
        }

        if( ! empty(
$updated_skin_ID ) )
        {
           
load_funcs( 'skins/_skin.funcs.php' );
            if( !
skin_check_compatibility( $updated_skin_ID, 'coll' ) )
            {    
// Redirect to admin skins page selector if the skin cannot be selected:
               
$Messages->add( T_('This skin cannot be used as a collection skin.'), 'error' );
            }
        }

        if(
param( 'archives_sort_order', 'string', NULL ) !== NULL )
        {
// Archive sorting
           
$this->set_setting( 'archives_sort_order', param( 'archives_sort_order', 'string', false ) );
        }

        if(
param( 'download_delay', 'integer', NULL ) !== NULL )
        {    
// Enable Download pages:
           
$this->set_setting( 'download_enable', param( 'download_enable', 'integer', 0 ) );
           
// Download delay
           
param_check_range( 'download_delay', 0, 10, T_('Download delay must be numeric (0-10).') );
           
$this->set_setting( 'download_delay', get_param( 'download_delay' ) );
        }

        if(
param( 'feed_content', 'string', NULL ) !== NULL )
        {
// How much content in feeds?
           
$this->set_setting( 'feed_content', get_param( 'feed_content' ) );

           
param_integer_range( 'posts_per_feed', 1, 9999, T_('Items per feed must be between %d and %d.') );
           
$this->set_setting( 'posts_per_feed', get_param( 'posts_per_feed' ) );
        }

        if(
param( 'comment_feed_content', 'string', NULL ) !== NULL )
        {
// How much content in comment feeds?
           
$this->set_setting( 'comment_feed_content', get_param( 'comment_feed_content' ) );

           
param_integer_range( 'comments_per_feed', 1, 9999, T_('Comments per feed must be between %d and %d.') );
           
$this->set_setting( 'comments_per_feed', get_param( 'comments_per_feed' ) );
        }

        if(
param( 'blog_tagline', 'string', NULL ) !== NULL )
        {    
// tagline:
           
$this->set( 'tagline', get_param( 'blog_tagline' ) );
        }

        if(
param( 'blog_notes', 'html', NULL ) !== NULL )
        {    
// HTML notes:
           
param_check_html( 'blog_notes', T_('Invalid Blog Notes') );
           
$this->set( 'notes', get_param( 'blog_notes' ) );
        }


        if(
in_array( 'pings', $groups ) )
        {
// we want to load the ping checkboxes:
           
$blog_ping_plugins = param( 'blog_ping_plugins', 'array:string', array() );
           
$this->set_setting( 'ping_plugins', implode( ',', array_unique( $blog_ping_plugins ) ) );
        }

        if(
in_array( 'home', $groups ) )
        {
// we want to load the front page params:
           
$front_disp = param( 'front_disp', 'string', '' );
           
$this->set_setting( 'front_disp', $front_disp );
            if(
$front_disp == 'mustread' && ! is_pro() )
            {    
// Don't allow to store not supported front page:
               
$Messages->add( T_('"Must Read" is supported only on b2evolution PRO.'), 'error' );
            }

           
$front_post_ID = param( 'front_post_ID', 'integer', 0 );
            if(
$front_disp == 'page' )
            {
// Post ID must be required
               
param_check_not_empty( 'front_post_ID', T_('Please enter a specific post ID') );
            }
           
$this->set_setting( 'front_post_ID', $front_post_ID );
        }

        if(
in_array( 'features', $groups ) )
        {
// we want to load the posts related features:
           
$this->set_setting( 'use_workflow', param( 'blog_use_workflow', 'integer', 0 ) );
            if(
get_param( 'blog_use_workflow' ) )
            {    
// Update deadline setting only when workflow is enabled:
               
$this->set_setting( 'use_deadline', param( 'blog_use_deadline', 'integer', 0 ) );
            }

           
$this->set_setting( 'enable_goto_blog', param( 'enable_goto_blog', 'string', NULL ) );

           
$this->set_setting( 'editing_goto_blog', param( 'editing_goto_blog', 'string', NULL ) );

           
$this->set_setting( 'post_anonymous', param( 'post_anonymous', 'integer', 0 ) );

           
$this->set_setting( 'default_post_status', param( 'default_post_status', 'string', NULL ) );
           
$this->set_setting( 'default_post_status_anon', param( 'default_post_status_anon', 'string', NULL ) );

           
param( 'old_content_alert', 'integer', NULL );
           
param_check_range( 'old_content_alert', 1, 12, T_('Stale content alert must be configured with a number of months.').'(1 - 12)', false );
           
$this->set_setting( 'old_content_alert', get_param( 'old_content_alert' ), true );

           
$this->set_setting( 'post_categories', param( 'post_categories', 'string', NULL ) );

            if(
check_user_perm( 'blog_admin', 'edit', false, $this->ID ) )
            {    
// We have permission to edit advanced admin settings:
               
$this->set_setting( 'in_skin_editing', param( 'in_skin_editing', 'integer', 0 ) );
                if(
$this->get_setting( 'in_skin_editing' ) )
                {    
// Only when in-skin editing in enabled
                   
$this->set_setting( 'in_skin_editing_renderers', param( 'in_skin_editing_renderers', 'integer', 0 ) );
                }
            }
            if(
$this->get_setting( 'in_skin_editing' ) )
            {    
// Only when in-skin editing in enabled
               
$this->set_setting( 'in_skin_editing_category', param( 'in_skin_editing_category', 'integer', 0 ) );
               
$this->set_setting( 'in_skin_editing_category_order', param( 'in_skin_editing_category_order', 'integer', 0 ) );
            }

           
$this->set_setting( 'post_navigation', param( 'post_navigation', 'string', NULL ) );

           
// Show x days or x posts?:
           
$this->set_setting( 'what_to_show', param( 'what_to_show', 'string', '' ) );

           
param_integer_range( 'posts_per_page', 1, 9999, T_('Items/days per page must be between %d and %d.') );
           
$this->set_setting( 'posts_per_page', get_param( 'posts_per_page' ) );

           
$this->set_setting( 'orderby', param( 'orderby', 'string', true ) );
           
$this->set_setting( 'orderdir', param( 'orderdir', 'string', true ) );

           
// Set additional order fields
           
$orderby_1 = param( 'orderby_1', 'string', '' );
            if( empty(
$orderby_1 ) )
            {
// Delete if first additional field is not defined
               
$this->delete_setting( 'orderby_1' );
               
$this->delete_setting( 'orderdir_1' );
               
$this->delete_setting( 'orderby_2' );
               
$this->delete_setting( 'orderdir_2' );
            }
            else
            {
               
$this->set_setting( 'orderby_1', $orderby_1 );
               
$this->set_setting( 'orderdir_1', param( 'orderdir_1', 'string', '' ) );
               
$this->set_setting( 'orderby_2', param( 'orderby_2', 'string', '' ) );
               
$this->set_setting( 'orderdir_2', param( 'orderdir_2', 'string', '' ) );
            }

           
$postlist_enable = param( 'postlist_enable', 'integer', 0 );
           
$this->set_setting( 'postlist_enable', $postlist_enable );

           
$disp_featured_above_list = param( 'disp_featured_above_list', 'integer', 0 );
           
$this->set_setting( 'disp_featured_above_list', $disp_featured_above_list );

           
// Show post types:
           
$checked_item_types = param( 'show_post_types', 'array:integer', array() );
           
$unchecked_item_types = array_diff( $this->get_enabled_item_types( 'post' ), $checked_item_types );
           
// Store only unchecked item types in order to keep all new item types enabled by default:
           
$this->set_setting( 'show_post_types', implode( ',', $unchecked_item_types ) );

           
// Front office statuses
           
$this->load_inskin_statuses( 'post' );

           
// Time frame
           
$this->set_setting( 'timestamp_min', param( 'timestamp_min', 'string', '' ) );
           
$this->set_setting( 'timestamp_min_duration', param_duration( 'timestamp_min_duration' ) );
           
$this->set_setting( 'timestamp_max', param( 'timestamp_max', 'string', '' ) );
           
$this->set_setting( 'timestamp_max_duration', param_duration( 'timestamp_max_duration' ) );

           
// call modules update_collection_features on this blog
           
modules_call_method( 'update_collection_features', array( 'edited_Blog' => & $this ) );

           
// load post moderation statuses
           
$moderation_statuses = get_visibility_statuses( 'moderation' );
           
$post_moderation_statuses = array();
            foreach(
$moderation_statuses as $status )
            {
                if(
param( 'post_notif_'.$status, 'integer', 0 ) )
                {
                   
$post_moderation_statuses[] = $status;
                }
            }
           
$this->set_setting( 'post_moderation_statuses', implode( ',', $post_moderation_statuses ) );

           
// If notifications are temporarily disabled, no subscription options should be lost when user saves.
            // When notification are turned back on, the subscriptions will resume as they were before.
           
if( $notifications_mode != 'off' )
            {
               
// Subscriptions:
               
$this->set_setting( 'allow_subscriptions', param( 'allow_subscriptions', 'integer', 0 ) );
               
$this->set_setting( 'allow_item_subscriptions', param( 'allow_item_subscriptions', 'integer', 0 ) );
               
$this->set_setting( 'allow_item_mod_subscriptions', param( 'allow_item_mod_subscriptions', 'integer', 0 ) );
            }

           
// Voting options:
           
$this->set_setting( 'voting_positive', param( 'voting_positive', 'integer', 0 ) );
           
$this->set_setting( 'voting_neutral', param( 'voting_neutral', 'integer', 0 ) );
           
$this->set_setting( 'voting_negative', param( 'voting_negative', 'integer', 0 ) );
        }

        if(
in_array( 'comments', $groups ) )
        {
// we want to load the comments settings:
            // load moderation statuses
           
$moderation_statuses = get_visibility_statuses( 'moderation' );
           
$blog_moderation_statuses = array();
            foreach(
$moderation_statuses as $status )
            {
                if(
param( 'notif_'.$status, 'integer', 0 ) )
                {
                   
$blog_moderation_statuses[] = $status;
                }
            }
           
$this->set_setting( 'moderation_statuses', implode( ',', $blog_moderation_statuses ) );

           
$this->set_setting( 'comment_quick_moderation',  param( 'comment_quick_moderation', 'string', 'expire' ) );

           
// If notifications are temporarily disabled, no subscription options should be lost when user saves.
            // When notification are turned back on, the subscriptions will resume as they were before.
           
if( $notifications_mode != 'off' )
            {
               
// Subscriptions:
               
$this->set_setting( 'allow_comment_subscriptions', param( 'allow_comment_subscriptions', 'integer', 0 ) );
               
$this->set_setting( 'allow_item_subscriptions', param( 'allow_item_subscriptions', 'integer', 0 ) );
               
$this->set_setting( 'allow_anon_subscriptions', param( 'allow_anon_subscriptions', 'integer', 0 ) );
               
$this->set_setting( 'default_anon_comment_notify', param( 'default_anon_comment_notify', 'integer', 0 ) );
               
$this->set_setting( 'anon_notification_email_limit', param( 'anon_notification_email_limit', 'integer', 0 ) );
            }

           
$this->set_setting( 'comments_detect_email', param( 'comments_detect_email', 'integer', 0 ) );
           
$this->set_setting( 'comments_register', param( 'comments_register', 'integer', 0 ) );
           
$this->set_setting( 'meta_comments_frontoffice', param( 'meta_comments_frontoffice', 'integer', 0 ) );
        }

        if(
in_array( 'contact', $groups ) )
        {    
// we want to load the contact form settings:
           
$this->set_setting( 'msgform_title', param( 'msgform_title', 'string' ) );
           
$this->set_setting( 'msgform_display_recipient', param( 'msgform_display_recipient', 'integer', 0 ) );
           
$this->set_setting( 'msgform_recipient_label', param( 'msgform_recipient_label', 'string' ) );
           
$this->set_setting( 'msgform_display_avatar', param( 'msgform_display_avatar', 'integer', 0 ) );
           
$this->set_setting( 'msgform_avatar_size', param( 'msgform_avatar_size', 'string' ) );
           
$this->set_setting( 'msgform_user_name', param( 'msgform_user_name', 'string' ) );
           
$this->set_setting( 'msgform_require_name', param( 'msgform_require_name', 'integer', 0 ) );
           
$this->set_setting( 'msgform_subject_list', param( 'msgform_subject_list', 'text' ) );
           
$this->set_setting( 'msgform_display_subject', param( 'msgform_display_subject', 'integer', 0 ) );
           
$this->set_setting( 'msgform_require_subject', param( 'msgform_require_subject', 'integer', 0 ) );
           
$msgform_additional_fields = param( 'msgform_additional_fields', 'array:integer', array() );
           
$this->set_setting( 'msgform_additional_fields', implode( ',', $msgform_additional_fields ) );
           
$this->set_setting( 'msgform_contact_method', param( 'msgform_contact_method', 'integer', 0 ) );
           
$this->set_setting( 'msgform_display_message', param( 'msgform_display_message', 'integer', 0 ) );
            if(
get_param( 'msgform_display_message' ) )
            {    
// Update these two fields only if message is allowed:
               
$this->set_setting( 'msgform_require_message', param( 'msgform_require_message', 'integer', 0 ) );
               
$this->set_setting( 'msgform_message_label', param( 'msgform_message_label', 'string' ) );
            }
        }

        if(
in_array( 'userdir', $groups ) )
        {
// we want to load the user directory settings:
           
$this->set_setting( 'userdir_enable', param( 'userdir_enable', 'integer', 0 ) );
           
$this->set_setting( 'userdir_filter_restrict_to_members', param( 'userdir_filter_restrict_to_members', 'integer', 0 ) );
           
$this->set_setting( 'userdir_filter_name', param( 'userdir_filter_name', 'integer', 0 ) );
           
$this->set_setting( 'userdir_filter_email', param( 'userdir_filter_email', 'integer', 0 ) );
           
$this->set_setting( 'userdir_filter_country', param( 'userdir_filter_country', 'integer', 0 ) );
           
$this->set_setting( 'userdir_filter_region', param( 'userdir_filter_region', 'integer', 0 ) );
           
$this->set_setting( 'userdir_filter_subregion', param( 'userdir_filter_subregion', 'integer', 0 ) );
           
$this->set_setting( 'userdir_filter_city', param( 'userdir_filter_city', 'integer', 0 ) );
           
$this->set_setting( 'userdir_filter_age_group', param( 'userdir_filter_age_group', 'integer', 0 ) );

           
$this->set_setting( 'userdir_picture', param( 'userdir_picture', 'integer', 0 ) );
           
$this->set_setting( 'image_size_user_list', param( 'image_size_user_list', 'string' ) );

           
$this->set_setting( 'userdir_login', param( 'userdir_login', 'integer', 0 ) );
           
$this->set_setting( 'userdir_firstname', param( 'userdir_firstname', 'integer', 0 ) );
           
$this->set_setting( 'userdir_lastname', param( 'userdir_lastname', 'integer', 0 ) );
           
$this->set_setting( 'userdir_nickname', param( 'userdir_nickname', 'integer', 0 ) );
           
$this->set_setting( 'userdir_fullname', param( 'userdir_fullname', 'integer', 0 ) );

           
$this->set_setting( 'userdir_country', param( 'userdir_country', 'integer', 0 ) );
           
$this->set_setting( 'userdir_country_type', param( 'userdir_country_type', 'string' ) );
           
$this->set_setting( 'userdir_region', param( 'userdir_region', 'integer', 0 ) );
           
$this->set_setting( 'userdir_subregion', param( 'userdir_subregion', 'integer', 0 ) );
           
$this->set_setting( 'userdir_city', param( 'userdir_city', 'integer', 0 ) );

           
$this->set_setting( 'userdir_phone', param( 'userdir_phone', 'integer', 0 ) );
           
$this->set_setting( 'userdir_soclinks', param( 'userdir_soclinks', 'integer', 0 ) );
           
$this->set_setting( 'userdir_lastseen', param( 'userdir_lastseen', 'integer', 0 ) );
           
$this->set_setting( 'userdir_lastseen_view', param( 'userdir_lastseen_view', 'string' ) );
           
$this->set_setting( 'userdir_lastseen_cheat', param( 'userdir_lastseen_cheat', 'integer', 0 ) );
        }

        if(
in_array( 'search', $groups ) )
        {
// we want to load the search settings:

            // Search results:
           
param_integer_range( 'search_per_page', 1, 9999, T_('Number of search results per page must be between %d and %d.') );
           
$this->set_setting( 'search_enable', param( 'search_enable', 'integer', 0 ) );
           
$this->set_setting( 'search_per_page', get_param( 'search_per_page' ) );
           
$this->set_setting( 'search_sort_by', param( 'search_sort_by', 'string' ) );
           
$this->set_setting( 'search_include_cats', param( 'search_include_cats', 'integer', 0 ) );
           
$this->set_setting( 'search_include_posts', param( 'search_include_posts', 'integer', 0 ) );
           
$this->set_setting( 'search_include_cmnts', param( 'search_include_cmnts', 'integer', 0 ) );
           
$this->set_setting( 'search_include_metas', param( 'search_include_metas', 'integer', 0 ) );
           
$this->set_setting( 'search_include_tags', param( 'search_include_tags', 'integer', 0 ) );
           
$this->set_setting( 'search_include_files', param( 'search_include_files', 'integer', 0 ) );
           
// Scoring for posts:
           
$this->set_setting( 'search_score_post_title', param( 'search_score_post_title', 'integer', 0 ) );
           
$this->set_setting( 'search_score_post_content', param( 'search_score_post_content', 'integer', 0 ) );
           
$this->set_setting( 'search_score_post_tags', param( 'search_score_post_tags', 'integer', 0 ) );
           
$this->set_setting( 'search_score_post_excerpt', param( 'search_score_post_excerpt', 'integer', 0 ) );
           
$this->set_setting( 'search_score_post_titletag', param( 'search_score_post_titletag', 'integer', 0 ) );
           
$this->set_setting( 'search_score_post_metakeywords', param( 'search_score_post_metakeywords', 'integer', 0 ) );
           
$this->set_setting( 'search_score_post_author', param( 'search_score_post_author', 'integer', 0 ) );
           
$this->set_setting( 'search_score_post_date_future', param( 'search_score_post_date_future', 'integer', 0 ) );
           
$this->set_setting( 'search_score_post_date_moremonth', param( 'search_score_post_date_moremonth', 'integer', 0 ) );
           
$this->set_setting( 'search_score_post_date_lastmonth', param( 'search_score_post_date_lastmonth', 'integer', 0 ) );
           
$this->set_setting( 'search_score_post_date_twoweeks', param( 'search_score_post_date_twoweeks', 'integer', 0 ) );
           
$this->set_setting( 'search_score_post_date_lastweek', param( 'search_score_post_date_lastweek', 'integer', 0 ) );
           
// Scoring for comments:
           
$this->set_setting( 'search_score_cmnt_post_title', param( 'search_score_cmnt_post_title', 'integer', 0 ) );
           
$this->set_setting( 'search_score_cmnt_content', param( 'search_score_cmnt_content', 'integer', 0 ) );
           
$this->set_setting( 'search_score_cmnt_author', param( 'search_score_cmnt_author', 'integer', 0 ) );
           
$this->set_setting( 'search_score_cmnt_date_future', param( 'search_score_cmnt_date_future', 'integer', 0 ) );
           
$this->set_setting( 'search_score_cmnt_date_moremonth', param( 'search_score_cmnt_date_moremonth', 'integer', 0 ) );
           
$this->set_setting( 'search_score_cmnt_date_lastmonth', param( 'search_score_cmnt_date_lastmonth', 'integer', 0 ) );
           
$this->set_setting( 'search_score_cmnt_date_twoweeks', param( 'search_score_cmnt_date_twoweeks', 'integer', 0 ) );
           
$this->set_setting( 'search_score_cmnt_date_lastweek', param( 'search_score_cmnt_date_lastweek', 'integer', 0 ) );
           
// Scoring for files:
           
$this->set_setting( 'search_score_file_name', param( 'search_score_file_name', 'integer', 0 ) );
           
$this->set_setting( 'search_score_file_path', param( 'search_score_file_path', 'integer', 0 ) );
           
$this->set_setting( 'search_score_file_title', param( 'search_score_file_title', 'integer', 0 ) );
           
$this->set_setting( 'search_score_file_alt', param( 'search_score_file_alt', 'integer', 0 ) );
           
$this->set_setting( 'search_score_file_description', param( 'search_score_file_description', 'integer', 0 ) );
           
// Scoring for categories:
           
$this->set_setting( 'search_score_cat_name', param( 'search_score_cat_name', 'integer', 0 ) );
           
$this->set_setting( 'search_score_cat_desc', param( 'search_score_cat_desc', 'integer', 0 ) );
           
// Scoring for tags:
           
$this->set_setting( 'search_score_tag_name', param( 'search_score_tag_name', 'integer', 0 ) );

           
// Quick Templates for search results:
           
$this->set_setting( 'search_result_template_item', param( 'search_result_template_item', 'string' ) );
           
$this->set_setting( 'search_result_template_comment', param( 'search_result_template_comment', 'string' ) );
           
$this->set_setting( 'search_result_template_meta', param( 'search_result_template_meta', 'string' ) );
           
$this->set_setting( 'search_result_template_file', param( 'search_result_template_file', 'string' ) );
           
$this->set_setting( 'search_result_template_category', param( 'search_result_template_category', 'string' ) );
           
$this->set_setting( 'search_result_template_tag', param( 'search_result_template_tag', 'string' ) );
        }

        if(
in_array( 'other', $groups ) )
        {
// we want to load the other settings:

            // Latest comments :
           
param_integer_range( 'latest_comments_num', 1, 9999, T_('Number of shown comments must be between %d and %d.') );
           
$this->set_setting( 'latest_comments_num', get_param( 'latest_comments_num' ) );

           
// Messaging pages:
           
$this->set_setting( 'image_size_messaging', param( 'image_size_messaging', 'string' ) );

           
// Archive pages:
           
$this->set_setting( 'archive_mode', param( 'archive_mode', 'string', true ) );
        }

        if(
in_array( 'popup', $groups ) )
        {
// we want to load the popups settings:

            // Marketing Popup:
           
$this->set_setting( 'marketing_popup_using', param( 'marketing_popup_using', 'string' ) );
           
$this->set_setting( 'marketing_popup_animation', param( 'marketing_popup_animation', 'string' ) );
           
$container_disps = array( 'front', 'posts', 'single', 'page', 'catdir' );
            foreach(
$container_disps as $container_disp )
            {
               
$this->set_setting( 'marketing_popup_container_'.$container_disp, param( 'marketing_popup_container_'.$container_disp, 'string' ) );
            }
           
$this->set_setting( 'marketing_popup_container_other_disps', param( 'marketing_popup_container_other_disps', 'string' ) );
           
$this->set_setting( 'marketing_popup_show_repeat', param( 'marketing_popup_show_repeat', 'integer', 0 ) );
           
$this->set_setting( 'marketing_popup_show_frequency', param( 'marketing_popup_show_frequency', 'string' ) );
           
$this->set_setting( 'marketing_popup_show_period_val', param( 'marketing_popup_show_period_val', 'integer', NULL ), true );
           
$this->set_setting( 'marketing_popup_show_period_unit', param( 'marketing_popup_show_period_unit', 'string' ) );
        }

        if(
in_array( 'more', $groups ) )
        {
// we want to load more settings:

            // Tracking:
           
$this->set_setting( 'track_unread_content', param( 'track_unread_content', 'integer', 0 ) );

           
// If notifications are temporarily disabled, no subscription options should be lost when user saves.
            // When notification are turned back on, the subscriptions will resume as they were before.
           
if( $notifications_mode != 'off' )
            {
               
// Subscriptions:
               
$this->set_setting( 'allow_subscriptions', param( 'allow_subscriptions', 'integer', 0 ) );
               
$this->set_setting( 'opt_out_subscription', param( 'opt_out_subscription', 'integer', 0 ) );
               
$this->set_setting( 'allow_comment_subscriptions', param( 'allow_comment_subscriptions', 'integer', 0 ) );
               
$this->set_setting( 'opt_out_comment_subscription', param( 'opt_out_comment_subscription', 'integer', 0 ) );
               
$this->set_setting( 'allow_item_subscriptions', param( 'allow_item_subscriptions', 'integer', 0 ) );
               
$this->set_setting( 'opt_out_item_subscription', param( 'opt_out_item_subscription', 'integer', 0 ) );
               
$this->set_setting( 'allow_item_mod_subscriptions', param( 'allow_item_mod_subscriptions', 'integer', 0 ) );
               
$this->set_setting( 'opt_out_item_mod_subscription', param( 'opt_out_item_mod_subscription', 'integer', 0 ) );
            }

           
// Sitemaps:
           
$this->set_setting( 'enable_sitemaps', param( 'enable_sitemaps', 'integer', 0 ) );
        }

        if(
param( 'allow_comments', 'string', NULL ) !== NULL )
        {
// Feedback options:
           
$this->set_setting( 'allow_comments', param( 'allow_comments', 'string', 'any' ) );
           
$this->set_setting( 'allow_view_comments', param( 'allow_view_comments', 'string', 'any' ) );
           
$new_feedback_status = param( 'new_feedback_status', 'string', 'draft' );
            if(
$new_feedback_status != $this->get_setting( 'new_feedback_status' ) && ( $new_feedback_status != 'published' || check_user_perm( 'blog_admin', 'edit', false, $this->ID ) ) )
            {
// Only admin can set this setting to 'Public'
               
$this->set_setting( 'new_feedback_status', $new_feedback_status );
            }
           
$this->set_setting( 'comment_form_msg', param( 'comment_form_msg', 'text' ) );
           
$this->set_setting( 'require_anon_name', param( 'require_anon_name', 'string', '0' ) );
           
$this->set_setting( 'require_anon_email', param( 'require_anon_email', 'string', '0' ) );
           
$this->set_setting( 'allow_anon_url', param( 'allow_anon_url', 'string', '0' ) );
           
$this->set_setting( 'comment_maxlen', param( 'comment_maxlen', 'integer', '' ) );
           
$this->set_setting( 'allow_html_comment', param( 'allow_html_comment', 'string', '0' ) );
           
$this->set_setting( 'allow_attachments', param( 'allow_attachments', 'string', 'registered' ) );
           
$this->set_setting( 'max_attachments', param( 'max_attachments', 'integer', '' ) );
           
$this->set_setting( 'autocomplete_usernames', param( 'autocomplete_usernames', 'integer', '' ) );
           
$this->set_setting( 'display_rating_summary', param( 'display_rating_summary', 'string', '0' ) );
           
$this->set_setting( 'allow_rating_items', param( 'allow_rating_items', 'string', 'never' ) );
           
$this->set_setting( 'rating_question', param( 'rating_question', 'text' ) );
           
$this->set_setting( 'allow_rating_comment_helpfulness', param( 'allow_rating_comment_helpfulness', 'string', '0' ) );
           
$blog_allowtrackbacks = param( 'blog_allowtrackbacks', 'integer', 0 );
            if(
$blog_allowtrackbacks != $this->get( 'allowtrackbacks' ) && ( $blog_allowtrackbacks == 0 || check_user_perm( 'blog_admin', 'edit', false, $this->ID ) ) )
            {
// Only admin can turn ON this setting
               
$this->set( 'allowtrackbacks', $blog_allowtrackbacks );
            }
           
$blog_webmentions = param( 'blog_webmentions', 'integer', 0 );
            if(
$blog_webmentions != $this->get_setting( 'webmentions' ) && ( $blog_webmentions == 0 || check_user_perm( 'blog_admin', 'edit', false, $this->ID ) ) )
            {    
// Only admin can turn ON this setting
               
$this->set_setting( 'webmentions', $blog_webmentions );
            }
           
$this->set_setting( 'comments_orderdir', param( 'comments_orderdir', '/^(?:ASC|DESC)$/', 'ASC' ) );

           
// call modules update_collection_comments on this blog
           
modules_call_method( 'update_collection_comments', array( 'edited_Blog' => & $this ) );

           
$threaded_comments = param( 'threaded_comments', 'integer', 0 );
           
$this->set_setting( 'threaded_comments', $threaded_comments );
           
$this->set_setting( 'paged_comments', $threaded_comments ? 0 : param( 'paged_comments', 'integer', 0 ) );
           
param_integer_range( 'comments_per_page', 1, 9999, T_('Comments per page must be between %d and %d.') );
           
$this->set_setting( 'comments_per_page', get_param( 'comments_per_page' ) );
           
$this->set_setting( 'comments_avatars', param( 'comments_avatars', 'integer', 0 ) );
           
$this->set_setting( 'comments_latest', param( 'comments_latest', 'integer', 0 ) );

           
// load blog front office comment statuses
           
$this->load_inskin_statuses( 'comment' );
        }


        if(
in_array( 'seo', $groups ) )
        {
// we want to load the workflow checkboxes:
           
$this->set_setting( 'canonical_homepage', param( 'canonical_homepage', 'integer', 0 ) );
           
$this->set_setting( 'self_canonical_homepage', param( 'self_canonical_homepage', 'integer', 0 ) );
           
$this->set_setting( 'relcanonical_homepage', param( 'relcanonical_homepage', 'integer', 0 ) );
           
$this->set_setting( 'canonical_posts', param( 'canonical_posts', 'integer', 0 ) );
           
$this->set_setting( 'self_canonical_posts', param( 'self_canonical_posts', 'integer', 0 ) );
           
$this->set_setting( 'relcanonical_posts', param( 'relcanonical_posts', 'integer', 0 ) );
           
$this->set_setting( 'canonical_item_urls', param( 'canonical_item_urls', 'integer', 0 ) );
            if( !
$this->get_setting( 'canonical_item_urls' ) )
            {    
// When "301 redirect to canonical URL" is disabled then we always must NOT do 301 redirect cross-posted Items:
               
$this->set_setting( 'allow_crosspost_urls', 1 );
            }
            else
            {
               
$this->set_setting( 'allow_crosspost_urls', param( 'allow_crosspost_urls', 'integer', 0 ) );
            }
           
$this->set_setting( 'self_canonical_item_urls', param( 'self_canonical_item_urls', 'integer', 0 ) );
           
$this->set_setting( 'relcanonical_item_urls', param( 'relcanonical_item_urls', 'integer', 0 ) );
           
$this->set_setting( 'canonical_archive_urls', param( 'canonical_archive_urls', 'integer', 0 ) );
           
$this->set_setting( 'self_canonical_archive_urls', param( 'self_canonical_archive_urls', 'integer', 0 ) );
           
$this->set_setting( 'relcanonical_archive_urls', param( 'relcanonical_archive_urls', 'integer', 0 ) );
           
$this->set_setting( 'canonical_cat_urls', param( 'canonical_cat_urls', 'integer', 0 ) );
           
$this->set_setting( 'self_canonical_cat_urls', param( 'self_canonical_cat_urls', 'integer', 0 ) );
           
$this->set_setting( 'relcanonical_cat_urls', param( 'relcanonical_cat_urls', 'integer', 0 ) );
           
$this->set_setting( 'canonical_tag_urls', param( 'canonical_tag_urls', 'integer', 0 ) );
           
$this->set_setting( 'self_canonical_tag_urls', param( 'self_canonical_tag_urls', 'integer', 0 ) );
           
$this->set_setting( 'relcanonical_tag_urls', param( 'relcanonical_tag_urls', 'integer', 0 ) );
           
$this->set_setting( 'default_noindex', param( 'default_noindex', 'integer', 0 ) );
           
$this->set_setting( 'posts_firstpage_noindex', param( 'posts_firstpage_noindex', 'integer', 0 ) );
           
$this->set_setting( 'paged_noindex', param( 'paged_noindex', 'integer', 0 ) );
           
$this->set_setting( 'paged_intro_noindex', param( 'paged_intro_noindex', 'integer', 0 ) );
           
$this->set_setting( 'paged_nofollowto', param( 'paged_nofollowto', 'integer', 0 ) );
           
$this->set_setting( 'single_noindex', param( 'single_noindex', 'integer', 0 ) );
           
$this->set_setting( 'archive_noindex', param( 'archive_noindex', 'integer', 0 ) );
           
$this->set_setting( 'archive_nofollowto', param( 'archive_nofollowto', 'integer', 0 ) );
           
$this->set_setting( 'chapter_noindex', param( 'chapter_noindex', 'integer', 0 ) );
           
$this->set_setting( 'chapter_intro_noindex', param( 'chapter_intro_noindex', 'integer', 0 ) );
           
$this->set_setting( 'tag_noindex', param( 'tag_noindex', 'integer', 0 ) );
           
$this->set_setting( 'tag_intro_noindex', param( 'tag_intro_noindex', 'integer', 0 ) );
           
$this->set_setting( 'filtered_noindex', param( 'filtered_noindex', 'integer', 0 ) );
           
$this->set_setting( 'filtered_intro_noindex', param( 'filtered_intro_noindex', 'integer', 0 ) );
           
$this->set_setting( 'arcdir_noindex', param( 'arcdir_noindex', 'integer', 0 ) );
           
$this->set_setting( 'catdir_noindex', param( 'catdir_noindex', 'integer', 0 ) );
           
$this->set_setting( 'feedback-popup_noindex', param( 'feedback-popup_noindex', 'integer', 0 ) );
           
$this->set_setting( 'msgform_noindex', param( 'msgform_noindex', 'integer', 0 ) );
           
$this->set_setting( 'msgform_nofollowto', param( 'msgform_nofollowto', 'integer', 0 ) );
           
$this->set_setting( 'msgform_redirect_slug', param( 'msgform_redirect_slug', 'string' ) );
           
$this->set_setting( 'special_noindex', param( 'special_noindex', 'integer', 0 ) );
           
$this->set_setting( 'title_link_type', param( 'title_link_type', 'string', '' ) );
           
$this->set_setting( 'permalinks', param( 'permalinks', 'string', '' ) );
           
$this->set_setting( '404_response', param( '404_response', 'string', '' ) );
           
$this->set_setting( 'help_link', param( 'help_link', 'string', '' ) );
           
$this->set_setting( 'excerpts_meta_description', param( 'excerpts_meta_description', 'integer', 0 ) );
           
$this->set_setting( 'categories_meta_description', param( 'categories_meta_description', 'integer', 0 ) );
           
$this->set_setting( 'tags_meta_keywords', param( 'tags_meta_keywords', 'integer', 0 ) );
           
$this->set_setting( 'tags_open_graph', param( 'tags_open_graph', 'integer', 0 ) );
           
$this->set_setting( 'tags_twitter_card', param( 'tags_twitter_card', 'integer', 0 ) );
           
$this->set_setting( 'tags_structured_data', param( 'tags_structured_data', 'integer', 0 ) );
           
$this->set_setting( 'download_noindex', param( 'download_noindex', 'integer', 0 ) );
           
$this->set_setting( 'download_nofollowto', param( 'download_nofollowto', 'integer', 0 ) );
           
$this->set_setting( 'canonical_user_urls', param( 'canonical_user_urls', 'integer', 0 ) );
        }

        if(
in_array( 'credits', $groups ) )
        {    
// We want to load the software credits settings:
           
if( is_pro() )
            {    
// Allow to remove "Powered by b2evolution" logos only for PRO version:
               
$this->set_setting( 'powered_by_logos', param( 'powered_by_logos', 'integer', 0 ) );
            }
           
param_integer_range( 'max_footer_credits', 0, 3, T_('Max credits must be between %d and %d.') );
           
$this->set_setting( 'max_footer_credits', get_param( 'max_footer_credits' ) );
        }

       
/*
         * ADVANCED ADMIN SETTINGS
         */
       
if( check_user_perm( 'blog_admin', 'edit', false, $this->ID ) ||
            (
$this->ID == 0 && check_user_perm( 'blogs', 'create', false, $this->sec_ID ) ) )
        {    
// We have permission to edit advanced admin settings,
            // OR user is creating/coping new collection in section where he has an access:
           
if( ( $blog_urlname = param( 'blog_urlname', 'string', NULL ) ) !== NULL )
            {    
// check urlname
               
if( param_check_not_empty( 'blog_urlname', T_('You must provide an URL collection name!') ) )
                {
                    if( !
preg_match( '|^[A-Za-z0-9\-]+$|', $blog_urlname ) )
                    {
                       
param_error( 'blog_urlname', sprintf( T_('The url name %s is invalid.'), "&laquo;$blog_urlname&raquo;" ) );
                       
$blog_urlname = NULL;
                    }

                    if( isset(
$blog_urlname) && $DB->get_var( 'SELECT COUNT(*)
                                                            FROM T_blogs
                                                            WHERE blog_urlname = '
.$DB->quote($blog_urlname).'
                                                            AND blog_ID <> '
.$this->ID
                                                       
) )
                    {
// urlname is already in use
                       
param_error( 'blog_urlname', sprintf( T_('The URL name %s is already in use by another collection. Please choose another name.'), "&laquo;$blog_urlname&raquo;" ) );
                       
$blog_urlname = NULL;
                    }

                    if( isset(
$blog_urlname ) )
                    {
// Set new urlname and save old media dir in order to rename folder to new
                       
$old_media_dir = $this->get_media_dir( false );
                       
$this->set_from_Request( 'urlname' );
                    }
                }
            }
        }
        if(
check_user_perm( 'blog_admin', 'edit', false, $this->ID ) )
        {    
// We have permission to edit advanced admin settings:

           
if( in_array( 'cache', $groups ) )
            {
// we want to load the cache params:
               
$this->set_setting( 'ajax_form_enabled', param( 'ajax_form_enabled', 'integer', 0 ) );
               
$this->set_setting( 'ajax_form_loggedin_enabled', param( 'ajax_form_loggedin_enabled', 'integer', 0 ) );
               
$this->set_setting( 'cache_enabled_widgets', param( 'cache_enabled_widgets', 'integer', 0 ) );
            }

            if(
in_array( 'styles', $groups ) )
            {
// we want to load the styles params:
               
$display_alt_skin_referer = param( 'display_alt_skin_referer', 'integer', 0 );
               
$this->set_setting( 'display_alt_skin_referer', $display_alt_skin_referer );
               
$display_alt_skin_referer_url = param( 'display_alt_skin_referer_url', 'string', NULL );
                if(
$display_alt_skin_referer )
                {    
// Check for not empty value only when setting is enabled:
                   
param_check_not_empty( 'display_alt_skin_referer_url', T_('The Referer URL cannot be empty to display Alt skin automatically.') );
                }
               
$this->set_setting( 'display_alt_skin_referer_url', $display_alt_skin_referer_url );

               
$this->set( 'allowblogcss', param( 'blog_allowblogcss', 'integer', 0 ) );
               
$this->set( 'allowusercss', param( 'blog_allowusercss', 'integer', 0 ) );
            }

            if(
in_array( 'login', $groups ) )
            {
// we want to load the login params:
               
if( ! get_setting_Blog( 'login_blog_ID', $this ) )
                {
// Update this only when no blog is defined for login/registration
                   
$this->set_setting( 'in_skin_login', param( 'in_skin_login', 'integer', 0 ) );
                }
               
$this->set_setting( 'in_skin_editing', param( 'in_skin_editing', 'integer', 0 ) );
               
$this->set_setting( 'in_skin_change_proposal', param( 'in_skin_change_proposal', 'integer', 0 ) );
            }

            if(
param( 'blog_head_includes', 'html', NULL ) !== NULL )
            {    
// HTML header includes:
               
param_check_html( 'blog_head_includes', T_('Invalid Custom meta tag/css section.').' '.sprintf( T_('You can loosen this restriction in the <a %s>Group settings</a>.'), 'href='.$admin_url.'?ctrl=groups&amp;action=edit&amp;grp_ID='.$current_User->grp_ID ), '#', 'head_extension' );
               
$this->set_setting( 'head_includes', get_param( 'blog_head_includes' ) );
            }

            if(
param( 'blog_body_includes', 'html', NULL ) !== NULL )
            {
// HTML body includes:
               
param_check_html( 'blog_body_includes', T_('Invalid Custom javascript section.').' '.sprintf( T_('You can loosen this restriction in the <a %s>Group settings</a>.'), 'href='.$admin_url.'?ctrl=groups&amp;action=edit&amp;grp_ID='.$current_User->grp_ID ), "#", 'body_extension' );
               
$this->set_setting( 'body_includes', get_param( 'blog_body_includes' ) );
            }

            if(
param( 'blog_footer_includes', 'html', NULL ) !== NULL )
            {    
// HTML footer includes:
               
param_check_html( 'blog_footer_includes', T_('Invalid Custom javascript section.').' '.sprintf( T_('You can loosen this restriction in the <a %s>Group settings</a>.'), 'href='.$admin_url.'?ctrl=groups&amp;action=edit&amp;grp_ID='.$current_User->grp_ID ), "#", 'footer_extension' );
               
$this->set_setting( 'footer_includes', get_param( 'blog_footer_includes' ) );
            }

            if(
param( 'owner_login', 'string', NULL ) !== NULL )
            {
// Permissions:
               
$UserCache = & get_UserCache();
               
$owner_User = & $UserCache->get_by_login( get_param('owner_login') );
                if( empty(
$owner_User ) )
                {
                   
param_error( 'owner_login', sprintf( T_('User &laquo;%s&raquo; does not exist!'), get_param('owner_login') ) );
                }
                else
                {
                   
$this->set( 'owner_user_ID', $owner_User->ID );
                   
$this->owner_User = & $owner_User;
                }
            }


            if( (
$access_type = param( 'blog_access_type', 'string', NULL )) !== NULL )
            {
// Blog URL parameters:
                // Note: We must avoid to set an invalid url, because the new blog url will be displayed in the evobar even if it was not saved
               
$allow_new_access_type = true;

                if(
$access_type == 'absolute' )
                {
                   
$blog_siteurl = param( 'blog_siteurl_absolute', 'url', true );
                    if(
preg_match( '#^https?://[^/]+/.*#', $blog_siteurl, $matches ) )
                    {
// It looks like valid absolute URL, so we may update the blog siteurl
                       
$this->set( 'siteurl', $blog_siteurl );
                    }
                    else
                    {
// It is not valid absolute URL, don't update the blog 'siteurl' to avoid errors
                       
$allow_new_access_type = false; // If site url is not updated do not allow access_type update either
                       
$Messages->add( T_('Collection base URL').': '.sprintf( T_('%s is an invalid absolute URL'), '&laquo;'.htmlspecialchars( $blog_siteurl ).'&raquo;' )
                            .
'. '.T_('You must provide an absolute URL (starting with <code>http://</code> or <code>https://</code>) and it must contain at least one \'/\' sign after the domain name!'), 'error' );
                    }
                }
                elseif(
$access_type == 'relative' )
                {
// relative siteurl
                   
$blog_siteurl = param( 'blog_siteurl_relative', 'string', true );
                    if(
preg_match( '#^https?://#', $blog_siteurl ) )
                    {
                       
$Messages->add( T_('Blog Folder URL').': '
                                                       
.T_('You must provide a relative URL (without <code>http://</code> or <code>https://</code>)!'), 'error' );
                    }
                   
$this->set( 'siteurl', $blog_siteurl );
                }
                else
                {
                   
$this->set( 'siteurl', '' );
                }

                if(
$allow_new_access_type )
                {
// The received siteurl value was correct, may update the access_type value
                   
$this->set( 'access_type', $access_type );
                }
            }

            if( (
$http_protocol = param( 'http_protocol', 'string', NULL ) ) !== NULL )
            {    
// SSL:
               
$this->set_setting( 'http_protocol', $http_protocol );
            }

            if( (
$url_aliases = param( 'blog_url_alias', 'array', NULL ) ) !== NULL )
            {
               
$has_error = array();
                foreach(
$url_aliases as $key => $alias )
                {
                   
$alias = trim( $alias );
                   
$url_aliases[$key] = $alias;
                    if( empty(
$alias ) )
                    {
                        unset(
$url_aliases[$key] );
                        continue;
                    }
                    if( !
preg_match( '#^https?://[^/]+/.*#', $alias, $matches ) )
                    {
// It is not valid absolute URL
                       
$Messages->add( T_('Invalid URL format').': '.sprintf( T_('%s is an invalid absolute URL'), '&laquo;'.htmlspecialchars( $alias ).'&raquo;' )
                            .
'. '.T_('You must provide an absolute URL (starting with <code>http://</code> or <code>https://</code>) and it must contain at least one \'/\' sign after the domain name!'), 'error' );
                    }
                   
$has_error = validate_url( $alias, 'http-https' );
                    if(
$has_error )
                    {
                       
param_error( 'blog_url_alias', T_('Invalid URL format.') );
                        break;
                    }

                   
$alias_wo_protocol = preg_replace( '#^https?(?=://)#', '', $alias );

                   
// Check existing URL aliases in request array
                   
foreach( $url_aliases as $sec_key => $sec_alias )
                    {
                        if(
$sec_key == $key )
                        {
                            continue;
                        }

                       
$re = "/^https?(.*)/i";
                       
$subst = "https?$1";
                       
$sec_alias = trim( $sec_alias );
                       
$r =  strlen( $alias ) - strlen( $sec_alias );
                        if(
$r < 0 )
                        {
                           
$alias = rtrim( $alias, "/\\" );
                           
$str = preg_quote( $alias );
                           
$subject = $sec_alias;
                        }
                        else
                        {
                           
$sec_alias = rtrim( $sec_alias, "/\\" );
                           
$str = preg_quote( $sec_alias );
                           
$subject = $alias;
                        }

                       
// Add trailing slash to subject
                       
if( ! preg_match( '~(^|/|\.php.?)$~i', $subject ) )
                        {
                           
$subject .= '/';
                        }

                       
$url_regex = preg_replace( $re, $str, $subst );
                       
// Remove trailing slash from url_regex
                        //die( var_dump( $url_regex ) );

                       
if( preg_match( '#^'.$url_regex.'(?=/)#', $subject, $matches ) )
                        {
                           
param_error( 'blog_url_alias', sprintf( T_('URL alias %s is already in use by another collection or contains an existing alias.'), '&laquo;'.$alias.'&raquo;' ) );
                            break;
                        }
                    }

                   
$trailing_slash = '';
                    if( !
preg_match( '~(^|/|\.php.?)$~i', $alias_wo_protocol ) )
                    {
                       
$trailing_slash = '/';
                    }

                   
// Check existing URL aliases in other collections
                   
$SQL = new SQL('Check existing URL aliases');
                   
$SQL->SELECT( 'cua_url_alias' );
                   
$SQL->FROM( 'T_coll_url_aliases' );
                   
$SQL->WHERE( '( cua_coll_ID != '.$this->ID.' )' );
                   
$SQL->WHERE_and( '( cua_url_alias IN ('.implode( ',', array( $DB->quote( 'http'.$alias_wo_protocol ), $DB->quote( 'https'.$alias_wo_protocol ) ) ).') )'
                           
.'OR '.$DB->quote( 'http'.$alias_wo_protocol ).' LIKE CONCAT( cua_url_alias, IF(cua_url_alias REGEXP "(^|/|\.php)$", "", "/"), "%" )'
                           
.'OR '. $DB->quote( 'https'.$alias_wo_protocol ).' LIKE CONCAT( cua_url_alias, IF(cua_url_alias REGEXP "(^|/|\.php)$", "", "/"), "%" )'
                           
.'OR cua_url_alias LIKE '.$DB->quote( 'http'.$alias_wo_protocol.$trailing_slash.'%' )
                            .
'OR cua_url_alias LIKE '.$DB->quote( 'https'.$alias_wo_protocol.$trailing_slash.'%' ) );

                    if(
$DB->get_var( $SQL->get() ) )
                    {
                       
param_error( 'blog_url_aliases', sprintf( T_('URL alias %s is already in use by another collection or contains an existing alias.'), '&laquo;'.$alias.'&raquo;' ) );
                        break;
                    }
                }

                if( !
param_has_error( 'blog_url_alias' ) )
                {
                   
$this->set( 'url_aliases', $url_aliases );
                }
            }

            if(
param( 'tinyurl_type', 'string', NULL ) !== NULL )
            {
// Tiny URL type:
               
if( get_param( 'tinyurl_type') == 'advanced' )
                {
                   
$tinyurl_domain = param( 'tinyurl_domain', 'string', NULL );
                    if( !
preg_match( '#^https?://[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]+/$#', $tinyurl_domain, $matches ) )
                    {
// It is not valid absolute URL
                       
$Messages->add( T_('Tiny URL').': '.sprintf( T_('Supplied URL is invalid. (%s)'), '<code>'.htmlspecialchars( $tinyurl_domain ).'</code>' ), 'error' );
                    }
                   
$this->set_setting( 'tinyurl_type', get_param( 'tinyurl_type' ) );
                   
$this->set_setting( 'tinyurl_domain', $tinyurl_domain );
                }
                else
                {
                   
$this->set_setting( 'tinyurl_type', get_param( 'tinyurl_type' ) );
                }
            }

            if(
is_pro() && in_array( 'urls', $groups ) )
            {    
// Only PRO version and on update from tab "URLs":

                // Tag source:
               
$this->set_setting( 'tinyurl_tag_source_enabled', param( 'tinyurl_tag_source_enabled', 'integer', 0 ) );
               
$this->set_setting( 'tinyurl_tag_source', param( 'tinyurl_tag_source', 'string', NULL ), true );

               
// Tag slug:
               
$this->set_setting( 'tinyurl_tag_slug_enabled', param( 'tinyurl_tag_slug_enabled', 'integer', 0 ) );
               
$this->set_setting( 'tinyurl_tag_slug', param( 'tinyurl_tag_slug', 'string', NULL ), true );

               
// Tag extra term:
               
$this->set_setting( 'tinyurl_tag_extra_term_enabled', param( 'tinyurl_tag_extra_term_enabled', 'integer', 0 ) );
               
$this->set_setting( 'tinyurl_tag_extra_term', param( 'tinyurl_tag_extra_term', 'string', NULL ), true );
            }

            if( (
param( 'cookie_domain_type', 'string', NULL ) !== NULL ) &&  check_user_perm( 'blog_admin', 'edit', false, $this->ID ) )
            {    
// Cookies:
               
$this->set_setting( 'cookie_domain_type', get_param( 'cookie_domain_type' ) );
                if(
get_param( 'cookie_domain_type' ) == 'custom' )
                {    
// Update custom cookie domain:
                   
$cookie_domain_custom = param( 'cookie_domain_custom', 'string', NULL );
                   
preg_match( '#^https?://(.+?)(:(.+?))?$#', $this->get_baseurl_root(), $coll_host );
                    if( empty(
$coll_host[1] ) || ! preg_match( '#(^|\.)'.preg_quote( preg_replace( '#^\.#i', '', $cookie_domain_custom ), '#' ).'$#i', $coll_host[1] ) )
                    {    
// Wrong cookie domain:
                       
param_error( 'cookie_domain_custom', T_('The custom cookie domain must be a parent of the collection domain.') );
                    }
                   
$this->set_setting( 'cookie_domain_custom', $cookie_domain_custom );
                }
                else
                {    
// Reset custom cookie domain:
                   
$this->set_setting( 'cookie_domain_custom', '' );
                }

               
$this->set_setting( 'cookie_path_type', param( 'cookie_path_type', 'string', NULL ) );
                if(
get_param( 'cookie_path_type' ) == 'custom' )
                {    
// Update custom cookie path:
                   
$cookie_path_custom = param( 'cookie_path_custom', 'string', NULL );
                    if( !
preg_match( '#^'.preg_quote( preg_replace( '#/$#i', '', $cookie_path_custom ), '#' ).'(/|$)#i', $this->get_basepath() ) )
                    {    
// Wrong cookie path:
                       
param_error( 'cookie_path_custom', T_('The custom cookie path must be a parent of the collection path.') );
                    }
                   
$this->set_setting( 'cookie_path_custom', $cookie_path_custom );
                }
                else
                {    
// Reset custom cookie path:
                   
$this->set_setting( 'cookie_path_custom', '' );
                }
            }

            if( (
param( 'rsc_assets_url_type', 'string', NULL ) !== NULL ) &&  check_user_perm( 'blog_admin', 'edit', false, $this->ID ) )
            {
// Assets URLs / CDN:

                // Check all assets types url settings:
               
$assets_url_data = array(
                   
'rsc_assets_url_type'     => array( 'url' => 'rsc_assets_absolute_url',     'folder' => '/rsc/' ),
                   
'media_assets_url_type'   => array( 'url' => 'media_assets_absolute_url',   'folder' => '/media/' ),
                   
'skins_assets_url_type'   => array( 'url' => 'skins_assets_absolute_url',   'folder' => '/skins/' ),
                   
'plugins_assets_url_type' => array( 'url' => 'plugins_assets_absolute_url', 'folder' => '/plugins/' ),
                   
'htsrv_assets_url_type'   => array( 'url' => 'htsrv_assets_absolute_url',   'folder' => '/htsrv/' ),
                );

                foreach(
$assets_url_data as $asset_url_type => $asset_url_data )
                {
                   
$asset_url_type_value = param( $asset_url_type, 'string', NULL );
                    if(
$asset_url_type_value === NULL )
                    {
// Skip this setting to don't save when it doesn't exist on form
                       
continue;
                    }
                   
$this->set_setting( $asset_url_type, $asset_url_type_value );

                   
$assets_absolute_url_value = param( $asset_url_data['url'], 'url', NULL );
                    if( (
get_param( $asset_url_type ) == 'absolute' ) && empty( $assets_absolute_url_value ) )
                    {
// Absolute URL cannot be empty
                       
$Messages->add( sprintf( T_('Absolute URL for %s cannot be empty!'), $asset_url_data['folder'] ) );
                    }
                    elseif( empty(
$assets_absolute_url_value ) || preg_match( '#^(https?:)?//.+/$#', $assets_absolute_url_value ) )
                    {
// It looks like valid absolute URL, so we may update it
                       
$this->set_setting( $asset_url_data['url'], $assets_absolute_url_value, true );
                    }
                    else
                    {
// It is not valid absolute URL, don't update it to avoid errors
                       
$Messages->add( sprintf( T_('Absolute URL for %s'), $asset_url_data['folder'] ).': '.sprintf( T_('%s is an invalid absolute URL'), '&laquo;'.htmlspecialchars( $assets_absolute_url_value ).'&raquo;' )
                            .
'. '.T_('You must provide an absolute URL (starting with <code>http://</code>, <code>https://</code> or <code>//</code>) and it must ending with \'/\' sign!'), 'error' );
                    }
                }

                if(
$this->get( 'access_type' ) == 'absolute' &&
                    (
$this->get_setting( 'rsc_assets_url_type' ) == 'basic' ||
                     
$this->get_setting( 'skins_assets_url_type' ) == 'basic' ||
                     
$this->get_setting( 'plugins_assets_url_type' ) == 'basic' ) )
                {    
// Display warning for such settings combination:
                   
$Messages->add( T_('WARNING: you will be loading your assets from a different domain. This may cause problems if you don\'t know exactly what you are doing. Please check the Assets URLs panel below.'), 'warning' );
                }

                if(
$this->get_setting( 'skins_assets_url_type' ) != 'relative' )
                {    
// Display warning if skins path is used as NOT relative url:
                   
if( $this->get_setting( 'rsc_assets_url_type' ) == 'relative' )
                    {    
// If rsc path is relative url:
                       
$Messages->add( sprintf( T_('WARNING: your %s and %s assets URL seem to be configured in a potentially undesirable way. Please check your Assets URLs below.'),
                           
'<code>/rsc/</code>', '<code>/skins/</code>' ), 'warning' );
                    }
                    if(
$this->get_setting( 'plugins_assets_url_type' ) == 'relative' )
                    {    
// If plugins path is relative url:
                       
$Messages->add( sprintf( T_('WARNING: your %s and %s assets URL seem to be configured in a potentially undesirable way. Please check your Assets URLs below.'),
                           
'<code>/plugins/</code>', '<code>/skins/</code>' ), 'warning' );
                    }
                }
            }


            if(
$this->type != 'main' || ( $this->type == 'main' && $this->ID > 0 ) )
            {
                if(
param( 'aggregate_coll_IDs', 'string', NULL ) !== NULL )
                {
// Aggregate list: (can be '*')
                   
$aggregate_coll_IDs = get_param( 'aggregate_coll_IDs' );

                    if(
$aggregate_coll_IDs != '*' )
                    {    
// Sanitize the string
                       
$aggregate_coll_IDs = sanitize_id_list($aggregate_coll_IDs);
                    }

                   
// fp> TODO: check perms on each aggregated blog (if changed)
                    // fp> TODO: better interface
                   
if( $aggregate_coll_IDs != '*' && !preg_match( '#^([0-9]+(,[0-9]+)*)?$#', $aggregate_coll_IDs ) )
                    {
                       
param_error( 'aggregate_coll_IDs', T_('Invalid aggregate collection ID list!') );
                    }
                   
$this->set_setting( 'aggregate_coll_IDs', $aggregate_coll_IDs );
                }
            }
            elseif(
$this->type == 'main' && $this->ID === 0 )
            {
// Only on new Home/Main collection
               
if( param( 'blog_aggregate', 'integer', 1 ) === 0 )
                {
                   
$this->set_setting( 'aggregate_coll_IDs', NULL );
                }
                else
                {
                   
$this->set_setting( 'aggregate_coll_IDs', '*' );
                }
            }


           
$media_location = param( 'blog_media_location', 'string', NULL );
            if(
$media_location !== NULL )
            {
// Media files location:
               
$old_media_dir = $this->get_media_dir( false );
               
$old_media_location = $this->get( 'media_location' );
               
$this->set_from_Request( 'media_location' );
               
$this->set_media_subdir( param( 'blog_media_subdir', 'string', '' ) );
               
$this->set_media_fullpath( param( 'blog_media_fullpath', 'string', '' ) );
               
$this->set_media_url( param( 'blog_media_url', 'url', '' ) );

               
// check params
               
switch( $this->get( 'media_location' ) )
                {
                    case
'custom': // custom path and URL
                       
global $demo_mode, $media_path;
                        if(
$this->get( 'media_fullpath' ) == '' )
                        {
                           
param_error( 'blog_media_fullpath', T_('Media dir location').': '.T_('You must provide the full path of the media directory.') );
                        }
                        if( !
preg_match( '#^https?://#', $this->get( 'media_url' ) ) )
                        {
                           
param_error( 'blog_media_url', T_('Media dir location').': '
                                                           
.T_('You must provide an absolute URL (starting with <code>http://</code> or <code>https://</code>)!') );
                        }
                        if(
$demo_mode )
                        {
                           
$canonical_fullpath = get_canonical_path($this->get('media_fullpath'));
                            if( !
$canonical_fullpath || strpos($canonical_fullpath, $media_path) !== 0 )
                            {
                               
param_error( 'blog_media_fullpath', T_('Media dir location').': in demo mode the path must be inside of $media_path.' );
                            }
                        }
                        break;

                    case
'subdir':
                        global
$media_path;
                        if(
$this->get( 'media_subdir' ) == '' )
                        {
                           
param_error( 'blog_media_subdir', T_('Media dir location').': '.T_('You must provide the media subdirectory.') );
                        }
                        else
                        {
// Test if it's below $media_path (subdir!)
                           
$canonical_path = get_canonical_path($media_path.$this->get( 'media_subdir' ));
                            if( !
$canonical_path || strpos($canonical_path, $media_path) !== 0 )
                            {
                               
param_error( 'blog_media_subdir', T_('Media dir location').': '.sprintf(T_('Invalid subdirectory &laquo;%s&raquo;.'), format_to_output($this->get('media_subdir'))) );
                            }
                            else
                            {
                               
// Validate if it's a valid directory name:
                               
$subdir = no_trailing_slash(substr($canonical_path, strlen($media_path)));
                                if(
$error = validate_dirname($subdir) )
                                {
                                   
param_error( 'blog_media_subdir', T_('Media dir location').': '.$error );
                                   
syslog_insert( sprintf( 'Invalid name is detected for folder %s', '[['.$subdir.']]' ), 'warning', 'file' );
                                }
                            }
                        }
                        break;
                }
            }

            if(
$this->ID > 0 && ! param_errors_detected() && ! empty( $old_media_dir ) )
            {
// No error were detected before and possibly the media directory path was updated, check if it can be managed
               
$this->check_media_dir_change( $old_media_dir, isset( $old_media_location ) ? $old_media_location : NULL );
            }

        }

        return !
param_errors_detected() && empty( $this->confirmation );
    }


   
/**
     * Load blog front office post/comment statuses
     *
     * @param string type = 'post' or 'comment'
     */
   
function load_inskin_statuses( $type )
    {
        if( (
$type != 'post' ) && ( $type != 'comment' ) )
        {
// Invalid type
           
debug_die( 'Invalid type to load blog inskin statuses!' );
        }

       
// Get possible front office statuses
       
$inskin_statuses = get_visibility_statuses( 'keys', array( 'deprecated', 'trash', 'redirected' ) );
       
$selected_inskin_statuses = array();
        foreach(
$inskin_statuses as $status )
        {
            if(
param( $type.'_inskin_'.$status, 'integer', 0 ) )
            {
// This status was selected
               
$selected_inskin_statuses[] = $status;
            }
        }
       
$this->set_setting( $type.'_inskin_statuses', implode( ',', $selected_inskin_statuses ) );
    }


   
/**
     * Set the media folder's subdir
     *
     * @param string the subdirectory
     */
   
function set_media_subdir( $path )
    {
       
parent::set_param( 'media_subdir', 'string', trailing_slash( $path ) );
    }


   
/**
     * Set the full path of the media folder
     *
     * @param string the full path
     */
   
function set_media_fullpath( $path )
    {
       
parent::set_param( 'media_fullpath', 'string', trailing_slash( $path ) );
    }


   
/**
     * Set the full URL of the media folder
     *
     * @param string the full URL
     */
   
function set_media_url( $url )
    {
       
parent::set_param( 'media_url', 'string', trailing_slash( $url ) );
    }


   
/**
     * Set favorite status of current_user
     *
     * @param integer User ID, leave empty for current user
     * @param integer Setting, leave empty to get favorite status
     * @return mixed Current favorite status or if set, True if the setting was changed  else false, NULL if unable to process
     */
   
function favorite( $user_ID = NULL, $setting = NULL )
    {
        global
$DB, $current_User;

        if(
is_null( $user_ID ) && $current_User )
        {
            if(
$current_User )
            {
               
$user_ID = $current_User->ID;
            }
            else
            {
                return
NULL;
            }
        }

        if(
$this->ID && $user_ID )
        {
            if(
is_null( $setting ) )
            {
// just return current favorite status
               
$fav_SQL = new SQL( 'Check if collection #'.$this->ID.' is a favorite for user #'.$user_ID );
               
$fav_SQL->SELECT( 'COUNT(*)' );
               
$fav_SQL->FROM( 'T_coll_user_favs' );
               
$fav_SQL->WHERE( 'cufv_blog_ID = '.$DB->quote( $this->ID ) );
               
$fav_SQL->WHERE_and( 'cufv_user_ID = '.$DB->quote( $user_ID ) );

                return
$DB->get_var( $fav_SQL );
            }

            if(
$setting == 1 )
            {
                return
$DB->query( 'REPLACE INTO T_coll_user_favs ( cufv_user_ID, cufv_blog_ID ) VALUES ( '.$user_ID.', '.$this->ID.' )' );
            }
            else
            {
                return
$DB->query( 'DELETE FROM T_coll_user_favs WHERE cufv_user_ID = '.$user_ID.' AND cufv_blog_ID = '.$this->ID );
            }
        }
        else
        {
            return
NULL;
        }
    }


   
/**
     * Set param value
     *
     * @param string Parameter name
     * @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 )
    {
        global
$Settings;

        switch(
$parname )
        {
            case
'ID':
            case
'allowtrackbacks':
                return
$this->set_param( $parname, 'number', $parvalue, $make_null );
                break;

            case
'url_aliases':
               
$this->url_aliases = $parvalue;
               
$this->update_url_aliases = true;
                break;

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


   
/**
     * Generate blog URL. That is the URL of the main page/home page of the blog.
     * This will not necessarily be a folder. For example, it can end in index.php?blog=4
     *
     * @param string Collection access type
     * @return string Collection full URL
     */
   
function gen_blogurl( $type = 'default' )
    {
        global
$baseprotocol, $basehost, $baseport, $baseurl, $Settings;

        if(
$type == 'default' )
        {    
// Use current access type of this collection:
           
$type = $this->get( 'access_type' );
        }

        switch(
$type )
        {
            case
'baseurl':
            case
'default':
               
// Access through index.php: match absolute URL or call default blog
               
if( ( $Settings->get('default_blog_ID') == $this->ID )
                    ||
preg_match( '#^https?://#', $this->siteurl ) )
                {
// Safety check! We only do that kind of linking if this is really the default blog...
                    // or if we call by absolute URL
                   
if( $this->get( 'access_type' ) == 'default' )
                    {
                        return
$this->get_protocol_url( $baseurl ).$this->siteurl.'index.php';
                    }
                    else
                    {
                        return
$this->get_protocol_url( $baseurl ).$this->siteurl;
                    }
                }
               
// ... otherwise, we add the blog ID:

           
case 'index.php':
               
// Access through index.php + blog qualifier
               
return $this->get_protocol_url( $baseurl ).$this->siteurl.'index.php?blog='.$this->ID;

            case
'extrabase':
               
// We want to use extra path on base url, use the blog urlname:
               
return $this->get_protocol_url( $baseurl ).$this->siteurl.$this->urlname.'/';

            case
'extrapath':
               
// We want to use extra path on index.php, use the blog urlname:
               
return $this->get_protocol_url( $baseurl ).$this->siteurl.'index.php/'.$this->urlname.'/';

            case
'relative':
                return
$this->get_protocol_url( $baseurl ).$this->siteurl;

            case
'subdom':
                return
$this->get_protocol_url( $baseprotocol.'://' ).$this->urlname.'.'.$basehost.$baseport.'/';

            case
'absolute':
                return
$this->get_protocol_url( $this->siteurl );

            default:
               
debug_die( 'Unhandled Blog access type ['.$type.']' );
        }
    }


   
/**
     * Generate the baseurl of the blog (URL of the folder where the blog lives).
     * Will always end with '/'.
     *
     * @param string Collection access type
     * @return string Collection base URL
     */
   
function gen_baseurl( $type = 'default' )
    {
        if(
$type == 'default' )
        {    
// Use current access type of this collection:
           
$type = $this->get( 'access_type' );
        }

       
$url = $this->gen_blogurl( $type );

        if(
substr( $url, -1 ) != '/' )
        {    
// Crop an url part after the last "/":
           
$url = substr( $url, 0, strrpos( $url, '/' ) + 1 );
        }

        switch(
$type )
        {
            case
'default':
            case
'index.php':
               
// Must be ended with index.php:
               
$url .= 'index.php/';
                break;
        }

        return
$url;
    }


   
/**
     * This is the domain of the collection.
     * This returns NO trailing slash.
     *
     * @return string
     */
   
function get_baseurl_root()
    {
        if( empty(
$this->baseurl_root ) )
        {    
// Initialize collection domain only first time:
           
if( preg_match( '#^(https?://(.+?)(:.+?)?)/#', $this->gen_baseurl(), $matches ) )
            {
               
$this->baseurl_root = $matches[1];
            }
            else
            {
               
debug_die( 'Blog::get(baseurl)/baseurlroot - assertion failed [baseurl: '.$this->gen_baseurl().'].' );
            }
        }

        return
$this->baseurl_root;
    }


   
/**
     * Get the URL to the basepath of that collection.
     * This is supposed to be the same as $baseurl but localized to the domain of the blog/
     *
     * @return string
     */
   
function get_basepath_url()
    {
        if( empty(
$this->basepath_url ) )
        {
            if(
$this->get( 'access_type' ) == 'absolute' )
            {    
// Use whole url when it is absolute:
                // Remove text like 'index.php' at the end:
               
$this->basepath_url = preg_replace( '/^(.+\/)([^\/]+\.[^\/]+)?$/', '$1', $this->get( 'siteurl' ) );
            }
            else
            {    
// Build url from base and sub path:
               
$this->basepath_url = $this->get_baseurl_root().$this->get_basepath();
            }

           
// Get URL with protocol which is defined by collection setting "SSL":
           
$this->basepath_url = $this->get_protocol_url( $this->basepath_url );
        }

        return
$this->basepath_url;
    }


   
/**
     * Get the basepath of this collection relative to domain
     *
     * @return string
     */
   
function get_basepath()
    {
        if( empty(
$this->basepath ) )
        {    
// Initialize base path only first time:
           
if( $this->get( 'access_type' ) == 'absolute' )
            {    
// If collection URL is absolute we should extract sub path from the URL because it can be different than site url:
               
$this->basepath = str_replace( $this->get_baseurl_root(), '', $this->get_basepath_url() );
            }
            else
            {    
// Otherwise we can use sub path of site url:
               
global $basesubpath;
               
$this->basepath = $basesubpath;
            }
        }

        return
$this->basepath;
    }


   
/**
     * Get cookie domain of this collection
     *
     * @param boolean|string FALSE - use dependon of setting,
     *                       'auto' - force to use automatic cookie domain,
     *                       'custom' - force to use custom cookie domain
     * @return string
     */
   
function get_cookie_domain( $force_type = false )
    {
        if( empty(
$this->cookie_domain ) )
        {    
// Initialize only first time:
           
$cookie_domain_type = ( $force_type === false ) ? $this->get_setting( 'cookie_domain_type' ) : $force_type;

            if(
$cookie_domain_type == 'custom' )
            {    
// Custom cookie domain:
               
return $this->get_setting( 'cookie_domain_custom' );
            }
            else
            {    
// Automatic cookie domain:
               
if( $this->get( 'access_type' ) == 'absolute' )
                {    
// If collection URL is absolute we should extract cookie domain from the URL because it can be different than site url:
                   
preg_match( '#^https?://(.+?)(:(.+?))?$#', $this->get_baseurl_root(), $matches );
                   
$coll_host = $matches[1];

                    if(
strpos( $coll_host, '.' ) === false )
                    {    
// localhost or windows machine name:
                       
$this->cookie_domain = $coll_host;
                    }
                    elseif(
preg_match( '~^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$~i', $coll_host ) )
                    {    
// The basehost is an IP address, use the basehost as it is:
                       
$this->cookie_domain = $coll_host;
                    }
                    else
                    {    
// Keep the part of the basehost after the www. :
                       
$this->cookie_domain = preg_replace( '/^(www\. )? (.+)$/xi', '.$2', $coll_host );
                    }
                }
                else
                {    
// Otherwise we can use sub path of site url:
                   
global $cookie_domain;
                   
$this->cookie_domain = $cookie_domain;
                }
            }
        }

        return
$this->cookie_domain;
    }


   
/**
     * Get cookie path of this collection
     *
     * @param boolean|string FALSE - use dependon of setting,
     *                       'auto' - force to use automatic cookie path,
     *                       'custom' - force to use custom cookie path
     * @return string
     */
   
function get_cookie_path( $force_type = false )
    {
        if( empty(
$this->cookie_path ) )
        {    
// Initialize only first time:
           
$cookie_domain_type = ( $force_type === false ) ? $this->get_setting( 'cookie_path_type' ) : $force_type;

            if(
$cookie_domain_type == 'custom' )
            {    
// Custom cookie path:
               
return $this->get_setting( 'cookie_path_custom' );
            }
            else
            {    
// Automatic cookie path:
               
$this->cookie_path = $this->get_basepath();
            }
        }

        return
$this->cookie_path;
    }


   
/**
     * Get URL to htsrv folder
     *
     * Note: For back-office or no collection page _init_hit.inc.php should be called before this call, because ReqHost and ReqPath must be initialized
     *
     * @param boolean TRUE to use https URL
     * @return string URL to htsrv folder
     */
   
function get_htsrv_url( $force_https = false )
    {
        if( ! isset(
$this->htsrv_urls[ $force_https ] ) )
        {    
// Initialize collection htsrv URL only first time and store in cache:
           
global $htsrv_url, $htsrv_subdir;

            if( !
is_array( $this->htsrv_urls ) )
            {
               
$this->htsrv_urls = array();
            }

           
// Get URL with protocol which is defined by collection setting "SSL":
           
$required_htsrv_url = $this->get_protocol_url( $htsrv_url );

           
// Force URL to https depending on the param $force_https:
           
$required_htsrv_url = force_https_url( $required_htsrv_url, $force_https );

           
// Cut htsrv folder from end of the URL:
           
$required_htsrv_url = substr( $required_htsrv_url, 0, strlen( $required_htsrv_url ) - strlen( $htsrv_subdir ) );

            if(
strpos( $this->get_basepath_url(), $required_htsrv_url ) !== false )
            {    
// If current request path contains the required htsrv URL:
               
return $required_htsrv_url.$htsrv_subdir;
            }

           
$coll_url_parts = @parse_url( $this->get_basepath_url() );
           
$htsrv_url_parts = @parse_url( $required_htsrv_url );
            if( ! isset(
$coll_url_parts['host'] ) || ! isset( $htsrv_url_parts['host'] ) )
            {
               
debug_die( 'Invalid hosts!' );
            }

           
$coll_domain = $coll_url_parts['host']
                .( empty(
$coll_url_parts['port'] ) ? '' : ':'.$coll_url_parts['port'] )
                .( empty(
$coll_url_parts['path'] ) ? '' : $coll_url_parts['path'] );
           
$htsrv_domain = $htsrv_url_parts['host']
                .( empty(
$htsrv_url_parts['port'] ) ? '' : ':'.$htsrv_url_parts['port'] )
                .( empty(
$htsrv_url_parts['path'] ) ? '' : $htsrv_url_parts['path'] );

            if( isset(
$coll_url_parts['scheme'], $htsrv_url_parts['scheme'] ) &&
               
$htsrv_url_parts['scheme'] != 'https' && // Don't force htsrv back to http when it was already forced to https above by force_https_url()
               
$coll_url_parts['scheme'] != $htsrv_url_parts['scheme'] )
            {    
// If this collection uses an url with scheme like "https://" then
                // htsrv url must also uses the same url scheme to avoid restriction by secure reason:
               
$required_htsrv_url = $coll_url_parts['scheme'].substr( $required_htsrv_url, strlen( $htsrv_url_parts['scheme'] ) );
            }

           
// Replace domain + path of htsrv URL with current request:
           
$this->htsrv_urls[ $force_https ] = substr_replace( $required_htsrv_url, $coll_domain, strpos( $required_htsrv_url, $htsrv_domain ), strlen( $htsrv_domain ) );

           
// Revert htsrv folder to end of the URL which has been cut above:
           
$this->htsrv_urls[ $force_https ] .= $htsrv_subdir;
        }

        return
$this->htsrv_urls[ $force_https ];
    }


   
/**
     * Get the URL of the htsrv folder, on the current collection's domain (which is NOT always the same as the $baseurl domain!).
     *
     * @param string NULL to use current htsrv_assets_url_type setting. Use 'basic', 'relative' or 'absolute' to force.
     * @param boolean TRUE to use https URL
     * @return string URL to /htsrv/ folder
     */
   
function get_local_htsrv_url( $url_type = NULL, $force_https = false )
    {
       
$url_type = is_null( $url_type ) ? $this->get_setting( 'htsrv_assets_url_type' ) : $url_type;

        if(
$url_type == 'relative' )
        {    
// Relative URL:
           
return $this->get_htsrv_url( $force_https );
        }
        elseif(
$url_type == 'absolute' )
        {    
// Absolute URL:
            // Get URL with protocol which is defined by collection setting "SSL":
           
$htsrv_assets_absolute_url = $this->get_protocol_url( $this->get_setting( 'htsrv_assets_absolute_url' ) );
           
// Force URL to https depending on the param $force_https:
           
return force_https_url( $htsrv_assets_absolute_url, $force_https );
        }
        else
// == 'basic'
       
{    // Basic Config URL from config:
           
global $htsrv_url;
           
// Get URL with protocol which is defined by collection setting "SSL":
           
$basic_htsrv_url = $this->get_protocol_url( $htsrv_url );
           
// Force URL to https depending on the param $force_https:
           
return force_https_url( $basic_htsrv_url, $force_https );
        }
    }


   
/**
     * Get the URL of the media folder, on the current blog's domain (which is NOT always the same as the $baseurl domain!).
     *
     * @param string NULL to use current media_assets_url_type setting.  Use 'basic', 'relative' or 'absolute' to force.
     * @param boolean TRUE - to don't force relative URL to absolute on back-office, mail template and feed skin
     * @return string URL to /media/ folder
     */
   
function get_local_media_url( $url_type = NULL, $force_normal_using = false )
    {
       
$url_type = is_null( $url_type ) ? $this->get_setting( 'media_assets_url_type' ) : $url_type;

        if(
$url_type == 'relative' )
        {    
// Relative URL:
           
if( ! $force_normal_using && is_admin_page() )
            {    
// Force to absolute base URL on back-office side and email template:
               
global $media_url;
               
$local_media_url = $media_url;
            }
            else
            {    
// Use absolute URL relative to collection media folder:
               
global $media_subdir;
               
$local_media_url = $this->get_baseurl_root().$this->get_basepath().$media_subdir;
            }
        }
        elseif(
$url_type == 'absolute' )
        {    
// Absolute URL:
           
$local_media_url = $this->get_setting( 'media_assets_absolute_url' );
        }
        else
// == 'basic'
       
{    // Basic URL from config:
           
global $media_url;
           
$local_media_url = $media_url;
        }

       
// Get URL with protocol which is defined by collection setting "SSL":
       
return $this->get_protocol_url( $local_media_url );
    }


   
/**
     * Get the URL of the rsc folder, on the current blog's domain (which is NOT always the same as the $baseurl domain!).
     *
     * @param string NULL to use current rsc_assets_url_type setting. Use 'basic', 'relative' or 'absolute' to force.
     * @return string URL to /rsc/ folder
     */
   
function get_local_rsc_url( $url_type = NULL )
    {
       
$url_type = is_null( $url_type ) ? $this->get_setting( 'rsc_assets_url_type' ) : $url_type;

        if(
$url_type == 'relative' )
        {
// Relative URL
           
global $rsc_subdir;
           
$local_rsc_url = $this->get_basepath().$rsc_subdir;
        }
        elseif(
$url_type == 'absolute' )
        {
// Absolute URL
           
$local_rsc_url = $this->get_setting( 'rsc_assets_absolute_url' );
        }
        else
// == 'basic'
       
{ // Basic URL from config
           
global $rsc_url;
           
$local_rsc_url = $rsc_url;
        }

       
// Get URL with protocol which is defined by collection setting "SSL":
       
return $this->get_protocol_url( $local_rsc_url );
    }


   
/**
     * Get the URL of the skins folder, on the current blog's domain (which is NOT always the same as the $baseurl domain!).
     *
     * @param string NULL to use current skins_assets_url_type setting. Use 'basic', 'relative' or 'absolute' to force.
     * @return string URL to /skins/ folder
     */
   
function get_local_skins_url( $url_type = NULL )
    {
       
$url_type = is_null( $url_type ) ? $this->get_setting( 'skins_assets_url_type' ) : $url_type;

        if(
$url_type == 'relative' )
        {
// Relative URL
           
global $skins_subdir;
           
$local_skins_url = $this->get_basepath().$skins_subdir;
        }
        elseif(
$url_type == 'absolute' )
        {
// Absolute URL
           
$local_skins_url = $this->get_setting( 'skins_assets_absolute_url' );
        }
        else
// == 'basic'
       
{ // Basic URL from config
           
global $skins_url;
           
$local_skins_url = $skins_url;
        }

       
// Get URL with protocol which is defined by collection setting "SSL":
       
return $this->get_protocol_url( $local_skins_url );
    }


   
/**
     * Get the URL of the plugins folder, on the current collection's domain (which is NOT always the same as the $baseurl domain!).
     *
     * @param string NULL to use current plugins_assets_url_type setting. Use 'basic', 'relative' or 'absolute' to force.
     * @return string URL to /plugins/ folder
     */
   
function get_local_plugins_url( $url_type = NULL )
    {
       
$url_type = is_null( $url_type ) ? $this->get_setting( 'plugins_assets_url_type' ) : $url_type;

        if(
$url_type == 'relative' )
        {    
// Relative URL:
           
global $plugins_subdir;
           
$local_plugins_url = $this->get_basepath().$plugins_subdir;
        }
        elseif(
$url_type == 'absolute' )
        {    
// Absolute URL:
           
$local_plugins_url = $this->get_setting( 'plugins_assets_absolute_url' );
        }
        else
// == 'basic'
       
{    // Basic Config URL from config:
           
global $plugins_url;
           
$local_plugins_url = $plugins_url;
        }

       
// Get URL with protocol which is defined by collection setting "SSL":
       
return $this->get_protocol_url( $local_plugins_url );
    }


   
/**
     * Get the URL of the xmlsrv folder, on the current blog's domain (which is NOT always the same as the $baseurl domain!).
     */
   
function get_local_xmlsrv_url()
    {
        global
$xmlsrv_subdir;

        return
$this->get_basepath_url().$xmlsrv_subdir;
    }


   
/**
     * Generate archive page URL
     *
     * Note: there ate two similar functions here.
     * @see Blog::get_archive_url()
     *
     * @param string year
     * @param string month
     * @param string day
     * @param string week
     */
   
function gen_archive_url( $year, $month = NULL, $day = NULL, $week = NULL, $glue = '&amp;', $paged = 1 )
    {
       
$blogurl = $this->gen_blogurl();

       
$archive_links = $this->get_setting('archive_links');

        if(
$archive_links == 'param' )
        {    
// We reference by Query
           
$separator = '';
        }
        else
        {    
// We reference by extra path info
           
$separator = '/';
        }

       
$datestring = $separator.$year.$separator;

        if( !empty(
$month ) )
        {
           
$datestring .= zeroise($month,2).$separator;
            if( !empty(
$day ) )
            {
               
$datestring .= zeroise($day,2).$separator;
            }
        }
        elseif( !
is_null($week) && $week !== '' )  // Note: week # can be 0 !
       
{
            if(
$archive_links == 'param' )
            {    
// We reference by Query
               
$datestring .= $glue.'w='.$week;
            }
            else
            {    
// extra path info
               
$datestring .= 'w'.zeroise($week,2).'/';
            }
        }

        if(
$archive_links == 'param' )
        {    
// We reference by Query
           
$link = url_add_param( $blogurl, 'm='.$datestring, $glue );

           
$archive_posts_per_page = $this->get_setting( 'archive_posts_per_page' );
            if( !empty(
$archive_posts_per_page) && $archive_posts_per_page != $this->get_setting( 'posts_per_page' ) )
            {    
// We want a specific post per page count:
               
$link = url_add_param( $link, 'posts='.$archive_posts_per_page, $glue );
            }
        }
        else
        {    
// We reference by extra path info
           
$link = url_add_tail( $blogurl, $datestring ); // there may already be a slash from a siteurl like 'http://example.com/'
       
}

        if(
$paged > 1 )
        {    
// We want a specific page:
           
$link = url_add_param( $link, 'paged='.$paged, $glue );
        }

        return
$link;
    }


   
/**
     * Generate link to archive
     * @uses Blog::gen_archive_url()
     * @return string HTML A tag
     */
   
function gen_archive_link( $text, $title, $year, $month = NULL, $day = NULL, $week = NULL, $glue = '&amp;', $paged = 1 )
    {
       
$link = '<a';

        if(
$this->get_setting( 'archive_nofollowto' ) )
        {
           
$link .= ' rel="nofollow"';
        }

         if( !empty(
$title) )
        {
           
$link .= ' title="'.format_to_output( $title, 'htmlattr' ).'"';
        }

       
$link .= ' href="'.$this->gen_archive_url( $year, $month, $day, $week, $glue, $paged ).'" >';
       
$link .= format_to_output( $text );
       
$link .= '</a>';

        return
$link;
    }


   
/**
     * Get archive page URL
     *
     * Note: there are two similar functions here.
     *
     * @uses Blog::gen_archive_url()
     *
     * @param string monthly, weekly, daily
     */
   
function get_archive_url( $date, $glue = '&amp;' )
    {
        switch(
$this->get_setting('archive_mode') )
        {
            case
'weekly':
                global
$cacheweekly, $DB;
                if((!isset(
$cacheweekly)) || (empty($cacheweekly[$date])))
                {
                   
$cacheweekly[$date] = $DB->get_var( 'SELECT '.$DB->week( $DB->quote($date), locale_startofweek() ) );
                }
                return
$this->gen_archive_url( substr( $date, 0, 4 ), NULL, NULL, $cacheweekly[$date], $glue );
                break;

            case
'daily':
                return
$this->gen_archive_url( substr( $date, 0, 4 ), substr( $date, 5, 2 ), substr( $date, 8, 2 ), NULL, $glue );
                break;

            case
'monthly':
            default:
                return
$this->gen_archive_url( substr( $date, 0, 4 ), substr( $date, 5, 2 ), NULL, NULL, $glue );
        }
    }


   
/**
     * Generate a tag url on this blog
     */
   
function gen_tag_url( $tag, $paged = 1, $glue = '&' )
    {
       
$link_type = $this->get_setting( 'tag_links' );
        switch(
$link_type )
        {
            case
'param':
               
$r = url_add_param( $this->gen_blogurl(), 'tag='.urlencode( $tag ), $glue );

               
$tag_posts_per_page = $this->get_setting( 'tag_posts_per_page' );
                if( !empty(
$tag_posts_per_page) && $tag_posts_per_page != $this->get_setting( 'posts_per_page' ) )
                {    
// We want a specific post per page count:
                   
$r = url_add_param( $r, 'posts='.$tag_posts_per_page, $glue );
                }
                break;

            default:
                switch(
$link_type )
                {
                    case
'dash':
                       
$trailer = '-';
                        break;
                    case
'semicol': // dh> TODO: old value. I had this in my DB. Convert this during upgrade?
                   
case 'semicolon':
                       
$trailer = ';';
                        break;
                    case
'colon':
                       
$trailer = ':';
                        break;
                    case
'prefix-only':
                    default:
                       
$trailer = '';
                }
               
$tag_prefix = $this->get_setting('tag_prefix');
                if( !empty(
$tag_prefix ) )
                {
                   
$r = url_add_tail( $this->gen_blogurl(), '/'.$tag_prefix.'/'.urlencode( $tag ).$trailer );
                }
                else
                {
                   
$r = url_add_tail( $this->gen_blogurl(), '/'.urlencode( $tag ).$trailer );
                }
        }

        if(
$paged > 1 )
        {    
// We want a specific page:
           
$r = url_add_param( $r, 'paged='.$paged, $glue );
        }

        return
$r;
    }


   
/**
     * Get a link (<a href>) to the tag page of a given tag.
     *
     * @param string Tag
     * @param string Link text (defaults to tag name)
     * @param array Additional attributes for the A tag (href gets overridden).
     * @return string The <a href> link
     */
   
function get_tag_link( $tag, $text = NULL, $attribs = array() )
    {
        if(
$this->get_setting('tag_rel_attrib') && $this->get_setting('tag_links') == 'prefix-only' )
        {    
// add rel=tag attrib -- valid only if the last part of the url is the tag name
           
if( ! isset($attribs['rel']) )
               
$attribs['rel'] = 'tag';
            else
               
$attribs['rel'] .= ' tag';
        }
       
$attribs['href'] = $this->gen_tag_url( $tag );

        if(
is_null($text) )
        {
           
$text = $tag;
        }

        return
'<a'.get_field_attribs_as_string($attribs).'>'.$text.'</a>';
    }


   
/**
     * Checks if the requested item status can be used by current user and if not, get max allowed item status of the collection
     *
     * @todo make default a Blog param
     *
     * @param string status to start with. Empty to use default.
     * @param object Permission object: Item or Comment
     * @return string authorized status; NULL if none
     */
   
function get_allowed_item_status( $status = NULL, $perm_target = NULL )
    {
        if( !
is_logged_in() )
        {    
// User must be logged in:
           
return $this->get_max_allowed_status( $status );
        }

        if( empty(
$status ) )
        {    
// Get default item status:
           
$status = $this->get_setting( 'default_post_status' );
        }

       
// Get max allowed visibility status:
       
$max_allowed_status = get_highest_publish_status( 'post', $this->ID, false, '', $perm_target );

       
$visibility_statuses = get_visibility_statuses();
       
$status_is_allowed = false;
       
$max_status_is_allowed = false;
       
$allowed_status = NULL;
        foreach(
$visibility_statuses as $status_key => $status_value )
        {
            if(
$status_key == $status || empty( $status ) )
            {    
// Start to check statuses only after the requested status:
               
$status_is_allowed = true;
            }
            if(
$status_key == $max_allowed_status )
            {    
// All next statuses are allowed for this collection:
               
$max_status_is_allowed = true;
            }
            if(
$status_is_allowed && $max_status_is_allowed && check_user_perm( 'blog_post!'.$status_key, 'create', false, $this->ID ) )
            {    
// This status is allowed for this collection and current has a permission:
               
$allowed_status = $status_key;
                break;
            }
        }

        if(
$allowed_status === NULL )
        {    
// Use max allowed status if it could not be found above:
           
$allowed_status = $max_allowed_status;
        }

        return
$allowed_status;
    }


   
/**
     * Get default category ID of the current collection
     *
     * @todo fp> this is a super lame stub, but it's still better than nothing. Should be user configurable.
     *
     * @return integer Chapter ID
     */
   
function get_default_cat_ID()
    {
        global
$DB, $Settings;

        if( empty(
$this->default_cat_ID ) )
        {
            if(
$default_cat_ID = $this->get_setting( 'default_cat_ID' ) )
            {    
// A specific cat has previosuly been configured as the default:
                // Try to get it from the DB (to make sure it exists and is in the right collection and not meta):
               
$SQL = new SQL( 'Check default category ID of collection #'.$this->ID );
               
$SQL->SELECT( 'cat_ID' );
               
$SQL->FROM( 'T_categories' );
               
$SQL->WHERE( 'cat_ID = '.$DB->quote( $default_cat_ID ) );
               
$SQL->WHERE_and( 'cat_blog_ID = '.$this->ID );
               
$SQL->WHERE_and( 'cat_meta = 0' );
               
$this->default_cat_ID = $DB->get_var( $SQL );
            }
        }

        if( empty(
$this->default_cat_ID ) )
        {    
// If the previous query has returned NULL
           
$SQL = new SQL( 'Get auto default category ID of collection #'.$this->ID );
           
$SQL->SELECT( 'cat_ID' );
           
$SQL->FROM( 'T_categories' );
           
$SQL->WHERE( 'cat_blog_ID = '.$this->ID );
           
$SQL->WHERE_and( 'cat_meta = 0' );
           
$SQL->LIMIT( '1' );
            if(
$this->get_setting('category_ordering') == 'manual' )
            {    
// Manual order
               
$SQL->SELECT_add( ', IF( cat_order IS NULL, 999999999, cat_order ) AS temp_order' );
               
$SQL->ORDER_BY( 'cat_parent_ID, temp_order' );
            }
            else
            {    
// Alphabetic order
               
$SQL->ORDER_BY( 'cat_parent_ID, cat_name' );
            }
           
$this->default_cat_ID = $DB->get_var( $SQL );
        }

        return
$this->default_cat_ID;
    }


   
/**
     * Get the blog's media directory (and create it if necessary).
     *
     * If we're {@link is_admin_page() on an admin page}, it adds status messages.
     * @todo These status messages should rather go to a "syslog" and not be displayed to a normal user
     * @todo dh> refactor this into e.g. create_media_dir() and use it for Blog::get_media_dir, too.
     *
     * @param boolean Create the directory, if it does not exist yet?
     * @return string path string on success, false if the dir could not be created
     */
   
function get_media_dir( $create = true )
    {
        global
$media_path, $Messages, $Settings, $Debuglog;

        if( !
$Settings->get( 'fm_enable_roots_blog' ) )
        {
// User directories are disabled:
           
$Debuglog->add( 'Attempt to access collection media dir, but this feature is globally disabled', 'files' );
            return
false;
        }

        switch(
$this->media_location )
        {
            case
'default':
               
$mediadir = get_canonical_path( $media_path.'blogs/'.$this->urlname.'/' );
                break;

            case
'subdir':
               
$mediadir = get_canonical_path( $media_path.$this->media_subdir );
                break;

            case
'custom':
               
$mediadir = get_canonical_path( $this->media_fullpath );
                break;

            case
'none':
            default:
               
$Debuglog->add( 'Attempt to access collection media dir, but this feature is disabled for this colletion', 'files' );
                return
false;
        }

       
// TODO: use a File object here (to access perms, ..), using FileCache::get_by_root_and_path().
       
if( $create && ! is_dir( $mediadir ) )
        {
           
// Display absolute path to blog admin and relative path to everyone else
           
$msg_mediadir_path = check_user_perm( 'blog_admin', 'edit', false, $this->ID ) ? $mediadir : rel_path_to_base( $mediadir );

           
// TODO: Link to some help page(s) with errors!
           
if( ! is_writable( dirname($mediadir) ) )
            {
// add error
               
if( is_admin_page() )
                {
                   
$Messages->add_to_group( sprintf( T_("Media directory &laquo;%s&raquo; could not be created, because the parent directory is not writable or does not exist."), $msg_mediadir_path ),
                           
'error', T_('Media directory file permission error').get_manual_link('media-file-permission-errors').':' );
                }
                return
false;
            }
            elseif( !
evo_mkdir( $mediadir ) )
            {
// add error
               
if( is_admin_page() )
                {
                   
$Messages->add_to_group( sprintf( T_("Media directory &laquo;%s&raquo; could not be created."), $msg_mediadir_path ),
                           
'error', T_('Media directory creation error').get_manual_link('media-file-permission-errors').':' );
                }
                return
false;
            }
            else
            {
// add note:
               
if( is_admin_page() )
                {
                   
$Messages->add_to_group( sprintf( T_("Media directory &laquo;%s&raquo; has been created with permissions %s."), $msg_mediadir_path, substr( sprintf('%o', fileperms($mediadir)), -3 ) ), 'success', T_('Collection media directories created:') );
                }
            }
        }

        return
$mediadir;
    }


   
/**
     * Get the URL to the media folder
     *
     * @return string the URL
     */
   
function get_media_url()
    {
        global
$media_subdir, $Settings, $Debuglog;

        if( !
$Settings->get( 'fm_enable_roots_blog' ) )
        {
// User directories are disabled:
           
$Debuglog->add( 'Attempt to access blog media URL, but this feature is disabled', 'files' );
            return
false;
        }

        switch(
$this->media_location )
        {
            case
'default':
                return
$this->get_local_media_url().'blogs/'.$this->urlname.'/';

            case
'subdir':
                return
$this->get_local_media_url().$this->media_subdir;
                break;

            case
'custom':
                return
$this->get_protocol_url( $this->media_url );

            case
'none':
            default:
               
$Debuglog->add( 'Attempt to access blog media url, but this feature is disabled for this blog', 'files' );
                return
false;
        }
    }


   
/**
      * Get link to edit files
      *
      * @param string link (false on error)
     */
   
function get_filemanager_link()
    {
        global
$admin_url;

       
load_class( '/files/model/_fileroot.class.php', 'FileRoot' );
        return
$admin_url.'?ctrl=files&amp;root='.FileRoot::gen_ID( 'collection', $this->ID );
    }


   
/**
     * Get URL to display the blog with a temporary skin.
     *
     * This is used to construct the various RSS/Atom feeds
     *
     * @param string Skin folder name
     * @param string Additional params
     * @param boolean Halt on unknown feed skin
     * @return string|false URL or FALSE if none feed skin is not installed in system
     */
   
function get_tempskin_url( $skin_folder_name, $additional_params = '', $halt_on_error = false )
    {
       
$SkinCache = & get_SkinCache();
        if( !
$Skin = & $SkinCache->get_by_folder( $skin_folder_name, $halt_on_error ) )
        {    
// If no requested skin try to fallback to first found feed skin:
           
$SkinCache->load_by_type( 'feed' );
           
$skin_folder_name = false;
            foreach(
$SkinCache->cache as $Skin )
            {
                if(
$Skin->type == 'feed' )
                {    
// Use the first found feed skin:
                   
$skin_folder_name = $Skin->folder;
                    break;
                }
            }
            if(
$skin_folder_name === false )
            {    
// No feed skin found:
               
return false;
            }
        }

        return
url_add_param( $this->gen_blogurl( 'default' ), 'tempskin='.$skin_folder_name );
    }


   
/**
     * Get URL to display the blog posts in an XML feed.
     *
     * @param string
     */
   
function get_item_feed_url( $skin_folder_name )
    {
        return
$this->get_tempskin_url( $skin_folder_name );
    }


   
/**
     * Get URL to display the blog comments in an XML feed.
     *
     * @param string
     * @return string|false URL or FALSE if none feed skin is not installed in system
     */
   
function get_comment_feed_url( $skin_folder_name )
    {
       
$tempskin_url = $this->get_tempskin_url( $skin_folder_name );
        return (
$tempskin_url ? url_add_param( $tempskin_url, 'disp=comments' ) : false );
    }


   
/**
     * Callback function for footer_text()
     * @param array
     * @return string
     */
   
function replace_callback( $matches )
    {
        global
$localtimenow;

        switch(
$matches[1] )
        {
            case
'year':
               
// for copyrigth year
               
return date( 'Y', $localtimenow );

            case
'publisher':
               
$publisher_name = $this->get_setting( 'publisher_name' );
                if( ! empty(
$publisher_name) )
                {
                    return
$publisher_name;
                }
               
// No break, use owner instead
           
case 'owner':
               
/**
                 * @var User
                 */
               
$owner_User = $this->get_owner_User();
               
// Full name gets priority over prefered name because it makes more sense for DEFAULT copyright.
                // Blog owner can set WHATEVER footer text she wants through the admin interface.
               
$owner = $owner_User->get( 'fullname' );
                if( empty(
$owner) )
                {
                   
$owner = $owner_User->get_preferred_name();
                }
                return
$owner;

            default:
                return
$matches[1];
        }
    }


   
/**
     * Get a param.
     *
     * @param string Parameter name
     * @param array Additional params
     * @return false|string The value as string or false in case of error (e.g. media dir is disabled).
     */
   
function get( $parname, $params = array() )
    {
        global
$xmlsrv_url, $basehost, $baseurl, $basepath, $media_url, $current_User, $Settings, $Debuglog, $Session;

        if(
gettype( $params ) != 'array' )
        {
           
debug_die('wrong $params');
        }

       
$params = array_merge( array(
               
'glue'       => '&amp;',
               
'url_suffix' => '', // additional url params are appended at the end
           
), $params );

        switch(
$parname )
        {
            case
'access_type':
               
$access_type_value = parent::get( $parname );
                if(
$access_type_value == 'subdom' && is_valid_ip_format( $basehost ) )
                {    
// Don't allow subdomain for IP address:
                   
$access_type_value = 'index.php';
                }
                return
$access_type_value;

            case
'blogurl':        // Deprecated
           
case 'link':          // Deprecated
           
case 'url':
                return
$this->gen_blogurl( 'default' );

            case
'baseurl':
                return
$this->gen_baseurl();

            case
'customizer_url':
                if(
check_user_perm( 'blog_properties', 'edit', false, $this->ID ) ||
                    (
$Settings->get( 'site_skins_enabled' ) && check_user_perm( 'options', 'edit' ) ) )
                {    
// Return customizer URL only if current User can edit skin settings of collection or site:
                   
if( ! empty( $params['customizing_url'] ) )
                    {    
// Get customizing URL from passed param:
                       
$customizing_url = $params['customizing_url'];
                    }
                    elseif(
get_param( 'customizing_url' ) != '' )
                    {    
// Get customizing URL from currently provided _GET param 'customizing_url':
                       
$customizing_url = get_param( 'customizing_url' );
                    }
                    else
                    {    
// Use current URL for customizing URL:
                       
$customizing_url = get_current_url();
                    }
                    if(
$customizing_url == '#baseurl#' )
                    {    
// Use base URL of this collection:
                       
$customizing_url = $this->get( 'baseurl' );
                    }
                   
$customizer_mode_param = ( isset( $params['mode'] ) ? 'customizer_mode='.$params['mode'].$params['glue'] : '' );
                   
$customizer_view_param = ( isset( $params['view'] ) ? 'view='.$params['view'].$params['glue'] : '' );
                    return
get_customizer_url().'?'.$customizer_mode_param.$customizer_view_param.'blog='.$this->ID.$params['glue'].'customizing_url='.urlencode( $customizing_url );
                }
                else
                {    
// Return this collection URL instead:
                   
return $this->get( 'url' );
                }

            case
'baseurlroot':
                return
$this->get_baseurl_root();

            case
'recentpostsurl':
               
$disp_param = 'posts';
                break;

            case
'lastcommentsurl':
               
$disp_param = 'comments';
                break;

            case
'searchurl':
               
$disp_param = 'search';
                break;

            case
'arcdirurl':
               
$disp_param = 'arcdir';
                break;

            case
'catdirurl':
               
$disp_param = 'catdir';
                break;

            case
'postidxurl':
               
$disp_param = 'postidx';
                break;

            case
'mediaidxurl':
               
$disp_param = 'mediaidx';
                break;

            case
'sitemapurl':
               
$disp_param = 'sitemap';
                break;

            case
'msgformurl':
               
$disp_param = 'msgform';
                break;

            case
'profileurl':
               
$disp_param = 'profile';
                break;

            case
'avatarurl':
               
$disp_param = 'avatar';
                break;

            case
'pwdchangeurl':
               
$disp_param = 'pwdchange';
                break;

            case
'userprefsurl':
               
$disp_param = 'userprefs';
                break;

            case
'closeaccounturl':
               
$disp_param = 'closeaccount';
                break;

            case
'subsurl':
               
$disp_param = 'subs';
               
$params['url_suffix'] .= '#subs';
                break;

            case
'register_finishurl':
               
$disp_param = 'register_finish';
                break;

            case
'userurl':
               
$disp_param = 'user';
                break;

            case
'usersurl':
               
$disp_param = 'users';
                break;

            case
'visitsurl':
               
$disp_param = 'visits';
                break;

            case
'tagsurl':
               
$disp_param = 'tags';
                break;

            case
'termsurl':
               
$disp_param = 'terms';
                break;

            case
'loginurl':
            case
'registerurl':
            case
'lostpasswordurl':
            case
'activateinfourl':
            case
'access_requires_loginurl':
            case
'content_requires_loginurl':
               
$url_disp = str_replace( 'url', '', $parname );
                if(
$login_Blog = & get_setting_Blog( 'login_blog_ID', $this ) )
                {
// Use special blog for login/register actions if it is defined in general settings
                   
$url = url_add_param( $login_Blog->gen_blogurl(), 'disp='.$url_disp, $params['glue'] );
                }
                else
                {
// Use login/register urls of this blog
                   
$url = url_add_param( $this->gen_blogurl(), 'disp='.$url_disp, $params['glue'] );
                }
                if( ! empty(
$params['url_suffix'] ) )
                {
// Append url suffix
                   
$url = url_add_param( $url, $params['url_suffix'], $params['glue'] );
                }
               
$url = force_https_url( $url, 'login' );
                return
$url;

            case
'threadsurl':
               
$disp_param = 'threads';
                break;

            case
'messagesurl':
               
$disp_param = 'messages';
                break;

            case
'contactsurl':
               
$disp_param = 'contacts';
                break;

            case
'flaggedurl':
               
$disp_param = 'flagged';
                break;

            case
'mustreadurl':
               
$disp_param = 'mustread';
                break;

            case
'helpurl':
                if(
$this->get_setting( 'help_link' ) == 'slug' )
                {
                    return
url_add_tail( $this->gen_blogurl(), '/help' );
                }
                else
                {
                   
$disp_param = 'help';
                    break;
                }

            case
'skin_ID':
                return
$this->get_skin_ID();

            case
'description':            // RSS wording
           
case 'shortdesc':
                return
$this->shortdesc;

            case
'rdf_url':
                return
$this->get_item_feed_url( '_rdf' );

            case
'rss_url':
                return
$this->get_item_feed_url( '_rss' );

            case
'rss2_url':
                return
$this->get_item_feed_url( '_rss2' );

            case
'atom_url':
                return
$this->get_item_feed_url( '_atom' );

            case
'comments_rdf_url':
                return
$this->get_comment_feed_url( '_rdf' );

            case
'comments_rss_url':
                return
$this->get_comment_feed_url( '_rss' );

            case
'comments_rss2_url':
                return
$this->get_comment_feed_url( '_rss2' );

            case
'comments_atom_url':
                return
$this->get_comment_feed_url( '_atom' );

            case
'rsd_url':
                return
$this->get_local_xmlsrv_url().'rsd.php?blog='.$this->ID;

           
/* Add the html for a blog-specified stylesheet
             * All stylesheets will be included if the blog settings allow it
             * and the file "style.css" exists. CSS rules say that the latter style sheets can
             * override earlier stylesheets.
             */
           
case 'blog_css':
                if(
$this->allowblogcss
                   
&& file_exists( $this->get_media_dir(false).'style.css' ) )
                {
                    return
'<link rel="stylesheet" href="'.$this->get_media_url().'style.css" type="text/css" />';
                }
                else
                {
                    return
'';
                }

           
/* Add the html for a user-specified stylesheet
             * All stylesheets will be included if the blog settings allow it
             * and the file "style.css" exists. CSS rules say that the latter style sheets can
             * override earlier stylesheets. A user-specified stylesheet will
             * override a blog-specified stylesheet which will override a skin stylesheet.
             */
           
case 'user_css':
                if(
$this->allowusercss
                   
&& isset( $current_User )
                    &&
file_exists( $current_User->get_media_dir(false).'style.css' ) )
                {
                    return
'<link rel="stylesheet" href="'.$current_User->get_media_url().'style.css" type="text/css" />';
                }
                else
                {
                    return
'';
                }

            case
'normal_skin_ID':
            case
'mobile_skin_ID':
            case
'tablet_skin_ID':
            case
'alt_skin_ID':
               
$result = parent::get( $parname );
                if(
$parname == 'mobile_skin_ID' || $parname == 'tablet_skin_ID' || $parname == 'alt_skin_ID' )
                {
                    if( empty(
$result ) && ! ( isset( $params['real_value'] ) && $params['real_value'] ) )
                    {    
// Empty values(NULL, 0, '0', '') means that use the same as normal case:
                       
$result = $this->get( 'normal_skin_ID' );
                    }
                }
                return
$result;

            case
'collection_image':
               
$FileCache = & get_FileCache();
                if(
$collection_image_ID = $this->get_setting( 'collection_logo_file_ID' ) )
                {
                    if(
$collection_image_File = & $FileCache->get_by_ID( $collection_image_ID, false, false ) )
                    {
                        return
$collection_image_File;
                    }
                }
                return
NULL;

            case
'collection_favicon':
               
$FileCache = & get_FileCache();
                if(
$collection_favicon_ID = $this->get_setting( 'collection_favicon_file_ID' ) )
                {
                    if(
$collection_favicon_File = & $FileCache->get_by_ID( $collection_favicon_ID, false, false ) )
                    {
                        return
$collection_favicon_File;
                    }
                }
                return
NULL;

            default:
               
// All other params:
               
return parent::get( $parname );
        }

        if( ! empty(
$disp_param ) )
        {
// Get url depending on value of param 'disp'
           
$this_Blog = & $this;
            if(
in_array( $disp_param, array( 'threads', 'messages', 'contacts', 'msgform', 'user', 'profile', 'avatar', 'pwdchange', 'userprefs', 'subs', 'register_finish', 'visits', 'closeaccount' ) ) )
            {
// Check if we can use this blog for messaging actions or we should use spec blog
               
if( $msg_Blog = & get_setting_Blog( 'msg_blog_ID' ) )
                {
// Use special blog for messaging actions if it is defined in general settings
                   
$this_Blog = & $msg_Blog;
                }
            }

            if(
$this_Blog->get_setting( 'front_disp' ) == $disp_param )
            {    
// Get home page of this blog because front page displays current disp:
               
$url = $this_Blog->gen_blogurl( 'default' );
            }
            elseif(
$disp_param == 'user' && ( isset( $params['user_login'] ) || isset( $params['user_ID'] ) ) )
            {    
// Use alias if user login or ID is provided:
               
$UserCache = & get_UserCache();
                if( ! isset(
$params['user_ID'] ) &&
                    (
$this_Blog->get_setting( 'user_links' ) == 'params' || $this_Blog->get_setting( 'user_links' ) == 'prefix_id' ) )
                {    
// We need get user ID by login:
                   
if( $param_User = & $UserCache->get_by_login( $params['user_login'] ) )
                    {    
// Set user ID if it is detected by login:
                       
$params['user_ID'] = $param_User->ID;
                    }
                    else
                    {    
// Wrong request:
                       
debug_die( 'Undefined param "user_ID" for Blog->get( "userurl" )' );
                    }
                }
                if( ! isset(
$params['user_login'] ) &&
                   
$this_Blog->get_setting( 'user_links' ) == 'prefix_login' )
                {    
// We need get user login by ID:
                   
if( $param_User = & $UserCache->get_by_ID( $params['user_ID'], false, false ) )
                    {    
// Use login if user is detected in DB:
                       
$params['user_login'] = $param_User->get( 'login' );
                    }
                    else
                    {    
// Wrong request:
                       
debug_die( 'Undefined param "user_login" for Blog->get( "userurl" )' );
                    }
                }

                if(
$this_Blog->get_setting( 'user_links' ) == 'params' || $this_Blog->get_setting( 'user_prefix' ) == '' )
                {    
// Use params E-g: ?disp=user&user_ID=4
                   
$url = url_add_param( $this_Blog->gen_blogurl(), 'disp=user'.$params['glue'].'user_ID='.$params['user_ID'], $params['glue'] );
                }
                elseif(
$this_Blog->get_setting( 'user_links' ) == 'prefix_id' )
                {    
// Use prefix with user ID:
                   
$url = url_add_tail( $this_Blog->gen_blogurl(), '/'.$this_Blog->get_setting( 'user_prefix' ).':'.$params['user_ID'] );
                }
                else
// 'prefix_login'
               
{    // Use prefix with user login:
                   
$url = url_add_tail( $this_Blog->gen_blogurl(), '/'.$this_Blog->get_setting( 'user_prefix' ).':'.$params['user_login'] );
                }
            }
            else
            {    
// Add disp param to blog's url when current disp is not a front page:
               
$url = url_add_param( $this_Blog->gen_blogurl(), 'disp='.$disp_param, $params['glue'] );
            }

            if(
$disp_param == 'pwdchange' || $disp_param == 'register_finish' )
            {    
// Force these pages to https if it is required by setting "Require SSL":
               
$url = force_https_url( $url, 'login' );
            }

            if( ! empty(
$params['url_suffix'] ) )
            {
// Append url suffix
               
$url = url_add_param( $url, $params['url_suffix'], $params['glue'] );
            }
            return
$url;
        }
    }


   
/**
     * Get warning message about the enabled advanced perms for those cases when we grant some permission for anonymous users which can be restricted for logged in users
     *
     * @return mixed NULL if advanced perms are not enabled in this blog, the warning message otherwise
     */
   
function get_advanced_perms_warning()
    {
        global
$admin_url;

        if(
$this->get( 'advanced_perms' ) )
        {
           
$warning = T_('ATTENTION: advanced <a href="%s">user</a> & <a href="%s">group</a> permissions are enabled and some logged in users may have less permissions than anonymous users.');
           
$advanced_perm_url = url_add_param( $admin_url, 'ctrl=coll_settings&amp;blog='.$this->ID.'&amp;tab=' );
            return
' <b class="warning text-danger">'.sprintf( $warning, $advanced_perm_url.'perm', $advanced_perm_url.'permgroup' ).'</b>';
        }

        return
NULL;
    }


   
/**
     * Get a setting.
     *
     * @param string setting name
     * @param boolean true to return param's real value
     * @return string|false|NULL value as string on success; NULL if not found; false in case of error
     */
   
function get_setting( $parname, $real_value = false )
    {
        global
$Settings;

       
$this->load_CollectionSettings();

       
$result = $this->CollectionSettings->get( $this->ID, $parname );

        switch(
$parname )
        {
            case
'moderation_statuses':
            case
'post_moderation_statuses':
                if(
$result === NULL )
                {
// moderation_statuses was not set yet, set the default value, which depends from the blog type
                   
$default = 'review,draft';
                   
$result = ( $this->type == 'forum' ) ? 'community,protected,'.$default : $default;
                }
                break;

            case
'comment_inskin_statuses':
            case
'post_inskin_statuses':
                if(
$result === NULL )
                {
// inskin_statuses was not set yet, set the default value, which depends from the blog type
                   
$default = 'published,community,protected,private,review';
                   
$result = ( $this->type == 'forum' ) ? $default.',draft' : $default;
                }
                break;

            case
'default_post_status':
            case
'new_feedback_status':
                if(
$result === NULL )
                {
// Default post/comment status was not set yet, use a default value corresponding to the blog type
                   
$result = ( $this->type == 'forum' ) ? 'review' : 'draft';
                }
                break;

            case
'aggregate_coll_IDs':
                if( ! empty(
$result ) && ( $result != '-' ) && ( $result != '*') )
                {
                   
$coll_IDs = explode( ',', $result );
                    if( !
in_array( $this->ID, $coll_IDs ) )
                    {
                       
array_push( $coll_IDs, $this->ID );
                       
$result = implode( ',', $coll_IDs );
                    }
                }
                break;

            case
'msgform_subject_options':
               
$subject_list = utf8_trim( $this->get_setting( 'msgform_subject_list' ) );
               
$result = array();
                if( ! empty(
$subject_list ) )
                {
                   
$subject_list = explode( "\n", str_replace( "\r", '', $subject_list ) );
                    foreach(
$subject_list as $subject_option )
                    {
                        if(
utf8_trim( $subject_option ) != '' )
                        {    
// Use only not empty line:
                           
$result[ $subject_option ] = $subject_option;
                        }
                    }
                }
                break;

            case
'webmentions':
                if(
$this->get_setting( 'allow_access' ) != 'public' )
                {    
// Disable receiving of webmentions for not public collections:
                   
$result = 0;
                }
                break;

            case
'ping_plugins':
                if(
$this->get_setting( 'allow_access' ) != 'public' )
                {    
// Disable ping plugins for not public collections:
                   
$result = '';
                }
                break;

            case
'social_media_image':
                if(
$result === NULL )
                {
                   
$FileCache = & get_FileCache();
                   
$File = & $FileCache->get_by_ID( $this->get_setting( 'social_media_image_file_ID' ), false, false );
                    if(
$File )
                    {
                        return
$File;
                    }
                }
                break;

            case
'allow_crosspost_urls':
                if( !
$this->get_setting( 'canonical_item_urls' ) )
                {    
// When "301 redirect to canonical URL" is disabled then we always must NOT do 301 redirect cross-posted Items:
                   
return 1;
                }
                break;
        }

        return
$result;
    }


     
/**
     * Get a ready-to-display setting from the DB settings table.
     *
     * Same as disp but don't echo
     *
     * @param string Name of setting
     * @param string Output format, see {@link format_to_output()}
     */
   
function dget_setting( $parname, $format = 'htmlbody' )
    {
       
$this->load_CollectionSettings();

        return
format_to_output( $this->CollectionSettings->get( $this->ID, $parname ), $format );
    }


     
/**
     * Display a setting from the DB settings table.
     *
     * @param string Name of setting
     * @param string Output format, see {@link format_to_output()}
     */
   
function disp_setting( $parname, $format = 'htmlbody' )
    {
       
$this->load_CollectionSettings();

        echo
format_to_output( $this->CollectionSettings->get( $this->ID, $parname ), $format );
    }


     
/**
     * Set a setting.
     *
     * @return boolean true, if the value has been set, false if it has not changed.
     */
   
function set_setting( $parname, $value, $make_null = false )
    {
         
// Make sure collection settings are loaded
       
$this->load_CollectionSettings();

        if(
$make_null && empty($value) )
        {
           
$value = NULL;
        }

        return
$this->CollectionSettings->set( $this->ID, $parname, $value );
    }


   
/**
     * Delete a setting.
     *
     * @return boolean true, if the value has been set, false if it has not changed.
     */
   
function delete_setting( $parname )
    {
         
// Make sure collection settings are loaded
       
$this->load_CollectionSettings();

        return
$this->CollectionSettings->delete( $this->ID, $parname );
    }


   
/**
     * Make sure collection settings are loaded.
     */
   
function load_CollectionSettings()
    {
        if( ! isset(
$this->CollectionSettings ) )
        {
           
load_class( 'collections/model/_collsettings.class.php', 'CollectionSettings' );
           
$this->CollectionSettings = new CollectionSettings(); // COPY (function)
       
}
    }


   
/**
     * Insert into the DB
     *
     * @return boolean Result
     */
   
function dbinsert()
    {
        global
$DB, $Plugins, $Settings;

       
// Set default skins on creating new collection:
       
$skin_types = array( 'normal', 'mobile', 'tablet', 'alt' );
        foreach(
$skin_types as $skin_type )
        {
           
$skin_ID = $this->get( $skin_type.'_skin_ID', array( 'real_value' => true ) );
            if( empty(
$skin_ID ) )
            {    
// Only if skin is not selected during creating:
               
$default_skin_ID = $Settings->get( 'def_'.$skin_type.'_skin_ID' );
                if( ! empty(
$default_skin_ID ) )
                {    
// And if default skin is defined for the skin type:
                   
$this->set( $skin_type.'_skin_ID', $default_skin_ID );
                }
            }
        }

       
$DB->begin();

        if(
$this->get( 'order' ) == 0 )
        {
// Set an order as max value of previous order + 1 if it is not defined yet
           
$SQL = new SQL( 'Get max collection order' );
           
$SQL->SELECT( 'MAX( blog_order )' );
           
$SQL->FROM( 'T_blogs' );
           
$max_order = intval( $DB->get_var( $SQL ) );
           
$this->set( 'order', $max_order + 1 );
        }

       
$set_default_blog_ID = isset( $Settings );
        if(
$Settings->get( 'default_blog_ID' ) == -1 || get_setting_Blog( 'default_blog_ID' ) )
        {    
// Don't set a default collection if it is already defined and the collection exists in DB OR back-office page is used for this:
           
$set_default_blog_ID = false;
        }

        if(
parent::dbinsert() )
        {
           
// Reset "all_loaded" flag in order to allow getting new created collection by ID:
           
$BlogCache = & get_BlogCache();
           
$BlogCache->all_loaded = false;

            if(
$set_default_blog_ID )
            {
// Use this blog as default because it is probably first created
               
$Settings->set( 'default_blog_ID', $this->ID );
               
$Settings->dbupdate();
            }

            if( isset(
$this->CollectionSettings ) )
            {
               
// So far all settings have been saved to collection #0 !
                // Update the settings: hackish but the base class should not even store this value actually...
                // dh> what do you mean? What "base class"? Is there a problem with CollectionSettings?
               
$this->CollectionSettings->cache[$this->ID] = $this->CollectionSettings->cache[0];
                unset(
$this->CollectionSettings->cache[0] );

               
$this->CollectionSettings->dbupdate();
            }

           
// Update locales:
           
if( empty( $this->locales ) )
            {    
// Don't forget to store main locale in extra locales table:
               
$this->locales = array( $this->get( 'locale' ) => NULL );
            }
           
$this->update_locales();

            if(
$this->base_collection_ID == 0 )
            {
               
$default_post_type_ID = $this->get_setting( 'default_post_type' );
                if( ! empty(
$default_post_type_ID ) )
                {
// Enable post type that is used by default for this collection:
                   
global $DB;
                   
$DB->query( 'INSERT INTO T_items__type_coll
                                         ( itc_ityp_ID, itc_coll_ID )
                            VALUES ( '
.$DB->quote( $default_post_type_ID ).', '.$DB->quote( $this->ID ).' )' );
                }
               
// Enable default item types for the inserted collection:
               
$this->enable_default_item_types();
            }
            else
            {
               
// Enable default item types for the inserted collection from base collection settings:
               
$this->enable_duplicate_item_types();
            }

           
// Owner automatically favorite the collection
           
$this->favorite( $this->owner_user_ID, 1 );

           
// All users automatically favorite the new blog if collection count < 5 and user count <= 10
           
load_funcs( 'tools/model/_system.funcs.php' );
           
$blog_count = count( system_get_blog_IDs( false ) );
           
$user_IDs = system_get_user_IDs();
           
$user_count = count( $user_IDs );
            if(
$blog_count < 5 && $user_count <= 10 )
            {
                foreach(
$user_IDs as $id )
                {
                   
$this->favorite( $id, 1 );
                }
            }

           
$Plugins->trigger_event( 'AfterCollectionInsert', $params = array( 'Blog' => & $this ) );

           
$DB->commit();

           
// Create collection media directory:
           
$this->get_media_dir();

            return
true;
        }

       
$DB->rollback();

        return
false;
    }


   
/**
     * Create a new blog...
     *
     * @param string Kind of blog ( 'std', 'photo', 'group', 'forum' )
     * @param array Params
     */
   
function create( $kind = '', $params = array() )
    {
        global
$DB, $Messages, $basepath, $admin_url, $Settings;

       
$params = array_merge( array(
               
'create_demo_contents' => false,
               
'create_demo_users'    => false,
               
'create_demo_org'      => false,
            ),
$params );

       
$DB->begin();

       
// DB INSERT
       
$this->dbinsert();

       
// Change access mode if a stub file exists:
       
$stub_filename = 'blog'.$this->ID.'.php';
        if(
is_file( $basepath.$stub_filename ) )
        {    
// Stub file exists and is waiting ;)
           
$DB->query( 'UPDATE T_blogs
                        SET blog_access_type = "relative", blog_siteurl = "'
.$stub_filename.'"
                        WHERE blog_ID = '
.$this->ID );
           
$Messages->add_to_group( sprintf(T_('The new collection has been associated with the stub file &laquo;%s&raquo;.'), $stub_filename ), 'success', T_('New collection created:') );
        }
        elseif(
$this->get( 'access_type' ) == 'relative' )
        {
// Show error message only if stub file should exists!
           
$Messages->add( sprintf(T_('No stub file named &laquo;%s&raquo; was found. You must create it for the blog to function properly with the current settings.'), $stub_filename ), 'error' );
        }

       
// Set default user permissions for this collection (All permissions for the collection owner):
       
if( ! empty( $this->owner_user_ID ) )
        {
// Proceed insertion:
           
$DB->query( 'INSERT INTO T_coll_user_perms
                    ( bloguser_blog_ID, bloguser_user_ID, bloguser_ismember,
                        bloguser_can_be_assignee, bloguser_workflow_status, bloguser_workflow_user, bloguser_workflow_priority,
                        bloguser_perm_item_propose, bloguser_perm_poststatuses, bloguser_perm_item_type, bloguser_perm_edit,
                        bloguser_perm_delpost, bloguser_perm_edit_ts,
                        bloguser_perm_delcmts, bloguser_perm_recycle_owncmts, bloguser_perm_vote_spam_cmts,
                        bloguser_perm_cmtstatuses, bloguser_perm_edit_cmt,
                        bloguser_perm_meta_comment, bloguser_perm_cats, bloguser_perm_properties, bloguser_perm_admin,
                        bloguser_perm_media_upload, bloguser_perm_media_browse, bloguser_perm_media_change,
                        bloguser_perm_analytics )
                    VALUES ( '
.$this->ID.', '.$this->owner_user_ID.', 1,
                        1, 1, 1, 1,
                        1, "published,community,deprecated,protected,private,review,draft,redirected", "admin", "all",
                        1, 1,
                        1, 1, 1,
                        "published,community,deprecated,protected,private,review,draft", "all",
                        1, 1, 1, 1,
                        1, 1, 1,
                        1 )'
);
        }

       
// Create default category:
       
load_class( 'chapters/model/_chapter.class.php', 'Chapter' );
       
$edited_Chapter = new Chapter( NULL, $this->ID );

       
$blog_urlname = $this->get( 'urlname' );
       
$edited_Chapter->set( 'name', T_('Uncategorized') );
       
$edited_Chapter->set( 'urlname', $blog_urlname.'-main' );
       
$edited_Chapter->dbinsert();

       
$Messages->add_to_group( T_('A default category has been created for this collection.'), 'success', T_('New collection created:') );

       
// Create demo contents for the new created collection:
        // NOTE: This must be done before install default widgets!
       
if( $params['create_demo_contents'] )
        {
            global
$user_org_IDs;

           
load_funcs( 'collections/_demo_content.funcs.php' );
           
$user_org_IDs = NULL;

            if(
$params['create_demo_org'] && check_user_perm( 'orgs', 'create', false ) )
            {
// Create the demo organization
               
if( $new_demo_organization = create_demo_organization( $this->get( 'owner_user_ID' ) ) )
                {
                   
$user_org_IDs = array( $new_demo_organization->ID );
                }
            }

           
// Switch to collection locale:
           
locale_temp_switch( $this->get( 'locale' ) );

           
$error_messages = array();
            if(
$params['create_demo_users'] )
            {
               
get_demo_users( true, false, $error_messages );
            }

           
create_sample_content( $kind, $this->ID, $this->get( 'owner_user_ID' ), true, 86400, $error_messages );
            if(
$error_messages )
            {
                foreach(
$error_messages as $error_message )
                {
                   
$Messages->add_to_group( $error_message, 'error', TB_('Demo contents').':' );
                }
            }
           
locale_restore_previous();
        }

       
// ADD DEFAULT WIDGETS:
       
$this->setup_default_widgets();

       
$Messages->add_to_group( T_('Default widgets have been set-up for this collection.'), 'success', T_('New collection created:') );

       
$DB->commit();

       
// set caching
       
if( $Settings->get( 'newblog_cache_enabled' ) )
        {
           
$result = set_cache_enabled( 'cache_enabled', true, $this->ID );
            if(
$result != NULL )
            {
                list(
$status, $message ) = $result;
                if(
$status == 'success' )
                {
                   
$Messages->add_to_group( $message, $status, T_('New collection created:') );
                }
                else
                {
                   
$Messages->add( $message, $status );
                }
            }
        }
       
$this->set_setting( 'cache_enabled_widgets', $Settings->get( 'newblog_cache_enabled_widget' ) );

       
// Insert default group permissions:
       
$default_perms_inserted = $this->insert_default_group_permissions();

        if(
$this->get( 'advanced_perms' ) && $default_perms_inserted )
        {    
// Display this warning if coolection has the enabled advanced perms AND default permissions have been inserted for at least one group:
           
$Messages->add( sprintf( T_('ATTENTION: We have set some advanced permissions for this collection. Please verify the <a %s>User permissions</a> and the <a %s>Group permissions</a> now.'),
                       
'href='.$admin_url.'?ctrl=coll_settings&amp;tab=perm&amp;blog='.$this->ID,
                       
'href='.$admin_url.'?ctrl=coll_settings&amp;tab=permgroup&amp;blog='.$this->ID ),
                   
'warning' );
        }

       
// Commit changes in cache:
       
$BlogCache = & get_BlogCache();
       
$BlogCache->add( $this );
    }


   
/**
     * Duplicate collection to new one
     *
     * @param boolean TRUE to duplicate categories and posts/items from source collection
     * @return boolean Result
     */
   
function duplicate( $params = array() )
    {
        global
$DB, $current_User;

       
$params = array_merge( array(
               
'duplicate_items'    => false,
               
'duplicate_comments' => false,
            ),
$params );

       
$DB->begin();

       
// Load all locales and linked collections:
       
$this->load_locales();

       
// Remember ID of the duplicated collection and Reset it to allow create new one:
       
$duplicated_coll_ID = $this->base_collection_ID = $this->ID;
       
$this->ID = 0;

       
// Get all fields of the duplicated collection:
       
$source_fields_SQL = new SQL( 'Get all fields of the duplicating collection #'.$duplicated_coll_ID );
       
$source_fields_SQL->SELECT( '*' );
       
$source_fields_SQL->FROM( 'T_blogs' );
       
$source_fields_SQL->WHERE( 'blog_ID = '.$DB->quote( $duplicated_coll_ID ) );
       
$source_fields = $DB->get_row( $source_fields_SQL, ARRAY_A );
       
// Use field values of duplicated collection by default:
       
foreach( $source_fields as $source_field_name => $source_field_value )
        {
           
// Cut prefix "blog_" of each field:
           
$source_field_name = substr( $source_field_name, 5 );
            if(
$source_field_name == 'ID' )
            {    
// Skip field ID:
               
continue;
            }
            if( isset(
$this->$source_field_name ) )
            {    
// Unset current value in order to assing new below, especially to update this in array $this->dbchanges:
               
unset( $this->$source_field_name );
            }
           
$this->set( $source_field_name, $source_field_value );
        }

       
// Get all settings of the duplicated collection:
       
$source_settings_SQL = new SQL( 'Get all settings of the duplicating collection #'.$duplicated_coll_ID );
       
$source_settings_SQL->SELECT( 'cset_name, cset_value' );
       
$source_settings_SQL->FROM( 'T_coll_settings' );
       
$source_settings_SQL->WHERE( 'cset_coll_ID = '.$DB->quote( $duplicated_coll_ID ) );
       
$source_settings = $DB->get_assoc( $source_settings_SQL );
       
// Use setting values of duplicated collection by default:
       
foreach( $source_settings as $source_setting_name => $source_setting_value )
        {
            if(
$source_setting_name == 'allow_duplicate' )
            {
                continue;
            }
           
$this->set_setting( $source_setting_name, $source_setting_value );
        }

       
$blog_urlname = param( 'blog_urlname', 'string', true );
        if( !
check_user_perm( 'blog_admin', 'edit', false, $duplicated_coll_ID ) )
        {
// validate the urlname, which was already set by init_by_kind() function
            // It needs to validated, because the user can not set the blog urlname, and every new blog would have the same urlname without validation.
            // When user has edit permission to blog admin part, the urlname will be validated in load_from_request() function.
           
$this->set( 'urlname', urltitle_validate( empty( $blog_urlname ) ? $this->get( 'urlname' ) : $blog_urlname, '', 0, false, 'blog_urlname', 'blog_ID', 'T_blogs' ) );
        }

       
// Duplicated collection should not have the same siteurl as original collection, set access type to default extrapath
        // and empty the siteurl, similar to what a new blank collection have:
       
global $Settings;
       
$this->set( 'access_type', $Settings->get( 'coll_access_type' ) );
       
$this->set( 'siteurl', '' );

       
// Set collection owner to current user
       
$this->set( 'owner_user_ID', $current_User->ID );

       
// Call this firstly to find all possible errors before inserting:
        // Also to set new values from submitted form:
       
if( ! $this->load_from_Request() )
        {    
// Error on handle new values from form:
           
$this->ID = $duplicated_coll_ID;
           
$DB->rollback();
            return
false;
        }

       
// Try insert new collection in DB:
       
if( ! $this->dbinsert() )
        {    
// Error on insert collection in DB:
           
$this->ID = $duplicated_coll_ID;
           
$DB->rollback();
            return
false;
        }

       
// Initialize fields of collection permission tables which must be duplicated:
       
$coll_perm_fields = '{prefix}ismember, {prefix}can_be_assignee, {prefix}workflow_status, {prefix}workflow_user, {prefix}workflow_priority,
                {prefix}perm_item_propose, {prefix}perm_poststatuses, {prefix}perm_item_type,
                {prefix}perm_edit, {prefix}perm_delpost, {prefix}perm_edit_ts, {prefix}perm_delcmts,
                {prefix}perm_recycle_owncmts, {prefix}perm_vote_spam_cmts, {prefix}perm_cmtstatuses,
                {prefix}perm_edit_cmt, {prefix}perm_meta_comment, {prefix}perm_cats, {prefix}perm_properties,
                {prefix}perm_admin, {prefix}perm_media_upload, {prefix}perm_media_browse, {prefix}perm_media_change,
                {prefix}perm_analytics'
;

       
// Copy all permissions "collection per user" from duplicated collection to new created:
       
$coll_user_perm_fields = 'bloguser_user_ID, '.str_replace( '{prefix}', 'bloguser_', $coll_perm_fields );
       
$DB->query( 'INSERT INTO T_coll_user_perms
              ( bloguser_blog_ID, '
.$coll_user_perm_fields.' )
            SELECT '
.$this->ID.', '.$coll_user_perm_fields.'
              FROM T_coll_user_perms
             WHERE bloguser_blog_ID = '
.$DB->quote( $duplicated_coll_ID ),
           
'Duplicate all coll-user permissions from collection #'.$duplicated_coll_ID.' to #'.$this->ID );

       
// Copy all permissions "collection per group" from duplicated collection to new created:
       
$coll_group_perm_fields = 'bloggroup_group_ID, '.str_replace( '{prefix}', 'bloggroup_', $coll_perm_fields );
       
$DB->query( 'INSERT INTO T_coll_group_perms
             ( bloggroup_blog_ID, '
.$coll_group_perm_fields.' )
            SELECT '
.$this->ID.', '.$coll_group_perm_fields.'
              FROM T_coll_group_perms
             WHERE bloggroup_blog_ID = '
.$DB->quote( $duplicated_coll_ID ),
           
'Duplicate all coll-group permissions from collection #'.$duplicated_coll_ID.' to #'.$this->ID );

       
// Copy all widgets and containers from duplicated collection to new created:
       
$SQL = new SQL( 'Get all widget containers of the duplicating collection #'.$duplicated_coll_ID );
       
$SQL->SELECT( '*' );
       
$SQL->FROM( 'T_widget__container' );
       
$SQL->WHERE( 'wico_coll_ID = '.$DB->quote( $duplicated_coll_ID ) );
       
$widget_containers = $DB->get_results( $SQL, ARRAY_A );
        foreach(
$widget_containers as $widget_container )
        {
           
$old_wico_ID = $widget_container['wico_ID'];
            unset(
$widget_container['wico_ID'] );
           
$widget_container['wico_coll_ID'] = $this->ID;
           
$r = $DB->query( 'INSERT INTO T_widget__container
                ( '
.implode( ', ', array_keys( $widget_container ) ).' )
                VALUES ( '
.$DB->quote( $widget_container ).' )',
               
'Duplicate widget container #'.$old_wico_ID .' from collection #'.$duplicated_coll_ID.' to #'.$this->ID );
            if(
$r && $DB->insert_id > 0 )
            {    
// Duplicate all widgets of the inserted container:
               
$DB->query( 'INSERT INTO T_widget__widget
                          ( wi_wico_ID, wi_order, wi_enabled, wi_type, wi_code, wi_params )
                  SELECT '
.$DB->insert_id.', wi_order, wi_enabled, wi_type, wi_code, wi_params
                    FROM T_widget__widget
                    WHERE wi_wico_ID = '
.$DB->quote( $old_wico_ID ),
                   
'Duplicate all widgets of container #'.$old_wico_ID.' from collection #'.$duplicated_coll_ID.' to #'.$this->ID );
            }
        }

        if(
$params['duplicate_items'] )
        {    
// Duplicate categories and posts/items:
            // Copy all categories from duplicated collection to new created:
           
$source_cats_SQL = new SQL( 'Get all categories of the duplicating collection #'.$duplicated_coll_ID );
           
$source_cats_SQL->SELECT( '*' );
           
$source_cats_SQL->FROM( 'T_categories' );
           
$source_cats_SQL->WHERE( 'cat_blog_ID = '.$DB->quote( $duplicated_coll_ID ) );
           
$source_cats = $DB->get_results( $source_cats_SQL, ARRAY_A );
           
$new_cats = array(); // Store all new created categories with key as ID of copied category in order to correct assign parent IDs
           
$ChapterCache = & get_ChapterCache();
            foreach(
$source_cats as $source_cat_fields )
            {    
// Copy each category separately because of unique field "cat_urlname":
               
$new_Chapter = & $ChapterCache->new_obj( NULL, $this->ID );
                foreach(
$source_cat_fields as $source_cat_field_name => $source_cat_field_value )
                {
                   
// Cut prefix "cat_" of each field:
                   
$source_cat_field_name = substr( $source_cat_field_name, 4 );
                    if(
$source_cat_field_name == 'ID' || $source_cat_field_name == 'blog_ID' )
                    {    
// Skip these fields, they must be new:
                       
continue;
                    }
                   
$new_Chapter->set( $source_cat_field_name, $source_cat_field_value );
                }
               
// Insert the duplicated category:
               
if( $new_Chapter->dbinsert() )
                {    
// If category has been inserted successfully, then update IDs for correct parent hierarchy:
                    // Key - ID of copied category, Value - new created category:
                   
$new_cats[ $source_cat_fields['cat_ID'] ] = $new_Chapter;
                }
            }
            foreach(
$new_cats as $duplicated_cat_ID => $new_Chapter )
            {    
// Update wrong parent IDs to IDs of new created categories:
               
$old_cat_parent_ID = intval( $new_Chapter->get( 'parent_ID' ) );
                if(
$old_cat_parent_ID > 0 && isset( $new_cats[ $old_cat_parent_ID ] ) )
                {
                   
$new_parent_Chapter = $new_cats[ $old_cat_parent_ID ];
                   
$new_Chapter->set( 'parent_ID', $new_parent_Chapter->ID );
                   
$new_Chapter->dbupdate();
                }
                if(
$this->get_setting( 'default_cat_ID' ) == $duplicated_cat_ID )
                {    
// Update wrong default category with correct ID of new duplicated category:
                   
$this->set_setting( 'default_cat_ID', $new_Chapter->ID );
                   
$this->dbupdate();
                }
            }

            if( ! empty(
$new_cats ) )
            {    
// Duplicate posts if collection has at least one category:
               
$old_items_SQL = new SQL( 'Get all posts of collection #'.$duplicated_coll_ID.' before duplicating them into collection #'.$this->ID );
               
$old_items_SQL->SELECT( 'T_items__item.*' );
               
$old_items_SQL->FROM( 'T_items__item' );
               
$old_items_SQL->FROM_add( 'INNER JOIN T_categories ON post_main_cat_ID = cat_ID' );
               
$old_items_SQL->WHERE( 'cat_blog_ID = '.$duplicated_coll_ID );
               
$old_items_SQL->ORDER_BY( 'post_ID' );
               
$old_items = $DB->get_results( $old_items_SQL, ARRAY_A );
               
$new_items = array();
                foreach(
$old_items as $old_item )
                {
                   
$old_item_ID = $old_item['post_ID'];
                    unset(
$old_item['post_ID'] );

                    if( isset(
$new_cats[ $old_item['post_main_cat_ID'] ] ) )
                    {    
// Use correct category ID:
                       
$new_item_Chapter = $new_cats[ $old_item['post_main_cat_ID'] ];
                       
$old_item['post_main_cat_ID'] = $new_item_Chapter->ID;
                    }

                   
// Get new unique slug:
                   
$old_item['post_urltitle'] = urltitle_validate( $old_item['post_urltitle'], $old_item['post_title'], 0, false, 'slug_title', 'slug_itm_ID', 'T_slug', $old_item['post_locale'], 'T_items__item' );

                   
// Duplicate a post:
                   
$DB->query( 'INSERT INTO T_items__item ( '.implode( ', ', array_keys( $old_item ) ).' ) VALUES ( '.$DB->quote( $old_item ).' )',
                       
'Duplicate from post #'.$old_item_ID );
                   
$new_item_ID = $DB->insert_id;

                   
// Create canonical and tiny slugs:
                   
load_funcs( 'slugs/model/_slug.funcs.php' );
                   
$new_canonical_Slug = new Slug();
                   
$new_canonical_Slug->set( 'title', $old_item['post_urltitle'] );
                   
$new_canonical_Slug->set( 'type', 'item' );
                   
$new_canonical_Slug->set( 'itm_ID', $new_item_ID );
                   
$new_canonical_Slug->dbinsert();
                   
$new_tiny_Slug = new Slug();
                   
$new_tiny_Slug->set( 'title', getnext_tinyurl() );
                   
$new_tiny_Slug->set( 'type', 'item' );
                   
$new_tiny_Slug->set( 'itm_ID', $new_item_ID );
                   
$new_tiny_Slug->dbinsert();

                   
$DB->query( 'UPDATE T_items__item
                          SET post_canonical_slug_ID = '
.$new_canonical_Slug->ID.',
                              post_tiny_slug_ID = '
.$new_tiny_Slug->ID.'
                        WHERE post_ID = '
.$new_item_ID );

                   
$new_items[ $old_item_ID ] = $new_item_ID;
                }

               
// Update parent post IDs with correct IDs of new inserted posts:
               
$update_parent_items_sql = '';
                foreach(
$old_items as $old_item )
                {
                    if( ! empty(
$old_item['post_parent_ID'] ) && isset( $new_items[ $old_item['post_parent_ID'] ] ) )
                    {
                       
$update_parent_items_sql .= ' WHEN post_parent_ID = '.$old_item['post_parent_ID'].' THEN '.$new_items[ $old_item['post_parent_ID'] ];
                    }
                }
                if(
$update_parent_items_sql != '' )
                {
                   
$DB->query( 'UPDATE T_items__item
                        INNER JOIN T_categories ON post_main_cat_ID = cat_ID
                          SET post_parent_ID = CASE '
.$update_parent_items_sql.' ELSE post_parent_ID END
                        WHERE cat_blog_ID = '
.$this->ID );
                }

                if( ! empty(
$new_items ) )
                {
                   
$old_items_IDs = $DB->quote( array_keys( $new_items ) );

                   
// Duplicate extra categories:
                   
$old_extra_cats_SQL = new SQL( 'Get all posts categories of collection #'.$duplicated_coll_ID.' before duplicating' );
                   
$old_extra_cats_SQL->SELECT( 'postcat_post_ID, postcat_cat_ID' );
                   
$old_extra_cats_SQL->FROM( 'T_postcats' );
                   
$old_extra_cats_SQL->WHERE( 'postcat_post_ID IN ( '.$old_items_IDs.' )' );
                   
$old_extra_cats = $DB->get_results( $old_extra_cats_SQL, ARRAY_A );
                   
$new_extra_cats_values = array();
                    foreach(
$old_extra_cats as $old_extra_cat )
                    {
                        if( isset(
$new_items[ $old_extra_cat['postcat_post_ID'] ], $new_cats[ $old_extra_cat['postcat_cat_ID'] ] ) )
                        {
                           
$new_extra_cats_values[] = '( '.$new_items[ $old_extra_cat['postcat_post_ID'] ].', '.$new_cats[ $old_extra_cat['postcat_cat_ID'] ]->ID.' )';
                        }
                    }
                    if( ! empty(
$new_extra_cats_values ) )
                    {
                       
$DB->query( 'INSERT INTO T_postcats ( '.$old_extra_cats_SQL->get_select( '' ).' ) VALUES '.implode( ', ', $new_extra_cats_values ),
                           
'Duplicate extra categories for posts of collection #'.$duplicated_coll_ID );
                    }

                   
// Duplicate tags:
                   
$old_tags_SQL = new SQL( 'Get all posts tags of collection #'.$duplicated_coll_ID.' before duplicating' );
                   
$old_tags_SQL->SELECT( 'itag_itm_ID, itag_tag_ID' );
                   
$old_tags_SQL->FROM( 'T_items__itemtag' );
                   
$old_tags_SQL->WHERE( 'itag_itm_ID IN ( '.$old_items_IDs.' )' );
                   
$old_tags = $DB->get_results( $old_tags_SQL, ARRAY_A );
                   
$new_tags_values = array();
                    foreach(
$old_tags as $old_tag )
                    {
                        if( isset(
$new_items[ $old_tag['itag_itm_ID'] ] ) )
                        {
                           
$old_tag['itag_itm_ID'] = $new_items[ $old_tag['itag_itm_ID'] ];
                           
$new_tags_values[] = '( '.$DB->quote( $old_tag ).' )';
                        }
                    }
                    if( ! empty(
$new_tags_values ) )
                    {
                       
$DB->query( 'INSERT INTO T_items__itemtag ( '.$old_tags_SQL->get_select( '' ).' ) VALUES '.implode( ', ', $new_tags_values ),
                           
'Duplicate tags for posts of collection #'.$duplicated_coll_ID );
                    }

                   
// Duplicate settings:
                   
$old_settings_SQL = new SQL( 'Get all posts settings of collection #'.$duplicated_coll_ID.' before duplicating' );
                   
$old_settings_SQL->SELECT( 'iset_item_ID, iset_name, iset_value' );
                   
$old_settings_SQL->FROM( 'T_items__item_settings' );
                   
$old_settings_SQL->WHERE( 'iset_item_ID IN ( '.$old_items_IDs.' )' );
                   
$old_settings = $DB->get_results( $old_settings_SQL, ARRAY_A );
                   
$new_settings_values = array();
                    foreach(
$old_settings as $old_setting )
                    {
                        if( isset(
$new_items[ $old_setting['iset_item_ID'] ] ) )
                        {
                           
$old_setting['iset_item_ID'] = $new_items[ $old_setting['iset_item_ID'] ];
                           
$new_settings_values[] = '( '.$DB->quote( $old_setting ).' )';
                        }
                    }
                    if( ! empty(
$new_settings_values ) )
                    {
                       
$DB->query( 'INSERT INTO T_items__item_settings ( '.$old_settings_SQL->get_select( '' ).' ) VALUES '.implode( ', ', $new_settings_values ),
                           
'Duplicate settings for posts of collection #'.$duplicated_coll_ID );
                    }

                   
// Duplicate links/attachments:
                   
$old_links_SQL = new SQL( 'Get all posts attachments of collection #'.$duplicated_coll_ID.' before duplicating' );
                   
$old_links_SQL->SELECT( 'link_datecreated, link_datemodified, link_creator_user_ID, link_lastedit_user_ID, link_itm_ID, link_file_ID, link_position, link_order' );
                   
$old_links_SQL->FROM( 'T_links' );
                   
$old_links_SQL->WHERE( 'link_itm_ID IN ( '.$old_items_IDs.' )' );
                   
$old_links = $DB->get_results( $old_links_SQL, ARRAY_A );
                   
$new_links_values = array();
                    foreach(
$old_links as $old_link )
                    {
                        if( isset(
$new_items[ $old_link['link_itm_ID'] ] ) )
                        {
                           
$old_link['link_itm_ID'] = $new_items[ $old_link['link_itm_ID'] ];
                           
$new_links_values[] = '( '.$DB->quote( $old_link ).' )';
                        }
                    }
                    if( ! empty(
$new_links_values ) )
                    {
                       
$DB->query( 'INSERT INTO T_links ( '.$old_links_SQL->get_select( '' ).' ) VALUES '.implode( ', ', $new_links_values ),
                           
'Duplicate links for posts of collection #'.$duplicated_coll_ID );
                    }

                    if(
$params['duplicate_comments'] )
                    {    
// Duplicate comments:
                       
$old_comments_SQL = new SQL( 'Get all comments of collection #'.$duplicated_coll_ID.' before duplicating' );
                       
$old_comments_SQL->SELECT( '*' );
                       
$old_comments_SQL->FROM( 'T_comments' );
                       
$old_comments_SQL->WHERE( 'comment_item_ID IN ( '.$old_items_IDs.' )' );
                       
$old_comments = $DB->get_results( $old_comments_SQL, ARRAY_A );
                        foreach(
$old_comments as $old_comment )
                        {
                            if( isset(
$new_items[ $old_comment['comment_item_ID'] ] ) )
                            {    
// Insert only comments with correct item ID:
                               
$old_comment_ID = $old_comment['comment_ID'];
                                unset(
$old_comment['comment_ID'] );
                               
$old_comment['comment_item_ID'] = $new_items[ $old_comment['comment_item_ID'] ];
                               
$DB->query( 'INSERT INTO T_comments ( '.implode( ', ', array_keys( $old_comment ) ).' ) VALUES ( '.$DB->quote( $old_comment ).' )',
                                   
'Duplicate comment #'.$old_comment_ID.' of collection #'.$duplicated_coll_ID );
                               
$new_comment_ID = $DB->insert_id;
                               
$new_comments[ $old_comment_ID ] = $new_comment_ID;
                            }
                        }

                       
// Update parent comment IDs with correct IDs of new inserted comments:
                       
$update_parent_comments_sql = '';
                        foreach(
$old_comments as $old_comment )
                        {
                            if( ! empty(
$old_comment['comment_in_reply_to_cmt_ID'] ) && isset( $new_comments[ $old_comment['comment_in_reply_to_cmt_ID'] ] ) )
                            {
                               
$update_parent_comments_sql .= ' WHEN comment_in_reply_to_cmt_ID = '.$old_comment['comment_in_reply_to_cmt_ID'].' THEN '.$new_comments[ $old_comment['comment_in_reply_to_cmt_ID'] ];
                            }
                        }
                        if(
$update_parent_comments_sql != '' )
                        {
                           
$DB->query( 'UPDATE T_comments
                                    SET comment_in_reply_to_cmt_ID = CASE '
.$update_parent_comments_sql.' ELSE comment_in_reply_to_cmt_ID END
                                WHERE comment_item_ID IN ( '
.$DB->quote( $new_items ).' )' );
                        }

                        if( ! empty(
$new_comments ) )
                        {    
// Duplicate links/attachments of the comments:
                           
$old_links_SQL = new SQL( 'Get all comments attachments of collection #'.$duplicated_coll_ID.' before duplicating' );
                           
$old_links_SQL->SELECT( 'link_datecreated, link_datemodified, link_creator_user_ID, link_lastedit_user_ID, link_cmt_ID, link_file_ID, link_position, link_order' );
                           
$old_links_SQL->FROM( 'T_links' );
                           
$old_links_SQL->WHERE( 'link_cmt_ID IN ( '.$old_items_IDs.' )' );
                           
$old_links = $DB->get_results( $old_links_SQL, ARRAY_A );
                           
$new_links_values = array();
                            foreach(
$old_links as $old_link )
                            {
                                if( isset(
$new_comments[ $old_link['link_cmt_ID'] ] ) )
                                {
                                   
$old_link['link_cmt_ID'] = $new_comments[ $old_link['link_cmt_ID'] ];
                                   
$new_links_values[] = '( '.$DB->quote( $old_link ).' )';
                                }
                            }
                            if( ! empty(
$new_links_values ) )
                            {
                               
$DB->query( 'INSERT INTO T_links ( '.$old_links_SQL->get_select( '' ).' ) VALUES '.implode( ', ', $new_links_values ),
                                   
'Duplicate links for comments of collection #'.$duplicated_coll_ID );
                            }
                        }
                    }
                }
            }
        }
        else
        {    
// Create at least one default category:
           
load_class( 'chapters/model/_chapter.class.php', 'Chapter' );
           
$new_Chapter = new Chapter( NULL, $this->ID );
           
$new_Chapter->set( 'name', T_('Uncategorized') );
           
$new_Chapter->set( 'urlname', $this->get( 'urlname' ).'-main' );
           
$new_Chapter->dbinsert();
           
// Use this single category as default:
           
$this->set_setting( 'default_cat_ID', $new_Chapter->ID );
           
$this->dbupdate();
        }

       
// The duplicating is successful, So commit all above changes:
       
$DB->commit();

       
// Commit changes in cache:
       
$BlogCache = & get_BlogCache();
       
$BlogCache->add( $this );

        return
true;
    }


   
/**
     * Insert default group permissions
     *
     * @return boolean TRUE on inserting the permissions for at least one group
     */
   
function insert_default_group_permissions()
    {
        global
$DB, $default_locale;

       
$groups = array(
           
'admins'     => array( 'ID' => 1, 'name' => 'Administrators' ),
           
'moderators' => array( 'ID' => 2, 'name' => 'Moderators' ),
           
'editors'    => array( 'ID' => 3, 'name' => 'Editors' ),
           
'users'      => array( 'ID' => 4, 'name' => 'Normal Users' ),
           
'suspect'    => array( 'ID' => 5, 'name' => 'Misbehaving/Suspect Users' ),
           
'spam'       => array( 'ID' => 6, 'name' => 'Spammers/Restricted Users' ),
           
'blogb'      => array( 'ID' => 7, 'name' => 'Blog B Members' ),
        );

       
// Define default group permissions for each group for any collection type:
        // NOTE: The fields order should be same in each group array!
       
$group_permissions = array(
           
'admins' => array(
               
'ismember'             => 1,
               
'can_be_assignee'      => 1,
               
'workflow_status'      => 1,
               
'workflow_user'        => 1,
               
'workflow_priority'    => 1,
               
'perm_item_propose'    => 1,
               
'perm_poststatuses'    => 'published,community,deprecated,protected,private,review,draft,redirected',
               
'perm_item_type'       => 'admin',
               
'perm_edit'            => 'all',
               
'perm_delpost'         => 1,
               
'perm_edit_ts'         => 1,
               
'perm_delcmts'         => 1,
               
'perm_recycle_owncmts' => 1,
               
'perm_vote_spam_cmts'  => 1,
               
'perm_cmtstatuses'     => 'published,community,deprecated,protected,private,review,draft',
               
'perm_edit_cmt'        => 'all',
               
'perm_meta_comment'    => 1,
               
'perm_cats'            => 1,
               
'perm_properties'      => 1,
               
'perm_admin'           => 1,
               
'perm_media_upload'    => 1,
               
'perm_media_browse'    => 1,
               
'perm_media_change'    => 1,
               
'perm_analytics'       => 1,
            ),
           
'moderators' => array(
               
'ismember'             => 1,
               
'can_be_assignee'      => 1,
               
'workflow_status'      => 1,
               
'workflow_user'        => 1,
               
'workflow_priority'    => 1,
               
'perm_item_propose'    => 1,
               
'perm_poststatuses'    => 'published,community,deprecated,protected,private,review,draft',
               
'perm_item_type'       => 'restricted',
               
'perm_edit'            => 'le',
               
'perm_delpost'         => 1,
               
'perm_edit_ts'         => 1,
               
'perm_delcmts'         => 1,
               
'perm_recycle_owncmts' => 1,
               
'perm_vote_spam_cmts'  => 1,
               
'perm_cmtstatuses'     => 'published,community,deprecated,protected,private,review,draft',
               
'perm_edit_cmt'        => 'le',
               
'perm_meta_comment'    => 1,
               
'perm_cats'            => 0,
               
'perm_properties'      => 0,
               
'perm_admin'           => 0,
               
'perm_media_upload'    => 1,
               
'perm_media_browse'    => 1,
               
'perm_media_change'    => 1,
               
'perm_analytics'       => 0,
            ),
           
'editors' => array(
               
'ismember'             => 1,
               
'can_be_assignee'      => 0,
               
'workflow_status'      => 0,
               
'workflow_user'        => 0,
               
'workflow_priority'    => 0,
               
'perm_item_propose'    => 1,
               
'perm_poststatuses'    => '',
               
'perm_item_type'       => 'standard',
               
'perm_edit'            => 'no',
               
'perm_delpost'         => 0,
               
'perm_edit_ts'         => 1,
               
'perm_delcmts'         => 0,
               
'perm_recycle_owncmts' => 0,
               
'perm_vote_spam_cmts'  => 1,
               
'perm_cmtstatuses'     => $this->get_setting( 'new_feedback_status' ),
               
'perm_edit_cmt'        => 'no',
               
'perm_meta_comment'    => 1,
               
'perm_cats'            => 0,
               
'perm_properties'      => 0,
               
'perm_admin'           => 0,
               
'perm_media_upload'    => 1,
               
'perm_media_browse'    => 1,
               
'perm_media_change'    => 0,
               
'perm_analytics'       => 0,
            )
        );
        if(
$this->type == 'forum' )
        {    
// Set special permissions for collection with type "Forum"
           
$group_permissions['editors'] = array(
               
'ismember'             => 1,
               
'can_be_assignee'      => 0,
               
'workflow_status'      => 0,
               
'workflow_user'        => 0,
               
'workflow_priority'    => 0,
               
'perm_item_propose'    => 0,
               
'perm_poststatuses'    => 'community,protected,draft,deprecated',
               
'perm_item_type'       => 'standard',
               
'perm_edit'            => 'own',
               
'perm_delpost'         => 0,
               
'perm_edit_ts'         => 1,
               
'perm_delcmts'         => 0,
               
'perm_recycle_owncmts' => 0,
               
'perm_vote_spam_cmts'  => 1,
               
'perm_cmtstatuses'     => 'community,protected,review,draft,deprecated',
               
'perm_edit_cmt'        => 'own',
               
'perm_meta_comment'    => 1,
               
'perm_cats'            => 0,
               
'perm_properties'      => 0,
               
'perm_admin'           => 0,
               
'perm_media_upload'    => 1,
               
'perm_media_browse'    => 1,
               
'perm_media_change'    => 0,
               
'perm_analytics'       => 0,
            );
           
$group_permissions['users'] = array(
               
'ismember'             => 1,
               
'can_be_assignee'      => 0,
               
'workflow_status'      => 0,
               
'workflow_user'        => 0,
               
'workflow_priority'    => 0,
               
'perm_item_propose'    => 0,
               
'perm_poststatuses'    => 'community,draft',
               
'perm_item_type'       => 'standard',
               
'perm_edit'            => 'no',
               
'perm_delpost'         => 0,
               
'perm_edit_ts'         => 0,
               
'perm_delcmts'         => 0,
               
'perm_recycle_owncmts' => 0,
               
'perm_vote_spam_cmts'  => 0,
               
'perm_cmtstatuses'     => 'community,draft',
               
'perm_edit_cmt'        => 'no',
               
'perm_meta_comment'    => 0,
               
'perm_cats'            => 0,
               
'perm_properties'      => 0,
               
'perm_admin'           => 0,
               
'perm_media_upload'    => 1,
               
'perm_media_browse'    => 0,
               
'perm_media_change'    => 0,
               
'perm_analytics'       => 0,
            );
           
$group_permissions['suspect'] = array(
               
'ismember'             => 1,
               
'can_be_assignee'      => 0,
               
'workflow_status'      => 0,
               
'workflow_user'        => 0,
               
'workflow_priority'    => 0,
               
'perm_item_propose'    => 0,
               
'perm_poststatuses'    => 'review,draft',
               
'perm_item_type'       => 'standard',
               
'perm_edit'            => 'no',
               
'perm_delpost'         => 0,
               
'perm_edit_ts'         => 0,
               
'perm_delcmts'         => 0,
               
'perm_recycle_owncmts' => 0,
               
'perm_vote_spam_cmts'  => 0,
               
'perm_cmtstatuses'     => 'review,draft',
               
'perm_edit_cmt'        => 'no',
               
'perm_meta_comment'    => 0,
               
'perm_cats'            => 0,
               
'perm_properties'      => 0,
               
'perm_admin'           => 0,
               
'perm_media_upload'    => 0,
               
'perm_media_browse'    => 0,
               
'perm_media_change'    => 0,
               
'perm_analytics'       => 0,
            );
        }

        if(
$this->type == 'std' && $this->get( 'shortname' ) == 'Blog B' )
        {    
// Set special permission for group "Blog B Members" for collection "Blog B":
           
$group_permissions['blogb'] = array(
               
'ismember'             => 1,
               
'can_be_assignee'      => 0,
               
'workflow_status'      => 0,
               
'workflow_user'        => 0,
               
'workflow_priority'    => 0,
               
'perm_item_propose'    => 0,
               
'perm_poststatuses'    => '',
               
'perm_item_type'       => 'standard',
               
'perm_edit'            => 'no',
               
'perm_delpost'         => 0,
               
'perm_edit_ts'         => 0,
               
'perm_delcmts'         => 0,
               
'perm_recycle_owncmts' => 0,
               
'perm_vote_spam_cmts'  => 0,
               
'perm_cmtstatuses'     => $this->get_setting( 'new_feedback_status' ),
               
'perm_edit_cmt'        => 'no',
               
'perm_meta_comment'    => 0,
               
'perm_cats'            => 0,
               
'perm_properties'      => 0,
               
'perm_admin'           => 0,
               
'perm_media_upload'    => 0,
               
'perm_media_browse'    => 0,
               
'perm_media_change'    => 0,
               
'perm_analytics'       => 0,
            );
        }

       
$insert_values = array();
        foreach(
$groups as $group_key => $group )
        {
            if( ! isset(
$group_permissions[ $group_key ] ) )
            {    
// Skip group without default permissions:
               
continue;
            }

           
// Check if the requested group still exists in DB:
           
$group_SQL = new SQL( 'Check if the requested group still exists in DB' );
           
$group_SQL->SELECT( 'grp_ID' );
           
$group_SQL->FROM( 'T_groups' );
           
$group_SQL->WHERE( 'grp_ID = '.$DB->quote( $group['ID'] ) );
           
$group_SQL->WHERE_and( 'grp_name = '.$DB->quote( $group['name'] ) );
           
$group_SQL->WHERE_and( 'grp_name = '.$DB->quote( T_( $group['name'], $default_locale ) ) );
            if( !
$DB->get_var( $group_SQL ) )
            {    
// Skip this group because we cannot find it in DB:
               
continue;
            }

            if( ! isset(
$insert_fields ) )
            {    
// Initialize DB field names only first time:
               
$insert_fields = 'bloggroup_'.implode( ', bloggroup_', array_keys( $group_permissions[ $group_key ] ) );
            }

           
// Initialize permission values for each group:
           
$insert_values[] = $this->ID.', '.$group['ID'].', "'.implode( '", "', $group_permissions[ $group_key ] ).'"';
        }

        if( isset(
$insert_fields ) )
        {    
// Insert group permissions into DB:
           
$r = $DB->query( 'INSERT INTO T_coll_group_perms ( bloggroup_blog_ID, bloggroup_group_ID, '.$insert_fields.' )
                    VALUES ( '
.implode( ' ), ( ', $insert_values ).' )' );
            if(
$r )
            {    
// The permissions have been inserted successfully
               
return true;
            }
        }

       
// No permission insertion
       
return false;
    }


   
/**
     * Update the DB based on previously recorded changes
     */
   
function dbupdate()
    {
        global
$DB, $Plugins, $servertimenow;

       
$DB->begin();

       
// Favorite blog by new owner
       
if( isset( $this->dbchanges['blog_owner_user_ID'] ) )
        {
           
$this->favorite( $this->owner_user_ID, 1 );
        }

       
// URL aliases
       
if( $this->update_url_aliases )
        {
           
$insert_values = array();
            foreach(
$this->url_aliases as $alias )
            {
                if( ! empty(
$alias ) )
                {
                   
$insert_values[] = '('.$DB->quote( $this->ID ).', '.$DB->quote( $alias ).')';
                }
            }

           
// Clear existing URL aliases first
           
$DB->query( 'DELETE FROM T_coll_url_aliases WHERE cua_coll_ID ='.$DB->quote( $this->ID ) );
            if( ! empty(
$insert_values ) )
            {
               
$SQL = new SQL( 'Update URL aliases for collection #'.$this->ID );
               
$SQL = $DB->query( 'INSERT INTO T_coll_url_aliases ( cua_coll_ID, cua_url_alias ) VALUES '.implode( ', ', $insert_values ) );
            }
        }

       
parent::dbupdate();

       
// if this blog settings was modified we need to invalidate this blog's page caches
        // this way all existing cached page on this blog will be regenerated during next display
        // TODO: Ideally we want to detect if the changes are minor/irrelevant to caching and not invalidate the page cache if not necessary.
        // In case of doubt (and for unknown changes), it's better to invalidate.
       
$this->set_setting( 'last_invalidation_timestamp', $servertimenow );

        if( isset(
$this->CollectionSettings ) )
        {
           
$this->CollectionSettings->dbupdate();
        }

       
// Update locales:
       
$this->update_locales();

       
$Plugins->trigger_event( 'AfterCollectionUpdate', $params = array( 'Blog' => & $this ) );

        if(
get_param( 'action' ) == 'update_confirm' )
        {    
// Reduce statuses of posts and comments by max allowed status of this collection ONLY if this has been confirmed:
           
$this->update_reduced_status_data();
        }

       
$DB->commit();

       
// BLOCK CACHE INVALIDATION:
       
BlockCache::invalidate_key( 'set_coll_ID', $this->ID ); // Settings have changed
       
BlockCache::invalidate_key( 'set_coll_ID', 'any' ); // Settings of a have changed (for widgets tracking a change on ANY blog)

        // cont_coll_ID  // Content has not changed
   
}


   
/**
     * Delete a blog and dependencies from database
     *
     * @param boolean true if you want to echo progress
     */
   
function dbdelete( $echo = false )
    {
        global
$DB, $Messages, $Plugins, $Settings;

       
// Try to obtain some serious time to do some serious processing (5 minutes)
       
set_max_execution_time(300);

        if(
$echo ) echo 'Delete collection with all of it\'s content... ';

       
// remember ID, because parent method resets it to 0
       
$old_ID = $this->ID;

       
// Delete main (blog) object:
       
if( ! parent::dbdelete() )
        {
           
$Messages->add( 'Blog has not been deleted.', 'error' );
            return
false;
        }

       
// Delete the blog cache folder - try to delete even if cache is disabled
       
load_class( '_core/model/_pagecache.class.php', 'PageCache' );
       
$PageCache = new PageCache( $this );
       
$PageCache->cache_delete();

       
// Delete blog's media folder recursively:
       
$FileRootCache = & get_FileRootCache();
        if(
$root_directory = $FileRootCache->get_root_dir( 'collection', $old_ID ) )
        {
// Delete the folder only when it is detected
           
rmdir_r( $root_directory );
           
$Messages->add( T_('Deleted blog\'s files'), 'success' );
        }

       
// re-set the ID for the Plugin event
       
$this->ID = $old_ID;
       
$Plugins->trigger_event( 'AfterCollectionDelete', $params = array( 'Blog' => & $this ) );
       
$this->ID = 0;

        if( isset(
$Settings ) )
        {
// Reset settings related to the deleted blog
           
if( $Settings->get( 'default_blog_ID' ) == $old_ID )
            {
// Reset default blog ID
               
$Settings->set( 'default_blog_ID', 0 );
            }
            if(
$Settings->get( 'info_blog_ID' ) == $old_ID )
            {
// Reset info blog ID
               
$Settings->set( 'info_blog_ID', 0 );
            }
            if(
$Settings->get( 'login_blog_ID' ) == $old_ID )
            {
// Reset login blog ID
               
$Settings->set( 'login_blog_ID', 0 );
            }
            if(
$Settings->get( 'msg_blog_ID' ) == $old_ID )
            {
// Reset messaging blog ID
               
$Settings->set( 'msg_blog_ID', 0 );
            }
           
$Settings->dbupdate();
        }

        if(
$echo ) echo '<br />Done.</p>';

        return
true;
    }


   
/*
     * Template function: display name of blog
     *
     * Template tag
     */
   
function name( $params = array() )
    {
       
// Make sure we are not missing any param:
       
$params = array_merge( array(
               
'before'      => ' ',
               
'after'       => ' ',
               
'format'      => 'htmlbody',
            ),
$params );

        if( !empty(
$this->name ) )
        {
            echo
$params['before'];
           
$this->disp( 'name', $params['format'] );
            echo
$params['after'];
        }
    }


   
/*
     * Template function: display name of blog
     *
     * Template tag
     */
   
function tagline( $params = array() )
    {
       
// Make sure we are not missing any param:
       
$params = array_merge( array(
               
'before'      => ' ',
               
'after'       => ' ',
               
'format'      => 'htmlbody',
            ),
$params );

        if( !empty(
$this->tagline ) )
        {
            echo
$params['before'];
           
$this->disp( 'tagline', $params['format'] );
            echo
$params['after'];
        }
    }


   
/*
     * Template function: display name of blog
     *
     * Template tag
     */
   
function longdesc( $params = array() )
    {
       
// Make sure we are not missing any param:
       
$params = array_merge( array(
               
'before'      => ' ',
               
'after'       => ' ',
               
'format'      => 'htmlbody',
            ),
$params );

        if( !empty(
$this->longdesc ) )
        {
            echo
$params['before'];
           
$this->disp( 'longdesc', $params['format'] );
            echo
$params['after'];
        }
    }


   
/**
     * Get the name of the blog
     *
     * @return string
     */
   
function get_name()
    {
        return
$this->name;
    }


   
/**
     * Get the short name of the blog
     *
     * @return sring
     */
   
function get_shortname()
    {
        return
$this->shortname;
    }


   
/**
     * Get the name of the blog limited by length
     *
     * @param integer Max length
     * @return string Limited name
     */
   
function get_maxlen_name( $maxlen = 50 )
    {
        return
strmaxlen( $this->get_name(), $maxlen, NULL, 'raw' );
    }


   
/**
     * Get short and long name of the blog
     *
     * @return string
     */
   
function get_extended_name()
    {
       
$names = array();

        if( ! empty(
$this->shortname ) )
        {
// Short name
           
$names[] = $this->shortname;
        }

        if( ! empty(
$this->name ) )
        {
// Title
           
$names[] = $this->name;
        }

        return
implode( ' - ', $names );
    }


   
/**
     * Get skin type which should be used for current session
     *
     * @return string Skin type
     */
   
function get_skin_type()
    {
        global
$Session;

        if( ! isset(
$Session ) )
        {    
// Session must be defined to autodetect current skin type:
           
return 'normal';
        }

        if(
$Session->is_mobile_session() )
        {
           
$skin_type = 'mobile';
        }
        elseif(
$Session->is_tablet_session() )
        {
           
$skin_type = 'tablet';
        }
        elseif(
$Session->is_alt_session() )
        {
           
$skin_type = 'alt';
        }
        else
        {
           
$skin_type = 'normal';
        }

        if(
$skin_type == 'mobile' || $skin_type == 'tablet' || $skin_type == 'alt' )
        {    
// Check if collection use different mobile/tablet/alt skin or same as normal skin:
           
$skin_ID = $this->get( $skin_type.'_skin_ID', array( 'real_value' => true ) );
            if( empty(
$skin_ID ) )
            {    
// Force to use widgets for normal skin because collection doesn't use different skin for mobile/tablet session:
               
$skin_type = 'normal';
            }
        }

        return
$skin_type;
    }


   
/*
     * Get the blog skin ID which correspond to the current session device or which correspond to the selected skin type
     *
     * @param string Skin type: 'auto', 'normal', 'mobile', 'tablet', 'alt'
     * @return integer skin ID
     */
   
function get_skin_ID( $skin_type = 'auto', $real_value = false )
    {
        switch(
$skin_type )
        {
            case
'auto':
               
// Auto detect skin by session type
               
global $Session;
                if( ! empty(
$Session ) )
                {
                    if(
$Session->is_mobile_session() )
                    {
                        return
$this->get( 'mobile_skin_ID', array( 'real_value' => $real_value ) );
                    }
                    if(
$Session->is_tablet_session() )
                    {
                        return
$this->get( 'tablet_skin_ID', array( 'real_value' => $real_value ) );
                    }
                    if(
$Session->is_alt_session() )
                    {
                        return
$this->get( 'alt_skin_ID', array( 'real_value' => $real_value ) );
                    }
                }
                return
$this->get( 'normal_skin_ID' );

            case
'normal':
               
// Normal skin
               
return $this->get( 'normal_skin_ID' );

            case
'mobile':
               
// Mobile skin
               
return $this->get( 'mobile_skin_ID', array( 'real_value' => $real_value ) );

            case
'tablet':
               
// Tablet skin
               
return $this->get( 'tablet_skin_ID', array( 'real_value' => $real_value ) );

            case
'alt':
               
// Alt skin
               
return $this->get( 'alt_skin_ID', array( 'real_value' => $real_value ) );
        }

       
// Deny to request invalid skin types
       
debug_die( 'The requested skin type is invalid.' );
    }


   
/**
     * Get distinct skin ids used by the current blog
     *
     * @return array Ids of skins which are used as normal, mobile, tablet or alt skin with the current Collection
     */
   
function get_skin_ids()
    {
       
$skin_ids = array( $this->get( 'normal_skin_ID' ) );
        if(
$this->get( 'mobile_skin_ID' ) > 0 )
        {
           
$skin_ids[] = $this->get( 'mobile_skin_ID' );
        }
        if(
$this->get( 'tablet_skin_ID' ) > 0 )
        {
           
$skin_ids[] = $this->get( 'tablet_skin_ID' );
        }
        if(
$this->get( 'alt_skin_ID' ) > 0 )
        {
           
$skin_ids[] = $this->get( 'alt_skin_ID' );
        }

        return
array_unique( $skin_ids );
    }


   
/**
     * Get collection skin containers which correspond to the current session device or which correspond to the selected skin type
     *
     * @param string Skin type: 'auto', 'normal', 'mobile', 'tablet', 'alt'
     * @return array
     */
   
function get_skin_containers( $skin_type = 'auto' )
    {
       
$SkinCache = & get_SkinCache();
       
$coll_Skin = & $SkinCache->get_by_ID( $this->get_skin_ID( $skin_type, true ), false, false );

        return
$coll_Skin ? $coll_Skin->get_containers() : array();
    }


   
/**
     * Get collection main containers
     *
     * @param string Skin type: 'normal', 'mobile', 'tablet', 'alt'
     * @param boolean TRUE to initialize IDs of containers
     * @return array main container codes => array( name, order, id(optional) )
     */
   
function get_main_containers( $skin_type = 'normal', $load_container_ids = false )
    {
        if( ! isset(
$this->widget_containers ) )
        {    
// Initialize
           
$this->widget_containers = array();
        }

        if( ! isset(
$this->widget_containers[ $skin_type ] ) )
        {    
// Get widget containers of requested collection skin:
           
$this->widget_containers[ $skin_type ] = $this->get_skin_containers( $skin_type );
        }

        if(
$load_container_ids &&
           
$this->ID > 0 &&
            empty(
$this->widget_containers_ids_loaded[ $skin_type ] ) &&
            ! empty(
$this->widget_containers[ $skin_type ] ) )
        {    
// Initialize IDs of containers:
           
global $DB;

           
$SQL = new SQL( 'Load widget containers data of collection #'.$this->ID );
           
$SQL->SELECT( 'wico_code, wico_ID' );
           
$SQL->FROM( 'T_widget__container' );
           
$SQL->WHERE( 'wico_coll_ID = '.$this->ID );
           
$SQL->WHERE_and( 'wico_skin_type = '.$DB->quote( $skin_type ) );
           
$SQL->WHERE_and( 'wico_code IN ( '.$DB->quote( array_keys( $this->widget_containers[ $skin_type ] ) ).' )' );
           
$containers_data = $DB->get_assoc( $SQL );
            foreach(
$containers_data as $container_code => $container_ID )
            {
                if( isset(
$this->widget_containers[ $skin_type ][ $container_code ] ) )
                {
                   
$this->widget_containers[ $skin_type ][ $container_code ]['ID'] = $container_ID;
                }
            }
           
// Set flag to don't load these data twice:
           
if( ! isset( $this->widget_containers_ids_loaded ) )
            {
               
$this->widget_containers_ids_loaded = array();
            }
           
$this->widget_containers_ids_loaded[ $skin_type ] = true;
        }

        return
$this->widget_containers[ $skin_type ];
    }


   
/**
     * Get all collection main containers from either declared skin function or from scanned skin files
     *
     * @param boolean TRUE to display result messages
     * @param string Skin type: 'all', 'normal', 'mobile', 'tablet', 'alt'
     */
   
function db_save_main_containers( $verbose = false, $skin_type = 'all' )
    {
        global
$DB, $Messages;

       
$DB->begin();

        if(
$skin_type == 'all' )
        {    
// Use all skin types:
           
$skin_types = array( 'normal', 'mobile', 'tablet', 'alt' );
        }
        elseif(
in_array( $skin_type, array( 'normal', 'mobile', 'tablet', 'alt' ) ) )
        {    
// Use single skin type:
           
$skin_types = array( $skin_type );
        }
        else
        {    
// Use Normal/Default skin for all other unknown requestes:
           
$skin_types = array( 'normal' );
        }

       
// Get currently saved collection containers from DB:
       
$SQL = new SQL( 'Get widget containers for collection #'.$this->ID.' for skin types: "'.implode( '", "', $skin_types ).'"' );
       
$SQL->SELECT( 'wico_skin_type, wico_code, wico_ID, wico_name, wico_order, COUNT( wi_ID ) AS widgets_count' );
       
$SQL->FROM( 'T_widget__container' );
       
$SQL->FROM_add( 'LEFT JOIN T_widget__widget ON wi_wico_ID = wico_ID' );
       
$SQL->WHERE( 'wico_coll_ID = '.$this->ID );
       
$SQL->WHERE_and( 'wico_skin_type IN ( '.$DB->quote( $skin_types ).' )' );
       
$SQL->GROUP_BY( 'wico_ID' );
       
$coll_containers = $DB->get_results( $SQL );
       
// Group containers with skin type:
       
$coll_containers_skin_types = array();
        foreach(
$coll_containers as $coll_container )
        {
           
$coll_containers_skin_types[ $coll_container->wico_skin_type ][ $coll_container->wico_code ] = array(
                   
'ID'    => $coll_container->wico_ID,
                   
'name'  => $coll_container->wico_name,
                   
'order' => $coll_container->wico_order,
                   
'count' => $coll_container->widgets_count,
                );
        }

       
$SkinCache = & get_SkinCache();
       
$skin_containers_num = 0;
       
$created_containers_num = 0;
       
$updated_containers_num = 0;
       
$deleted_containers_num = 0;

        foreach(
$skin_types as $skin_type )
        {
            if( ! (
$coll_Skin = & $SkinCache->get_by_ID( $this->get( $skin_type.'_skin_ID' ), false, false ) ) )
            {    
// Skip wrong skin to avoid errors:
               
continue;
            }

           
// Get default widget containers which are recommended for all skins:
           
$skin_default_containers = get_skin_default_containers();

           
// Get skin containers either from declared function or from scaned skin files:
           
$skin_containers = $coll_Skin->get_containers();
           
$skin_containers_num += count( $skin_containers );

           
// Check all main containers and create insert rows for those which are not saved yet,
           
$update_widget_containers_sql_rows = array();
           
$new_widget_containers = array();
            foreach(
$skin_containers as $wico_code => $wico_data )
            {
                if( ! isset(
$coll_containers_skin_types[ $skin_type ][ $wico_code ] ) )
                {    
// Create only those containers which are not saved yet:
                   
$update_widget_containers_sql_rows[] = '( '.$DB->quote( $wico_code ).', '.$DB->quote( $skin_type ).', '.$DB->quote( $wico_data[0] ).', '.$this->ID.', '.$DB->quote( $wico_data[1] ).', 1 )';
                   
$new_widget_containers[] = $wico_code;
                }
                else
                {    
// Check if we should update some container data:
                   
$update_widget_container_sql_data = array();
                    if(
$coll_containers_skin_types[ $skin_type ][ $wico_code ]['name'] != $wico_data[0] )
                    {    
// Update if name is different:
                       
$update_widget_container_sql_data[] = 'wico_name = '.$DB->quote( $wico_data[0] );
                    }
                    if(
$coll_containers_skin_types[ $skin_type ][ $wico_code ]['order'] != $wico_data[1] )
                    {    
// Update if order is different:
                       
$update_widget_container_sql_data[] = 'wico_order = '.$DB->quote( $wico_data[1] );
                    }
                    if( ! empty(
$update_widget_container_sql_data ) )
                    {    
// Update different data of the container:
                       
$updated_containers_num += $DB->query( 'UPDATE T_widget__container
                              SET '
.implode( ', ', $update_widget_container_sql_data ).'
                            WHERE wico_ID = '
.$coll_containers_skin_types[ $skin_type ][ $wico_code ]['ID'],
                           
'Update widget #'.$coll_containers_skin_types[ $skin_type ][ $wico_code ]['ID'].' "'.$wico_code.'" of collection #'.$this->ID.' for skin type "'.$skin_type.'"' );
                    }
                }
            }

           
// Delete empty default containers if they are not declared in the Skin:
           
$delete_widget_containers = array();
            foreach(
$skin_default_containers as $wico_code => $wico_data )
            {
                if( ! isset(
$skin_containers[ $wico_code ] ) &&
                    isset(
$coll_containers_skin_types[ $skin_type ][ $wico_code ] ) &&
                   
$coll_containers_skin_types[ $skin_type ][ $wico_code ]['count'] == 0 )
                {    
// Delete ONLY EMPTY default container if it exists in DB but not declared in the Skin (or cannot be found in fallback files of old skins):
                   
$delete_widget_containers[] = $coll_containers_skin_types[ $skin_type ][ $wico_code ]['ID'];
                }
            }

            if(
$verbose )
            {    
// Display how many containers are declared or scanned by the collection skin:
               
$skin_declared_containers = $coll_Skin->get_declared_containers();
                if(
$coll_Skin->get_api_version() > 5 || ! empty( $skin_declared_containers ) )
                {    
// If skin has the declared widget containers:
                   
$Messages->add( sprintf( T_('%d containers declared by skin "%s".'), $skin_containers_num, $coll_Skin->get_name() ), 'note' );
                }
                else
                {    
// If skin scans widget containers from skin and fallback files:
                   
$Messages->add( sprintf( T_('WARNING: This is an old skin which provides no container declarations (%s). Falling back to scanning all skin files.'), '<code>function get_declared_containers()</code>' ), 'warning' );
                   
$Messages->add( sprintf( T_('%d containers found by scanning templates of skin "%s" (including fall-back templates).'), $skin_containers_num, $coll_Skin->get_name() ), 'note' );
                }
            }

            if( ! empty(
$update_widget_containers_sql_rows ) )
            {
// Insert all containers defined by the blog skins into the database
               
$created_containers_num += $DB->query( 'REPLACE INTO T_widget__container( wico_code, wico_skin_type, wico_name, wico_coll_ID, wico_order, wico_main ) VALUES'
                       
.implode( ', ', $update_widget_containers_sql_rows ),
                   
'Insert new widget containers for collection #'.$this->ID );
            }

            if( ! empty(
$new_widget_containers ) )
            {    
// Install default widgets:
               
load_funcs( 'widgets/_widgets.funcs.php' );
               
$WidgetContainerCache = & get_WidgetContainerCache();
               
$new_container_messages = array();
                foreach(
$new_widget_containers as $new_widget_container_code )
                {
                   
$new_inserted_widgets_num = install_new_default_widgets( $new_widget_container_code, '*', $this->ID, $skin_type );
                   
$new_WidgetContainer = & $WidgetContainerCache->get_by_coll_skintype_code( $this->ID, $skin_type, $new_widget_container_code );
                   
$new_container_messages[] = sprintf( T_('%s has been added (populated with %d widgets)'),
                        (
$new_WidgetContainer ? $new_WidgetContainer->get_type_title().' "'.$new_WidgetContainer->get( 'name' ).'"' : $new_widget_container_code ),
                       
$new_inserted_widgets_num );
                }
            }

            if( ! empty(
$delete_widget_containers ) )
            {    
// Delete containers which are not used by Skin:
               
$deleted_containers_num += $DB->query( 'DELETE FROM T_widget__container
                    WHERE wico_ID IN ( '
.$DB->quote( $delete_widget_containers ).' )',
                   
'Delete unwanted empty widget containers from collection #'.$this->ID );
            }
        }

        if(
$verbose )
        {
            if(
$created_containers_num > 0 )
            {
               
$Messages->add( sprintf( T_('%d Containers are left untouched and %d Containers have been added:'), ( $skin_containers_num - $created_containers_num - $updated_containers_num - $deleted_containers_num ), $created_containers_num )
                    .( empty(
$new_container_messages ) ? '' : '<ul class="message_group"><li>'.implode( '</li><li>', $new_container_messages ).'</li></ul>' ), 'success' );
            }
            else
            {
               
$Messages->add( T_('All containers were already created.'), 'success' );
            }
            if(
$updated_containers_num > 0 )
            {
               
$Messages->add( sprintf( T_('%d container(s) were updated.'), $updated_containers_num ), 'success' );
            }
            if(
$deleted_containers_num > 0 )
            {
               
$Messages->add( sprintf( T_('%d container(s) were deleted.'), $deleted_containers_num ), 'success' );
            }
        }

       
$DB->commit();
    }


   
/**
     * Get skin folder/name by skin type
     *
     * @param string Force session type: 'auto', 'normal', 'mobile', 'tablet', 'alt'
     * @return string Skin folder/name
     */
   
function get_skin_folder( $skin_type = 'auto' )
    {
       
$blog_skin_ID = $this->get_skin_ID( $skin_type );
        if( empty(
$blog_skin_ID ) && $skin_type != 'auto' )
        {
// Get default skin ID if it is not defined for the selected session type yet
           
$blog_skin_ID = $this->get_skin_ID( 'auto' );
        }

       
$SkinCache = & get_SkinCache();
       
$Skin = & $SkinCache->get_by_ID( $blog_skin_ID );

        return
$Skin->folder;
    }


   
/**
     * Resolve user ID of owner
     *
     * @return User
     */
   
function & get_owner_User()
    {
        if( !isset(
$this->owner_User) )
        {
           
$UserCache = & get_UserCache();
           
$this->owner_User = & $UserCache->get_by_ID($this->owner_user_ID);
        }

        return
$this->owner_User;
    }


   
/**
     * Template tag: display a link leading to the contact form for the owner of the current Blog.
     *
     * @param array (empty default array is provided for compatibility with v 1.10)
     */
   
function contact_link( $params = array() )
    {
       
// Make sure we are not missing any param:
       
$params = array_merge( array(
               
'before'      => ' ',
               
'after'       => ' ',
               
'text'        => 'Contact', // Note: left untranslated, should be translated in skin anyway
               
'title'       => 'Send a message to the owner of this blog...',
               
'class'       => 'contact_link',
               
'with_redirect'=> false,
            ),
$params );

       
$contact_url = $this->get_contact_url( $params['with_redirect'] );
        if( empty(
$contact_url ) )
        {    
// If contact URL is not available:
           
return false;
        }

        echo
$params['before'];
        echo
'<a href="'.$contact_url.'" '
                   
.( $this->get_setting( 'msgform_nofollowto' ) ? 'rel="nofollow" ' : '' )
                    .
'title="'.format_to_output( $params['title'], 'htmlattr' ).'" '
                   
.'class="'.format_to_output( $params['class'], 'htmlattr' ).'">'
               
.format_to_output( $params['text'] )
            .
'</a>';
        echo
$params['after'];

        return
true;
    }


   
/**
     * Template tag: display a link leading to the help page
     *
     * @param array
     */
   
function help_link( $params = array() )
    {
       
// Make sure we are not missing any param:
       
$params = array_merge( array(
               
'before'      => ' ',
               
'after'       => ' ',
               
'text'        => 'Help', // Note: left untranslated, should be translated in skin anyway
               
'title'       => '',
            ),
$params );


        echo
$params['before'];
        echo
'<a href="'.$this->get('helpurl').'" title="'.$params['title'].'" class="help_link">'
                   
.$params['text'].'</a>';
        echo
$params['after'];

        return
true;
    }


   
/**
     * Template tag: display footer text for the current Blog.
     *
     * @param array
     * @return boolean true if something has been displayed
     */
   
function footer_text( $params )
    {
       
// Make sure we are not missing any param:
       
$params = array_merge( array(
               
'before'      => ' ',
               
'after'       => ' ',
            ),
$params );

       
$text = $this->get_setting( 'blog_footer_text' );
       
$text = preg_replace_callback( '~\$([a-z]+)\$~', array( $this, 'replace_callback' ), $text );

        if( empty(
$text) )
        {
            return
false;
        }

        echo
$params['before'];
        echo
$text;
        echo
$params['after'];

        return
true;
    }


   
/**
     * Get URL of message form to contact the owner
     *
     * @param boolean do we want to redirect back to where we came from after message?
     */
   
function get_contact_url( $with_redirect = false )
    {
       
$owner_User = & $this->get_owner_User();
        if( !
$owner_User->get_msgform_possibility() )
        {
// user does not allow contact form
           
return NULL;
        }

       
$blog_contact_url = $this->get( 'msgformurl', array(
               
'url_suffix' => 'recipient_id='.$owner_User->ID
           
) );

        if(
$with_redirect )
        {
            if(
param( 'redirect_to', 'url', NULL ) !== NULL )
            {    
// Use current redirect URL:
               
$redirect_to = get_param( 'redirect_to' );
            }
            elseif(
$owner_User->get_msgform_possibility() != 'login' )
            {    
// The URL will be made relative on the next page (this is needed when $htsrv_url is on another domain! -- multiblog situation )
               
$redirect_to = regenerate_url( '', '', '', '&' );
            }
            else
            {    
// no email option - try to log in and send private message (only registered users can send PM):
               
$redirect_to = url_add_param( $this->gen_blogurl(), 'disp=msgform', '&' );
            }
           
$blog_contact_url = url_add_param( $blog_contact_url, 'redirect_to='.rawurlencode( $redirect_to ) );
        }

        return
$blog_contact_url;
    }


   
/**
     * Get aggregate coll IDs
     *
     * @return integer|NULL|array
     *  - current blog ID if no aggreation
     *  - NULL if aggreagation of all blogs
     *  - array of aggreagated blog IDs
     */
   
function get_aggregate_coll_IDs()
    {
       
$aggregate_coll_IDs = $this->get_setting( 'aggregate_coll_IDs' );

        if( empty(
$aggregate_coll_IDs ) || $aggregate_coll_IDs == '-' )
        {
// No aggregation, return only the current blog:
           
return $this->ID;
        }
        elseif(
$aggregate_coll_IDs == '*' )
        {
// Aggregation of all blogs
           
return NULL;
        }

        return
explode( ',', $aggregate_coll_IDs );
    }


   
/**
     * Get SQL expression to match the list of aggregates collection IDs.
     *
     * This resolves as follows:
     *  - empty: current blog only
     *  - "*": all blogs (returns " 1 " as in "WHERE 1")
     *  - other: as present in DB
     *
     * @param string SQL field name
     * @param string Force current blog setting 'aggregate_coll_IDs'
     * @return string e.g. "$field IN (1,5)". It will return " 1 ", when all blogs/cats are aggregated.
     */
   
function get_sql_where_aggregate_coll_IDs( $field, $force_coll_IDs = NULL )
    {
        if(
is_null( $force_coll_IDs ) )
        {
// Use collection IDs from blog setting
           
$aggregate_coll_IDs = $this->get_setting( 'aggregate_coll_IDs' );
        }
        else
        {
// Force collections IDs
           
$aggregate_coll_IDs = $force_coll_IDs;
        }

        if( empty(
$aggregate_coll_IDs ) || $aggregate_coll_IDs == '-' )
        {
// We only want posts from the current blog:
           
return ' '.$field.' = '.$this->ID.' ';
        }
        elseif(
$aggregate_coll_IDs == '*' )
        {
// We are aggregating all blogs
           
return ' 1 ';
        }
        else
        {
// We are aggregating posts from several blogs:
           
global $DB;
            return
' '.$field.' IN ( '.$DB->quote( explode( ',', $aggregate_coll_IDs ) ).' )';
        }
    }


   
/**
     * Get # of posts for a given tag
     */
   
function get_tag_post_count( $tag )
    {
        global
$DB;

       
$sql = 'SELECT COUNT(DISTINCT itag_itm_ID)
                          FROM T_items__tag INNER JOIN T_items__itemtag ON itag_tag_ID = tag_ID
                                      INNER JOIN T_postcats ON itag_itm_ID = postcat_post_ID
                                      INNER JOIN T_categories ON postcat_cat_ID = cat_ID
                         WHERE cat_blog_ID = '
.$this->ID.'
                              AND tag_name = '
.$DB->quote( utf8_strtolower($tag) );

        return
$DB->get_var( $sql );

    }


   
/**
     * Get the number of items in this collection
     * Note: It counts all items with any kind of visibility status
     *
     * @return integer the number of items
     */
   
function get_number_of_items()
    {
        global
$DB;

       
$sql = 'SELECT COUNT(post_ID)
                FROM T_items__item WHERE post_main_cat_ID IN (
                    SELECT cat_ID FROM T_categories
                    WHERE cat_blog_ID = '
.$this->ID.' )';

        return
$DB->get_var( $sql );
    }


   
/**
     * Get the number of comments in this collection
     * Note: It counts all comments with any kind of visibility status, except comments in trash
     *
     * @return integer the number of not recycled comments
     */
   
function get_number_of_comments()
    {
        global
$DB;

       
$sql = 'SELECT COUNT(comment_ID)
                FROM T_comments
                LEFT JOIN T_items__item ON comment_item_ID = post_ID
                WHERE comment_status != "trash" AND post_main_cat_ID IN (
                    SELECT cat_ID FROM T_categories
                    WHERE cat_blog_ID = '
.$this->ID.' )';

        return
$DB->get_var( $sql );
    }


   
/**
     * Get the corresponding ajax form enabled setting
     *
     * @return boolean true if ajax form is enabled, false otherwise
     */
   
function get_ajax_form_enabled()
    {
        if(
is_logged_in() )
        {
            return
$this->get_setting( 'ajax_form_loggedin_enabled' );
        }
        return
$this->get_setting( 'ajax_form_enabled' );
    }


   
/**
     * Get timestamp value from the setting "timestamp_min"
     *
     * @return string
     */
   
function get_timestamp_min()
    {
       
$timestamp_min = $this->get_setting( 'timestamp_min' );

        switch (
$timestamp_min )
        {
            case
'duration': // Set timestamp to show past posts after this date
               
$timestamp_value = time() - $this->get_setting( 'timestamp_min_duration' );
                break;
            case
'no': // Don't show past posts
               
$timestamp_value = 'now';
                break;
            case
'yes': // Show all past posts
           
default:
               
$timestamp_value = '';
                break;
        }

        return
$timestamp_value;
    }


   
/**
     * Get timestamp value from the setting "timestamp_max"
     *
     * @return string
     */
   
function get_timestamp_max()
    {
       
$timestamp_max = $this->get_setting( 'timestamp_max' );

        switch (
$timestamp_max )
        {
            case
'duration': // Set timestamp to show future posts before this date
               
$timestamp_value = time() + $this->get_setting( 'timestamp_max_duration' );
                break;
            case
'yes': // Show all future posts
               
$timestamp_value = '';
                break;
            case
'no': // Don't show future posts
           
default:
               
$timestamp_value = 'now';
                break;
        }

        return
$timestamp_value;
    }


   
/**
     * Get url to write a new Post
     *
     * @param integer Category ID
     * @param string Post title
     * @param string Post urltitle
     * @param string Post type usage
     * @return string Url to write a new Post
     */
   
function get_write_item_url( $cat_ID = 0, $post_title = '', $post_urltitle = '', $post_type_usage = '' )
    {
       
$url = '';

        if(
is_logged_in( false ) ||
            ( !
is_logged_in() && $this->get_setting( 'post_anonymous' ) ) )
        {    
// Only logged in and activated users can write a Post,
            // Or anonymous user can post if it is allowed with collection setting:
           
$ChapterCache = & get_ChapterCache();
           
$selected_Chapter = $ChapterCache->get_by_ID( $cat_ID, false, false );
            if(
$selected_Chapter &&
                (
$selected_Chapter->get( 'lock' ) ||
                  (
$cat_ItemType = & $selected_Chapter->get_ItemType() ) === false ) )
            {    
// Don't allow to create new post with this category if it is locked or no default item type for the category:
               
return '';
            }

            if( !
is_logged_in() || check_user_perm( 'blog_post_statuses', 'edit', false, $this->ID ) )
            {    
// We have permission to add a post with at least one status:
               
if( $this->get_setting( 'in_skin_editing' ) && ! is_admin_page() )
                {    
// We have a mode 'In-skin editing' for the current Blog
                    // User must have a permission to publish a post in this blog
                   
$cat_url_param = '';
                    if(
$selected_Chapter )
                    {    
// Link to create a Item with predefined category
                       
$cat_url_param = '&amp;cat='.$selected_Chapter->ID;
                        if( empty(
$post_type_usage ) &&
                            (
$cat_ItemType = & $selected_Chapter->get_ItemType() ) )
                        {    
// Use predefined Item Type from selected category:
                           
$cat_url_param .= '&amp;item_typ_ID='.$cat_ItemType->ID;
                        }
                    }
                   
$url = url_add_param( $this->get( 'url' ), ( is_logged_in() ? 'disp=edit' : 'disp=anonpost' ).$cat_url_param );
                }
                elseif(
check_user_perm( 'admin', 'restricted' ) )
                {    
// Edit a post from Back-office
                   
global $admin_url;
                   
$url = $admin_url.'?ctrl=items&amp;action=new&amp;blog='.$this->ID;
                    if( !empty(
$cat_ID ) )
                    {    
// Add category param to preselect category on the form
                       
$url = url_add_param( $url, 'cat='.$cat_ID );
                    }
                }
                else
                {
// User does not have any means to edit the post!
                   
return '';
                }

                if( !empty(
$post_title ) )
                {
// Append a post title
                   
$url = url_add_param( $url, 'post_title='.urlencode( $post_title ) );
                }
                if( !empty(
$post_urltitle ) )
                {
// Append a post urltitle
                   
$url = url_add_param( $url, 'post_urltitle='.urlencode( $post_urltitle ) );
                }
                if( ! empty(
$post_type_usage ) )
                {
// Append a post type ID
                   
global $DB;
                   
$post_type_ID = $DB->get_var( 'SELECT ityp_ID FROM T_items__type WHERE ityp_usage = '.$DB->quote( $post_type_usage ) );
                    if( ! empty(
$post_type_ID ) )
                    {
                       
$url = url_add_param( $url, 'item_typ_ID='.$post_type_ID );
                    }
                }
            }
        }

        return
$url;
    }


   
/**
     * Get url to create a new Chapter
     *
     * @param integer Parent category ID
     * @return string Url to create a new Chapter
     */
   
function get_create_chapter_url( $cat_ID = 0 )
    {
       
$url = '';

        if(
is_logged_in( false ) )
        {    
// Only logged in and activated users can write a Post
           
if( check_user_perm( 'admin', 'restricted' ) &&
               
check_user_perm( 'blog_cats', 'edit', false, $this->ID ) )
            {    
// Check permissions to create a new chapter in this blog
               
global $admin_url;
               
$url = $admin_url.'?ctrl=chapters&amp;action=new&amp;blog='.$this->ID;
                if( ! empty(
$cat_ID ) )
                {    
// Add category param to preselect category on the form:
                   
$url = url_add_param( $url, 'cat_parent_ID='.$cat_ID );
                }
                if( !
is_admin_page() )
                {    
// Add this param to redirect after saving to parent category permanent url:
                   
$url = url_add_param( $url, 'redirect_page=parent' );
                }
            }
        }

        return
$url;
    }


   
/**
     * Has current user an access to this collection depending on settings
     *
     * @return boolean TRUE on success
     */
   
function has_access( $User = NULL )
    {
        global
$current_User;

        if(
is_null( $User ) )
        {
           
$User = $current_User;
        }

       
$allow_access = $this->get_setting( 'allow_access' );

        if(
$allow_access == 'public' )
        {    
// Everyone has an access to this collection:
           
return true;
        }

        if( !
is_logged_in() )
        {    
// Only logged in users have an access to this collection:
           
return false;
        }
        elseif(
$allow_access == 'members' && ! $User->check_perm( 'blog_ismember', 'view', false, $this->ID ) )
        {    
// Current user must be a member of this collection:
           
return false;
        }

        return
true;
    }


   
/**
     * Check if current user has access to this blog depending on settings
     *
     * @return boolean TRUE on success
     */
   
function check_access()
    {
        global
$Messages, $skins_path, $ads_current_skin_path, $ReqURL, $disp;

       
$allow_access = $this->get_setting( 'allow_access' );

        if(
$allow_access == 'public' )
        {
// Everyone has an access to this blog
           
return true;
        }

        if(
in_array( $disp, array( 'login', 'lostpassword', 'register', 'help', 'msgform', 'access_requires_login', 'content_requires_login' ) ) )
        {
// Don't restrict these pages
           
return true;
        }

       
/**
         * $allow_access == 'users' || 'members'
         */
       
if( ! is_logged_in() )
        {
// Only logged in users have an access to this blog
           
$Messages->add( T_( 'You need to log in before you can access this section.' ), 'error' );

           
$login_Blog = & get_setting_Blog( 'login_blog_ID' );
            if(
$login_Blog && $login_Blog->ID != $this->ID )
            {    
// If this collection is not used for login actions,
                // Redirect to login form on "access_requires_login.main.php":
               
header_redirect( get_login_url( 'no access to blog', NULL, false, NULL, 'access_requires_loginurl' ), 302 );
               
// will have exited
           
}
            else
            {    
// This collection is used for login actions
                // Don't redirect, just display a login form of this collection:
               
$disp = 'access_requires_login';
               
// Set redirect_to param to current url in order to display a requested page after login action:
               
global $ReqURI;
               
param( 'redirect_to', 'url', $ReqURI );
            }
        }
        elseif(
$allow_access == 'members' )
        {
// Check if current user is member of this blog
           
if( ! check_user_perm( 'blog_ismember', 'view', false, $this->ID ) )
            {
// Force disp to restrict access for current user
               
$disp = 'access_denied';

               
$Messages->add( T_( 'You are not a member of this section, therefore you are not allowed to access it.' ), 'error' );

               
$blog_skin_ID = $this->get_skin_ID();
                if( ! empty(
$blog_skin_ID ) )
                {
                   
$SkinCache = & get_SkinCache();
                   
$Skin = & $SkinCache->get_by_ID( $blog_skin_ID );
                   
$ads_current_skin_path = $skins_path.$Skin->folder.'/';

                   
// Use 'access_denied.main.php' instead of real template when current User is not a member of this blog:
                   
$skin_template_name = $ads_current_skin_path.'access_denied.main.php';
                    if(
file_exists( $skin_template_name ) )
                    {
// Display a special template of this skin
                       
require $skin_template_name;
                        exit;
                    }
                    else
                    {
// Display a template from site skins
                       
siteskin_init();
                       
siteskin_include( '_access_denied_site.main.php' );
                        exit;
                    }
                }
            }
        }

        return
true;
    }


   
/**
     * Check if the media directory or it's location was changed and perform the required data migration
     *
     * @param string the media directory path before update
     * @param string the media directory location before update
     * @return boolean true if the media directory was not changed or the change was successful, false otherwise
     */
   
function check_media_dir_change( $old_media_dir, $old_media_location = NULL )
    {
        global
$Messages;

       
$new_media_dir = $this->get_media_dir( false );
        if(
$new_media_dir == $old_media_dir )
        {
// The media dir was not changed, no need fo further updates
           
return true;
        }

       
$new_media_location = $this->get( 'media_location' );
        if(
$old_media_location == NULL )
        {
// Media location was not changed
           
$old_media_location = $new_media_location;
        }

        switch(
$new_media_location )
        {
            case
'none':
                if(
is_empty_directory( $old_media_dir ) )
                {
// Delete old media dir if it is empty
                   
if( file_exists( $old_media_dir ) && ( ! rmdir_r( $old_media_dir ) ) )
                    {
                       
$Messages->add( T_('The old media dir could not be removed, please remove it manually!'), 'warning' );
                    }
                }
                else
                {
// The old media dir is not empty, but it must be cleared before it can be changed to none
                   
$Messages->add( T_('Blog media folder is not empty, you cannot change it to "None".'), 'error' );
                    return
false;
                }
                break;

            case
'default':
            case
'subdir':
            case
'custom':
                global
$media_path;
                if(
file_exists( $new_media_dir ) )
                {
// Don't use the existing folder twice
                   
$Messages->add( sprintf( T_('Folder %s already exists, it cannot be used for several media locations.'), '<b>'.$new_media_dir.'</b>' ), 'error' );
                    return
false;
                }
                if(
in_array( trim( $new_media_dir, '/\\' ), array( $media_path.'blogs', $media_path.'import', $media_path.'shared', $media_path.'users' ) ) )
                {
// Don't use the reserved paths
                   
$Messages->add( sprintf( T_('Please use another folder name, because %s is reserved.'), '<b>'.$new_media_dir.'</b>' ), 'error' );
                    return
false;
                }
                if(
$new_media_location == 'custom' )
                {
// Check for folder is not used by other blog, and it is not a sub-folder of other blog folder
                   
$BlogCache = & get_BlogCache();
                   
$BlogCache->clear( true );
                   
$BlogCache->load_where( 'blog_ID != '.$this->ID );
                   
$other_blog_IDs = $BlogCache->get_ID_array();
                    foreach(
$other_blog_IDs as $other_blog_ID )
                    {
                       
$other_Blog = & $BlogCache->get_by_ID( $other_blog_ID, false, false );
                       
$other_media_dir = $other_Blog->get_media_dir( false );
                        if( ! empty(
$other_media_dir ) && strpos( $new_media_dir, $other_media_dir ) === 0 )
                        {
                           
$Messages->add( sprintf( T_('Please use another folder name, because %s is already used for another media location.'), '<b>'.$new_media_dir.'</b>' ), 'error' );
                            return
false;
                        }
                    }
                }
                if( (
$old_media_location == 'none' ) || ( ! file_exists( $old_media_dir ) ) )
                {
// The media folder was not used before, create the new media folder
                   
return $this->get_media_dir( true );
                }
                if(
copy_r( $old_media_dir, $new_media_dir, '', array( '_evocache', '.evocache' ) ) )
                {
// The file have been copied to new folder successfully
                   
if( ! rmdir_r( $old_media_dir ) )
                    {
// Display a warning if old folder could not be deleted
                       
$Messages->add( sprintf( T_('Could not delete the old media folder "%s", please try to delete it manually.'), '<b>'.$old_media_dir.'</b>' ), 'warning' );
                    }
                }
                else
                {
// Display a message if some error on copying
                   
$Messages->add( sprintf( T_('Could not move the media folder content from "%s" to the new "%s" location.'), '<b>'.$old_media_dir.'</b>', '<b>'.$new_media_dir.'</b>' ), 'error' );
                    return
false;
                }
                break;

            default:
               
debug_die('Invalid media location setting received!');
        }

       
$Messages->add( T_('The media directory and all of its content were successfully moved to the new location.'), 'note' );
        return
true;
    }


   
/**
     * Get enabled item types for this collection by usage
     *
     * @param string|NULL Item type usage: 'post', 'page', 'intro-front' and etc.
     * @return array
     */
   
function get_enabled_item_types( $usage = NULL )
    {
        if( empty(
$this->ID ) )
        {    
// This is new blog, it doesn't have the enabled item types:
           
return array();
        }

        if( ! isset(
$this->enabled_item_types ) )
        {    
// Get all enabled item types by one sql query and only first time to cache result:
           
global $DB;

           
$SQL = new SQL( 'Get all enabled item types for collection #'.$this->ID );
           
$SQL->SELECT( 'itc_ityp_ID, ityp_usage' );
           
$SQL->FROM( 'T_items__type_coll' );
           
$SQL->FROM_add( 'INNER JOIN T_items__type ON itc_ityp_ID = ityp_ID' );
           
$SQL->WHERE( 'itc_coll_ID = '.$this->ID );

           
$this->enabled_item_types = $DB->get_assoc( $SQL );
        }

        if(
$usage === NULL )
        {    
// Get all item types:
           
return array_keys( $this->enabled_item_types );
        }
        else
        {    
// Restrict item types by requested usage value:
           
return array_keys( $this->enabled_item_types, $usage );
        }
    }


   
/**
     * Check if item type is enabled for this collection
     *
     * @param integer Item type ID
     * @return boolean TRUE if enabled
     */
   
function is_item_type_enabled( $item_type_ID )
    {
        if( empty(
$this->ID ) )
        {
// This is new blog, it doesn't have the enabled item types
           
return false;
        }

        return
in_array( $item_type_ID, $this->get_enabled_item_types() );
    }


   
/**
     * Check if item type can be disabled
     *
     * @param integer Item type ID
     * @param boolean TRUE to display a message about restriction
     * @return boolean TRUE if can
     */
   
function can_be_item_type_disabled( $item_type_ID, $display_message = false )
    {
        if( empty(
$this->ID ) )
        {
// This is new blog, no restriction to disable any post type
           
return true;
        }

        if(
$this->get_setting( 'default_post_type' ) == $item_type_ID )
        {
// Don't allow to disable an item type which is used as default for this collection:
           
if( $display_message )
            {
                global
$Messages;
               
$Messages->add( 'This Item type is used as the default for this collection. Thus you cannot disable it.', 'error' );
            }
            return
false;
        }

        return
true;
    }


   
/**
     * Check if this collection has items per requested Item Type
     *
     * @param integer Item Type ID
     * @return boolean TRUE if at least one Item exists with given
     */
   
function has_items_per_item_type( $item_type_ID )
    {
        if( ! isset(
$this->used_item_types ) )
        {    
// Get all Item Types which are used for Items in this Collection:
           
global $DB;

           
$coll_item_types_SQL = new SQL( 'Get all Item Types which are used for Items of Collection #'.$this->ID );
           
$coll_item_types_SQL->SELECT( 'post_ityp_ID' );
           
$coll_item_types_SQL->FROM( 'T_items__item' );
           
$coll_item_types_SQL->FROM_add( 'INNER JOIN T_categories ON post_main_cat_ID = cat_ID' );
           
$coll_item_types_SQL->WHERE( 'cat_blog_ID = '.$this->ID );
           
$coll_item_types_SQL->GROUP_BY( 'post_ityp_ID' );

           
$this->used_item_types = $DB->get_col( $coll_item_types_SQL );
        }

        return
is_array( $this->used_item_types ) && in_array( $item_type_ID, $this->used_item_types );
    }


   
/**
     * Enable default item types for the collection
     */
   
function enable_default_item_types()
    {
        if( empty(
$this->ID ) )
        {    
// Collection doesn't exist in DB yet:
           
return;
        }

        global
$DB, $cache_all_item_type_data;

        if( ! isset(
$cache_all_item_type_data ) )
        {    
// Get all item type data only first time to save execution time:
           
$cache_all_item_type_data = $DB->get_results( 'SELECT ityp_ID, ityp_usage, ityp_name, ityp_template_name FROM T_items__type' );
        }

       
// Decide what "post" item type we can enable depending on collection kind:
       
$default_post_types_by_template = array( 'recipe' );
       
$default_post_types = array( 'Post with Custom Fields', 'Child Post', 'Recipe' );
        switch(
$this->type )
        {
            case
'minisite':
               
$default_post_types[] = 'Homepage Content Tab';
                break;

            case
'main':
               
$default_post_types[] = 'Post';
               
$default_post_types[] = 'Homepage Content Tab';
                break;

            case
'photo':
               
$default_post_types[] = 'Photo Album';
                break;

            case
'forum':
               
$default_post_types = array(); // Don't install the default Item Types(see above) for forums collection.
               
$default_post_types[] = 'Forum Topic';
               
$default_post_types[] = 'Task';
                break;

            case
'manual':
               
$default_post_types[] = 'Manual Page';
                break;

            case
'group':
               
$default_post_types = array(); // Don't install the default Item Types(see above) for Tracker collection.
               
$default_post_types[] = 'Task';
                break;

            default:
// 'std'
               
$default_post_types[] = 'Post';
               
$default_post_types[] = 'Podcast Episode';
                break;
        }

       
$enable_post_types = array();
        foreach(
$cache_all_item_type_data as $item_type )
        {
            if(
$item_type->ityp_usage == 'post' &&
                (
in_array( $item_type->ityp_name, $default_post_types ) || in_array( $item_type->ityp_template_name, $default_post_types_by_template ) ) &&
                !
in_array( $item_type->ityp_ID, $enable_post_types ) )
            {    
// This "post" item type can be enabled:
               
$enable_post_types[] = $item_type->ityp_ID;
            }
        }

        if( empty(
$enable_post_types ) )
        {    
// Display a warning if we cannot find the default post types for current collection kind:
           
global $Messages;
           
$Messages->add( sprintf( T_('The expected item type(s) named %s have not been found. All item types have been enabled for this collection.'), '"'.implode( '", "', $default_post_types ).'"' ), 'warning' );
        }

       
$insert_sql = 'REPLACE INTO T_items__type_coll ( itc_ityp_ID, itc_coll_ID ) VALUES ';
       
$i = 0;
        foreach(
$cache_all_item_type_data as $item_type )
        {
            if(
$item_type->ityp_usage == 'post' &&
                ! empty(
$enable_post_types ) &&
                !
in_array( $item_type->ityp_ID, $enable_post_types ) )
            {    
// Skip this item type for current collection kind:
               
continue;
            }

            if(
$i > 0 )
            {    
// Add separator between rows:
               
$insert_sql .= ', ';
            }
           
$insert_sql .= '( '.$item_type->ityp_ID.', '.$this->ID.' )';
           
$i++;
        }

        if(
$i > 0 )
        {    
// Insert records to enable the default item types for this collection:
           
$DB->query( $insert_sql );
        }
    }
   
   
/**
     * Enable item types for duplicate collection
     */
   
function enable_duplicate_item_types()
    {
        if( empty(
$this->ID ) )
        {    
// Collection doesn't exist in DB yet:
           
return;
        }

        global
$DB, $cache_all_item_type_data;

        if( ! isset(
$cache_all_item_type_data ) )
        {    
// Get all item type data only first time to save execution time:
           
$cache_all_item_type_data = $DB->get_results( 'SELECT ityp_ID, ityp_usage, ityp_name, ityp_template_name FROM T_items__type' );
        }

       
$SQL = new SQL();
       
$SQL->SELECT( 't.ityp_ID, IF( tb.itc_ityp_ID > 0, 1, 0 ) AS type_enabled, IF( ityp_ID = '.$this->get_setting( 'default_post_type' ).', 1, 0 ) AS type_default' );
       
$SQL->FROM( 'T_items__type AS t' );
       
$SQL->FROM_add( 'LEFT JOIN T_items__type_coll AS tb ON itc_ityp_ID = ityp_ID AND itc_coll_ID = '.$this->base_collection_ID.' having type_enabled = 1 ' );

       
$base_blog_item_types = $DB->get_results($SQL->get());

       
$enable_post_types = array();

        foreach (
$base_blog_item_types as $key => $value)
        {
           
$enable_post_types[] = $value->ityp_ID;
        }

       
$insert_sql = 'REPLACE INTO T_items__type_coll ( itc_ityp_ID, itc_coll_ID ) VALUES ';
       
$i = 0;
        foreach(
$cache_all_item_type_data as $item_type )
        {
            if( !
in_array( $item_type->ityp_ID, $enable_post_types ) )
            {    
// Skip this item type for current collection kind:
               
continue;
            }

            if(
$i > 0 )
            {    
// Add separator between rows:
               
$insert_sql .= ', ';
            }
           
$insert_sql .= '( '.$item_type->ityp_ID.', '.$this->ID.' )';
           
$i++;
        }

        if(
$i > 0 )
        {    
// Insert records to enable the default item types for this collection:
           
$DB->query( $insert_sql );
        }
    }


   
/**
     * Get first Item in Main List on disp=posts
     *
     * @return object Item
     */
   
function & get_first_mainlist_Item()
    {
       
$ItemList2 = new ItemList2( $this, $this->get_timestamp_min(), $this->get_timestamp_max(), 1, 'ItemCache', 'first', 'first' );

       
// Set additional debug info prefix for SQL queries to know what code executes it:
       
$ItemList2->query_title_prefix = 'Blog->get_first_mainlist_Item()';

       
// Run the query:
       
$ItemList2->query();

       
// Get the expected Item:
       
$first_mainlist_Item = & $ItemList2->get_item();

        return
$first_mainlist_Item;
    }


   
/**
     * Get data of moderators which must be notified about new/edited comment
     *
     * @return array Array where each row is array with keys:
     *                - user_email
     *                - user_ID
     *                - notify_comment_moderation
     *                - notify_edit_cmt_moderation
     *                - notify_spam_cmt_moderation
     */
   
function get_comment_moderator_user_data()
    {
        if( ! isset(
$this->comment_moderator_user_data ) )
        {    
// Get it from DB only first time and then cache in array:
           
global $DB;

           
$SQL = new SQL( 'Get list of moderators to notify about new/edited comment of collection #'.$this->ID );
           
$SQL->SELECT( 'DISTINCT user_email, user_ID, s1.uset_value as notify_comment_moderation, s2.uset_value as notify_edit_cmt_moderation, s3.uset_value as notify_spam_cmt_moderation' );
           
$SQL->FROM( 'T_users' );
           
$SQL->FROM_add( 'LEFT JOIN T_users__usersettings AS s1 ON s1.uset_user_ID = user_ID AND s1.uset_name = "notify_comment_moderation"' );
           
$SQL->FROM_add( 'LEFT JOIN T_users__usersettings AS s2 ON s2.uset_user_ID = user_ID AND s2.uset_name = "notify_edit_cmt_moderation"' );
           
$SQL->FROM_add( 'LEFT JOIN T_users__usersettings AS s3 ON s3.uset_user_ID = user_ID AND s3.uset_name = "notify_spam_cmt_moderation"' );
           
$SQL->FROM_add( 'LEFT JOIN T_groups ON grp_ID = user_grp_ID' );
           
$SQL->WHERE( 'LENGTH( TRIM( user_email ) ) > 0' );
           
$SQL->WHERE_and( 'user_status IN ( "activated", "autoactivated", "manualactivated" )' );
           
$SQL->WHERE_and( '( grp_perm_blogs = "editall" )
                OR ( user_ID IN ( SELECT bloguser_user_ID FROM T_coll_user_perms WHERE bloguser_blog_ID = '
.$this->ID.' AND bloguser_perm_edit_cmt IN ( "anon", "lt", "le", "all" ) ) )
                OR ( grp_ID IN ( SELECT bloggroup_group_ID FROM T_coll_group_perms WHERE bloggroup_blog_ID = '
.$this->ID.' AND bloggroup_perm_edit_cmt IN ( "anon", "lt", "le", "all" ) ) )' );

           
$this->comment_moderator_user_data = $DB->get_results( $SQL );
        }

        return
$this->comment_moderator_user_data;
    }



   
/**
     * Get data of domain aliases for this collection
     *
     * @return array Array of domain aliases
     */
   
function get_url_aliases()
    {
        if( ! isset(
$this->url_aliases ) )
        {
            global
$DB;
           
$SQL = new SQL( 'Get list of URL aliases of collection #'.$this->ID );
           
$SQL->SELECT( 'cua_url_alias' );
           
$SQL->FROM( 'T_coll_url_aliases' );
           
$SQL->WHERE( 'cua_coll_ID = '.$DB->quote( $this->ID ) );
           
$this->url_aliases = $DB->get_col( $SQL->get() );
        }

        return
$this->url_aliases;
    }


   
/**
     * Get collection type title depending on access settings:
     *
     * @return string
     */
   
function get_access_title()
    {
        switch(
$this->get_setting( 'allow_access' ) )
        {
            case
'members':
                if(
$this->get( 'advanced_perms' ) )
                {
                    return
T_('Members only');
                }
                else
                {
                    return
T_('Private');
                }

            case
'users':
                return
T_('Community only');

            default:
// 'public'
               
return T_('Public');
        }
    }


   
/**
     * Get max allowed post/comment status on this collection depending on access settings
     *
     * @param string|NULL Status to check and reduce by max allowed, NULL - to use max allowed status of this collection
     * @return string
     */
   
function get_max_allowed_status( $check_status = NULL )
    {
        switch(
$this->get_setting( 'allow_access' ) )
        {
            case
'members':
                if(
$this->get( 'advanced_perms' ) )
                {
                   
$max_allowed_status = 'protected';
                }
                else
                {
                   
$max_allowed_status = 'private';
                }
                break;

            case
'users':
               
$max_allowed_status = 'community';
                break;

            default:
// 'public'
               
$max_allowed_status = 'published';
                break;
        }

        if(
$check_status === NULL )
        {    
// Don't check the requested status:
           
return $max_allowed_status;
        }

       
// Check if the requested status can be used on this collection:
       
$status_orders = get_visibility_statuses( 'ordered-index' );
       
$max_allowed_status_order = $status_orders[ $max_allowed_status ];
       
$check_status_order = $status_orders[ $check_status ];

        if(
$max_allowed_status_order >= $check_status_order )
        {    
// The requested status can be used on this collection:
           
return $check_status;
        }
        else
        {    
// Reduce the requested status by max allowed:
           
return $max_allowed_status;
        }
    }


   
/**
     * Get what statuses should be reduced by max allowed:
     *
     * @param string Max allowed status, NULL to get current
     * @return array Statuses
     */
   
function get_reduced_statuses( $max_allowed_status = NULL )
    {
        if(
$max_allowed_status === NULL )
        {    
// Get current max allowed status for posts and comments o this collection:
           
$max_allowed_status = $this->get_max_allowed_status();
        }

       
$status_orders = get_visibility_statuses( 'ordered-index' );
       
$max_allowed_status_order = $status_orders[ $max_allowed_status ];

       
// Find what statuses should be reduced by max allowed:
       
$reduced_statuses = array();
        foreach(
$status_orders as $status_key => $status_order )
        {
            if(
$max_allowed_status_order < $status_order )
            {    
// This status must be updated because the level is more than max allowed:
               
$reduced_statuses[] = $status_key;
            }
        }

        return
$reduced_statuses;
    }


   
/**
     * Get a count how much statuses of posts and comments will be reduced after new max allowed status of this collection
     *
     * @return array|boolean Array with keys 'posts' and 'comments' where each is array with key as status and value as a count | FALSE if nothing to update
     */
   
function get_count_reduced_status_data( $new_max_allowed_status )
    {
        global
$DB;

        if( empty(
$this->ID ) )
        {    
// Collection must be created before:
           
return false;
        }

       
// Get statuses that should be reduced by max allowed:
       
$reduced_statuses = $this->get_reduced_statuses( $new_max_allowed_status );

        if( empty(
$reduced_statuses ) )
        {    
// No status to update:
           
return false;
        }

       
$posts_SQL = new SQL( 'Get how much posts will be updated after new max allowed status of the collection #'.$this->ID );
       
$posts_SQL->SELECT( 'post_status, COUNT( post_ID )' );
       
$posts_SQL->FROM( 'T_items__item' );
       
$posts_SQL->FROM_add( 'INNER JOIN T_categories ON cat_ID = post_main_cat_ID' );
       
$posts_SQL->WHERE( 'cat_blog_ID = '.$this->ID );
       
$posts_SQL->WHERE_and( 'post_status IN ( '.$DB->quote( $reduced_statuses ).' )' );
       
$posts_SQL->GROUP_BY( 'post_status' );
       
$posts = $DB->get_assoc( $posts_SQL );

       
$comments_SQL = new SQL( 'Get how much comments will be updated after new max allowed status of the collection #'.$this->ID );
       
$comments_SQL->SELECT( 'comment_status, COUNT( comment_ID )' );
       
$comments_SQL->FROM( 'T_comments' );
       
$comments_SQL->FROM_add( 'INNER JOIN T_items__item ON comment_item_ID = post_ID' );
       
$comments_SQL->FROM_add( 'INNER JOIN T_categories ON cat_ID = post_main_cat_ID' );
       
$comments_SQL->WHERE( 'cat_blog_ID = '.$this->ID );
       
$comments_SQL->WHERE_and( 'comment_status IN ( '.$DB->quote( $reduced_statuses ).' )' );
       
$comments_SQL->GROUP_BY( 'comment_status' );
       
$comments = $DB->get_assoc( $comments_SQL );

        if( empty(
$posts ) && empty( $comments ) )
        {    
// All posts and comments have a correct status:
           
return false;
        }

        return array(
               
'posts'    => $posts,
               
'comments' => $comments,
            );
    }


   
/**
     * Reduce statuses of posts and comments by max allowed status of this collection
     */
   
function update_reduced_status_data()
    {
        global
$DB;

       
// Get statuses that should be reduced by max allowed:
       
$reduced_statuses = $this->get_reduced_statuses();

        if( empty(
$reduced_statuses ) )
        {    
// Nothing to update:
           
return;
        }

       
// Get max allowed status:
       
$max_allowed_status = $this->get_max_allowed_status();

       
// Update posts:
       
$DB->query( 'UPDATE T_items__item
            INNER JOIN T_categories ON cat_ID = post_main_cat_ID
              SET post_status = '
.$DB->quote( $max_allowed_status ).'
            WHERE cat_blog_ID = '
.$this->ID.'
                AND post_status IN ( '
.$DB->quote( $reduced_statuses ).' )',
           
'Reduce posts statuses by max allowed status of the collection #'.$this->ID );

       
// Update comments:
       
$DB->query( 'UPDATE T_comments
            INNER JOIN T_items__item ON comment_item_ID = post_ID
            INNER JOIN T_categories ON cat_ID = post_main_cat_ID
              SET comment_status = '
.$DB->quote( $max_allowed_status ).'
            WHERE cat_blog_ID = '
.$this->ID.'
                AND comment_status IN ( '
.$DB->quote( $reduced_statuses ).' )',
           
'Reduce comments statuses by max allowed status of the collection #'.$this->ID );
    }


   
/**
     * Load locales of this collection into cache array
     */
   
function load_locales()
    {
        if(
$this->locales === NULL && ! empty( $this->ID ) )
        {    
// Load locales from DB once and store in cache variable:
           
global $DB;
           
$SQL = new SQL( 'Get extra locales and linked collections of collection #'.$this->ID );
           
$SQL->SELECT( 'cl_locale, cl_linked_coll_ID' );
           
$SQL->FROM( 'T_coll_locales' );
           
$SQL->FROM_add( 'INNER JOIN T_locales ON loc_locale = cl_locale AND loc_enabled = 1' );
           
$SQL->WHERE( 'cl_coll_ID = '.$this->ID );
           
$SQL->ORDER_BY( 'loc_priority' );
           
$this->locales = $DB->get_assoc( $SQL );
            if( !
array_key_exists( $this->get( 'locale' ), $this->locales ) )
            {    
// Add main locale if it is not stored in extra locales by some unknown reason:
               
$this->locales[ $this->get( 'locale' ) ] = NULL;
            }
        }

        if( !
is_array( $this->locales ) )
        {    
// Set default array with main locale:
           
$this->locales = array( $this->get( 'locale' ) => NULL );
        }
    }


   
/**
     * Get locales of this collection
     *
     * @param string Type of locales:
     *        - 'locale' - locales of this collection,
     *        - 'coll' - locales as links to other collections,
     *        - 'all' - all locales of this and links with other collections,
     * @return array Array: Key is locale key, Value is ID of another collection or NULL
     */
   
function get_locales( $type = 'locale' )
    {
       
// Load all locales:
       
$this->load_locales();

        if(
$type == 'locale' || $type == 'coll' )
        {    
// Get only locales or linked collections:
           
$locales = array();
            foreach(
$this->locales as $locale => $linked_coll_ID )
            {
                if( (
$type == 'locale' && empty( $linked_coll_ID ) ) ||
                    (
$type == 'coll' && ! empty( $linked_coll_ID ) ) )
                {
                   
$locales[ $locale ] = $linked_coll_ID;
                }
            }
            return
$locales;
        }

       
// Return all locales and linked collections:
       
return $this->locales;
    }


   
/**
     * Set locales for this collection
     *
     * @param array Locales
     * @param array Linked collections
     */
   
function set_locales( $new_locales = array(), $new_linked_colls = array() )
    {
        global
$locales;

       
// Reset previous values:
       
$this->locales = array();

       
$enabled_locales = array();
        foreach(
$locales as $locale_key => $locale_data )
        {
            if(
$locale_data['enabled'] )
            {    
// If locale is enabled:
               
$enabled_locales[] = $locale_key;
            }
        }

        if(
is_array( $new_locales ) )
        {    
// Set locales:
           
foreach( $new_locales as $new_locale )
            {
                if(
in_array( $new_locale, $enabled_locales ) )
                {    
// Set only enabled locale:
                   
$this->locales[ $new_locale ] = NULL;
                }
            }
        }

        if( !
array_key_exists( $this->get( 'locale' ), $this->locales ) )
        {    
// Make sure main locale will be stored in the table of collection locales:
           
$this->locales[ $this->get( 'locale' ) ] = NULL;
        }

        if(
is_array( $new_linked_colls ) )
        {    
// Set linked collections:
           
foreach( $new_linked_colls as $locale_key => $linked_coll_ID )
            {
                if( ! empty(
$linked_coll_ID ) &&
                   
in_array( $locale_key, $enabled_locales ) &&
                    !
array_key_exists( $locale_key, $this->locales ) )
                {    
// Set collection only with enalbed locale link:
                   
$this->locales[ $locale_key ] = $linked_coll_ID;
                }
            }
        }
    }


   
/**
     * Update locales of this collection
     * and also update a linking with other collection by locales
     *
     * @return boolean TRUE on success
     */
   
function update_locales()
    {
        global
$DB;

        if( empty(
$this->ID ) || ! is_array( $this->locales ) )
        {    
// Collection must be stored in DB and new locales must be defined:
           
return false;
        }

       
// Delete all previous locales before inserting new:
       
$DB->query( 'DELETE FROM T_coll_locales
            WHERE cl_coll_ID = '
.$this->ID );

       
$sql_coll_locales = array();

        if( ! empty(
$this->locales ) )
        {    
// If at least one new locale:
           
foreach( $this->locales as $coll_locale => $linked_coll_ID )
            {
               
$sql_coll_locales[ $coll_locale ] = '( '.$this->ID.', '.$DB->quote( $coll_locale ).', '.$DB->quote( $linked_coll_ID ).' )';
            }
        }

        if( ! empty(
$sql_coll_locales ) )
        {    
// Insert new locales:
           
$DB->query( 'INSERT INTO T_coll_locales ( cl_coll_ID, cl_locale, cl_linked_coll_ID )
                VALUES '
.implode( ', ', $sql_coll_locales ) );
        }

        return
true;
    }


   
/**
     * Check if this collection has a requested locale
     *
     * @param string Locale key
     * @param string Type of locales:
     *        - 'locale' - locales of this collection,
     *        - 'coll' - locales as links to other collections,
     *        - 'all' - all locales of this and links with other collections,
     * @return boolean
     */
   
function has_locale( $locale_key, $type = 'locale' )
    {
        return
array_key_exists( $locale_key, $this->get_locales( $type ) );
    }


   
/**
     * Get a number of unread posts by current logged in User
     *
     * @return integer
     */
   
function get_unread_posts_count()
    {
        global
$DB, $current_User, $localtimenow;

        if( !
is_logged_in() || ! $this->get_setting( 'track_unread_content' ) )
        {    
// User must be logged in AND the tracking of unread content is turned ON for the collection:
           
return 0;
        }

       
// Initialize SQL query to get only the posts which are displayed by global $MainList on disp=posts:
       
$ItemList2 = new ItemList2( $this, $this->get_timestamp_min(), $this->get_timestamp_max(), NULL, 'ItemCache', 'recent_topics' );
       
$ItemList2->set_default_filters( array(
               
'unit' => 'all', // set this to don't calculate total rows
           
) );
       
$ItemList2->query_init();

       
// Get a count of the unread topics for current user:
       
$unread_posts_SQL = new SQL( 'Get a count of the unread topics of collection #'.$this->ID.' for current user' );
       
$unread_posts_SQL->SELECT( 'COUNT( post_ID )' );
       
$unread_posts_SQL->FROM( 'T_items__item' );
       
$unread_posts_SQL->FROM_add( 'LEFT JOIN T_items__user_data ON post_ID = itud_item_ID AND itud_user_ID = '.$DB->quote( $current_User->ID ) );
       
$unread_posts_SQL->FROM_add( 'INNER JOIN T_categories ON post_main_cat_ID = cat_ID' );
       
$unread_posts_SQL->FROM_add( 'LEFT JOIN T_items__type ON post_ityp_ID = ityp_ID' );
       
$unread_posts_SQL->WHERE( $ItemList2->ItemQuery->get_where( '' ) );
       
$unread_posts_SQL->WHERE_and( 'post_contents_last_updated_ts > '.$DB->quote( date2mysql( $localtimenow - 30 * 86400 ) ) );
       
// In theory, it would be more safe to use this comparison:
        // $unread_posts_SQL->WHERE_and( 'itud_item_ID IS NULL OR itud_read_item_ts <= post_contents_last_updated_ts' );
        // But until we have milli- or micro-second precision on timestamps, we decided it was a better trade-off to never see our own edits as unread. So we use:
       
$unread_posts_SQL->WHERE_and( 'itud_item_ID IS NULL OR itud_read_item_ts < post_contents_last_updated_ts' );

       
// Execute a query with to know if current user has new data to view:
       
return intval( $DB->get_var( $unread_posts_SQL ) );
    }


   
/**
     * Get recipient link on disp=msgform
     *
     * @return string
     */
   
function get_msgform_recipient_link()
    {
       
$recipient_link = '';

        if(
get_param( 'recipient_id' ) > 0 )
        {    
// Get recipient identity link for registered user:
           
$UserCache = & get_UserCache();
           
$recipient_User = & $UserCache->get_by_ID( get_param( 'recipient_id' ) );

            if(
$this->get_setting( 'msgform_display_avatar' ) )
            {    
// Display recipient name with avatar:
               
$recipient_link_params = array(
                       
'link_text'   => 'avatar_name',
                       
'thumb_size'  => $this->get_setting( 'msgform_avatar_size' ),
                       
'thumb_class' => 'avatar_before_login_middle',
                    );
            }
            else
            {    
// Display only recipient name:
               
$recipient_link_params = array( 'link_text' => 'auto' );
            }
           
$recipient_link = $recipient_User->get_identity_link( $recipient_link_params );
        }
        elseif(
get_param( 'comment_id' ) > 0 )
        {    
// Get login name for anonymous user:
           
$CommentCache = & get_CommentCache();
           
$Comment = & $CommentCache->get_by_ID( get_param( 'comment_id' ) );
           
// Set a gender class for anonymous user if the setting is enabled:
           
$gender_class = check_setting( 'gender_colored' ) ? ' nogender' : '';
           
$recipient_link = '<span class="user anonymous'.$gender_class.'" rel="bubbletip_comment_'.$Comment->ID.'">'.$Comment->get_author_name().'</span>';
        }

        return
$recipient_link;
    }


   
/**
     * Get additional fields on disp=msgform
     *
     * @return array
     */
   
function get_msgform_additional_fields()
    {
       
$msgform_additional_fields = trim( $this->get_setting( 'msgform_additional_fields' ) );

       
$saved_additional_fields = array();

        if( empty(
$msgform_additional_fields ) )
        {    
// No saved additional fields for this collection:
           
return $saved_additional_fields;
        }

       
// Get data of saved additional fields from DB by IDs:
       
$msgform_additional_fields = explode( ',', $msgform_additional_fields );
       
$UserFieldCache = & get_UserFieldCache();
        foreach(
$msgform_additional_fields as $user_field_ID )
        {
            if(
$UserField = & $UserFieldCache->get_by_ID( $user_field_ID, false, false ) )
            {
               
$saved_additional_fields[ $UserField->ID ] = $UserField;
            }
        }

        return
$saved_additional_fields;
    }


   
/**
     * Display all selected additional fields on disp=msgform
     *
     * @param object Form
     * @param array Filled user fields from submitted form
     * @return array
     */
   
function display_msgform_additional_fields( $Form, $filled_user_fields = array() )
    {
       
$msgform_additional_fields = $this->get_msgform_additional_fields();

        foreach(
$msgform_additional_fields as $UserField )
        {
           
$field_values = array();

            if( ! empty(
$filled_user_fields[ $UserField->ID ] ) )
            {    
// Get values from the submitted form:
               
if( is_array( $filled_user_fields[ $UserField->ID ] ) )
                {    
// Multiple field:
                   
$field_values = $filled_user_fields[ $UserField->ID ];
                }
                else
                {    
// Single field:
                   
$field_values = array( $filled_user_fields[ $UserField->ID ] );
                }
            }

            if(
is_logged_in() && empty( $field_values ) )
            {    
// Get saved field value from the current logged in User:
               
global $current_User;
               
$userfields = $current_User->userfields_by_ID( $UserField->ID );
                if(
is_array( $userfields ) )
                {
                    foreach(
$userfields as $userfield )
                    {
                       
$field_values[] = $userfield->uf_varchar;
                    }
                }
            }

            if( empty(
$field_values ) )
            {    
// Display at least one additional field if user is not filled that in profile yet:
               
$field_values = array( '' );
            }

            foreach(
$field_values as $f => $field_value )
            {    
// Display additional fields:
               
$this->display_msgform_additional_field( $Form, $UserField, $field_value, $f != 0 );
            }
        }
    }


   
/**
     * Display single additional field on disp=msgform
     *
     * @param object Form
     * @param object UserField
     * @param string Field value
     * @param boolean Is field duplicated
     * @return array
     */
   
function display_msgform_additional_field( $Form, $UserField, $field_value = '', $force_not_required = false )
    {
       
$field_params = array();

        if( !
$force_not_required && $UserField->get( 'required' ) == 'require' )
        {    
// Field is required and it is not a second copy of the field:
           
$field_params['required'] = true;
        }

        if(
$UserField->get( 'suggest' ) == '1' && $UserField->get( 'type' ) == 'word' )
        {    
// Mark field with this tag to suggest a values
           
$field_params['autocomplete'] = 'on';
        }

       
// Field name:
       
$field_name = 'user_fields['.$UserField->ID.']';
        if(
in_array( $UserField->get( 'duplicated' ), array( 'allowed', 'list' ) ) )
        {    
// If field can be duplicated:
           
$field_name .= '[]';
        }

        switch(
$UserField->get( 'type' ) )
        {
            case
'text':
               
$field_params['cols'] = 38;
               
$Form->textarea_input( $field_name, $field_value, 5, $UserField->get_input_label(), $field_params );
                break;

            case
'list':
               
$uf_options = explode( "\n", str_replace( "\r", '', $UserField->get( 'options' ) ) );
                if(
$UserField->get( 'required' ) != 'require' )
                {    
// Add an empty value for not required field:
                   
$uf_options = array_merge( array( '', '---' ), $uf_options );
                }
               
$Form->select_input_array( $field_name, $field_value, $uf_options, $UserField->get_input_label(), '', $field_params );
                break;

            case
'user':
                if(
is_pro() )
                {    
// Display user selector by PRO function:
                   
load_funcs( '_core/_pro_features.funcs.php' );
                   
pro_display_user_field_input( $field_name, $UserField, $field_value, '', $field_params, $Form );
                }
                break;

            default:
               
$field_params['maxlength'] = 255;
               
$field_params['style'] = 'max-width:90%';
               
$Form->text_input( $field_name, $field_value, ( $UserField->get( 'type' ) == 'url' ? 80 : 40 ), $UserField->get_input_label(), '', $field_params );
        }
    }


   
/**
     * Get default item type object
     *
     * @return object ItemType
     */
   
function & get_default_ItemType()
    {
        if( ! isset(
$this->default_ItemType ) )
        {    
// Initialize default item type object for this collection:
           
$ItemTypeCache = & get_ItemTypeCache();
           
$this->default_ItemType = & $ItemTypeCache->get_by_ID( $this->get_setting( 'default_post_type' ), false, false );
        }

        return
$this->default_ItemType;
    }


   
/**
     * Setup default widgets for this collection
     *
     * @param string Skin type: 'all', 'normal', 'mobile', 'tablet'
     * @param array Context
     */
   
function setup_default_widgets( $skin_type = 'all', $context = array() )
    {
        global
$DB;

        if( empty(
$this->ID ) )
        {    
// This function should be called only for created collection:
           
return;
        }

        if(
$skin_type == 'all' )
        {    
// Setaup default widgets for skins of all types:
           
$this->setup_default_widgets( 'normal', $context );
           
$this->setup_default_widgets( 'mobile', $context );
           
$this->setup_default_widgets( 'tablet', $context );
           
$this->setup_default_widgets( 'alt', $context );
            return;
        }

       
$coll_skin_ID = $this->get_skin_ID( $skin_type );
       
$SkinCache = & get_SkinCache();
        if( ! (
$coll_Skin = & $SkinCache->get_by_ID( $coll_skin_ID, false, false ) ) )
        {    
// This collection must has a correct skin:
           
return;
        }

       
// Get the declarations of the widgets that the skin wants to use:
       
$context['current_coll_ID'] = $this->ID;
       
$skin_widgets = $coll_Skin->get_default_widgets( $this->get( 'type' ), $skin_type, $context );

       
// Check if the skin wants to use all b2evolution default widgets:
       
if( isset( $skin_widgets['*'] ) )
        {    
// Depending on how the skin want to use this:
           
$use_all_b2evo_default_widgets = $skin_widgets['*'];
            unset(
$skin_widgets['*'] );
        }
        else
        {    
// Use all default widgets if the skin does NOT say about this:
           
$use_all_b2evo_default_widgets = true;
        }

       
load_funcs( 'widgets/_widgets.funcs.php' );

       
// Get the declarations of the widgets that b2evolution recommends by default:
       
$b2evo_default_widgets = get_default_widgets( $this->get( 'type' ), $context );

       
// Merge the skin widget declarations with b2evolution default widgets:
       
foreach( $b2evo_default_widgets as $container_code => $widgets )
        {
            if(
// The skin has no widget declarations for this container and it allows to use b2evolution default widgets:
               
( ! isset( $skin_widgets[ $container_code ] ) &&
                 
$use_all_b2evo_default_widgets )
                ||
               
// The skin wants to use default widget declarations for this container:
               
( isset( $skin_widgets[ $container_code ] ) &&
                 
$skin_widgets[ $container_code ] === true ) )
            {    
// Merge widgets from b2evolution default declarations to the skin:
               
$skin_widgets[ $container_code ] = $widgets;
            }
            elseif( isset(
$skin_widgets[ $container_code ] ) &&
                   
is_array( $skin_widgets[ $container_code ] ) )
            {    
// Append custom skin widgets to default widgets:
               
$skin_widgets[ $container_code ] = array_merge( $widgets, $skin_widgets[ $container_code ] );
            }
        }

       
// Check all skin widget containers to be sure they all are proper arrays and not other values like true, false and etc.:
       
foreach( $skin_widgets as $container_code => $container_widgets )
        {
            if( ! empty(
$container_widgets['type'] ) &&
                !
in_array( $container_widgets['type'], array( 'main', 'sub', 'page' ) ) )
            {    
// Skip not collection container:
               
unset( $skin_widgets[ $container_code ] );
                continue;
            }
            if( isset(
$container_widgets['coll_type'] ) &&
                !
is_allowed_option( $this->get( 'type' ), $container_widgets['coll_type'] ) )
            {    
// Skip container because it should not be installed for the given collection kind:
               
unset( $skin_widgets[ $container_code ] );
                continue;
            }

            if( isset(
$container_widgets['skin_type'] ) &&
                !
is_allowed_option( $skin_type, $container_widgets['skin_type'] ) )
            {    
// Skip container because it should not be installed for the given skin type:
               
unset( $skin_widgets[ $container_code ] );
                continue;
            }

            if(
$container_widgets === true )
            {    
// Set empty container if it has not been detected in b2evolution default widget declarations above:
               
$skin_widgets[ $container_code ] = array();
            }
            elseif( !
is_array( $container_widgets ) )
            {    
// Ignore all other not array, probably it is a boolean false or some other string value which should be ignored indeed:
               
unset( $skin_widgets[ $container_code ] );
            }
        }

        if( empty(
$skin_widgets ) )
        {    
// No skin default widgets:
           
return;
        }

       
// Get all containers declared in this collection skin type:
       
$blog_containers = $this->get_skin_containers( $skin_type );

       
// Install additional sub-containers and page containers from default config,
        // which are not declared as main containers but should be installed too:
       
foreach( $skin_widgets as $container_code => $container_widgets )
        {
            if( isset(
$container_widgets['type'] ) &&
                (
$container_widgets['type'] == 'sub' || $container_widgets['type'] == 'page' ) )
            {    
// If it is a sub-container or page container:
               
$blog_containers[ $container_code ] = array(
                        isset(
$container_widgets['name'] ) ? $container_widgets['name'] : $container_code,
                        isset(
$container_widgets['order'] ) ? $container_widgets['order'] : 1,
                        (
$container_widgets['type'] == 'sub' ? 0 : 1 ), // Main or Sub-container
                       
isset( $container_widgets['item_ID'] ) ? $container_widgets['item_ID'] : NULL,
                    );
            }
        }

       
// Create rows to insert for all collection containers:
       
$widget_containers_sql_rows = array();
        foreach(
$blog_containers as $container_code => $wico_data )
        {
           
$widget_containers_sql_rows[] = '( '.$DB->quote( $container_code ).', '
               
.$DB->quote( $skin_type ).', '
               
.$DB->quote( $wico_data[0] ).', '
               
.$this->ID.', '
               
.$DB->quote( $wico_data[1] ).', '
               
.( isset( $wico_data[2] ) ? intval( $wico_data[2] ) : '1' ).', '
               
.( isset( $wico_data[3] ) ? intval( $wico_data[3] ) : 'NULL' ).' )';
        }

        if( empty(
$widget_containers_sql_rows ) )
        {    
// No collection containers to install:
           
return;
        }

       
// Insert widget containers records by one SQL query:
       
$DB->query( 'INSERT INTO T_widget__container ( wico_code, wico_skin_type, wico_name, wico_coll_ID, wico_order, wico_main, wico_item_ID  ) VALUES'
           
.implode( ', ', $widget_containers_sql_rows ) );

       
$insert_id = $DB->insert_id;
        foreach(
$blog_containers as $container_code => $wico_data )
        {
           
$blog_containers[ $container_code ]['wico_ID'] = $insert_id;
           
$insert_id++;
        }

       
$basic_widgets_insert_sql_rows = array();
        foreach(
$skin_widgets as $container_code => $container_widgets )
        {
            if( ! isset(
$blog_containers[ $container_code ] ) )
            {    
// Skip container which is not supported by current collection's skin:
               
continue;
            }

           
$wico_id = $blog_containers[ $container_code ]['wico_ID'];

            foreach(
$container_widgets as $key => $widget )
            {
                if( !
is_number( $key ) )
                {    
// Skip the config data which is used as additional info for container like 'type', 'name', 'order', 'item_ID', 'coll_type':
                   
continue;
                }

                if( isset(
$widget['install'] ) && ! $widget['install'] )
                {    
// Skip widget because it should not be installed by condition from config:
                   
continue;
                }

                if( isset(
$widget['coll_type'] ) && ! is_allowed_option( $this->get( 'type' ), $widget['coll_type'] ) )
                {    
// Skip widget because it should not be installed for the given collection kind:
                   
continue;
                }

                if( isset(
$widget['is_pro'] ) && $widget['is_pro'] !== is_pro() )
                {    
// Skip widget because it should not be installed for the current version:
                   
continue;
                }

                if( isset(
$widget['coll_ID'] ) && ! is_allowed_option( $this->ID, $widget['coll_ID'] ) )
                {    
// Skip widget because it should not be installed for the given collection ID:
                   
continue;
                }

                if( isset(
$widget['skin_type'] ) && ! is_allowed_option( $skin_type, $widget['skin_type'] ) )
                {    
// Skip widget because it should not be installed for the given skin type:
                   
continue;
                }

               
// Initialize a widget row to insert into DB below by single query:
               
$widget_type = isset( $widget['type'] ) ? $widget['type'] : 'core';
               
$widget_params = isset( $widget['params'] ) ? ( is_array( $widget['params'] ) ? serialize( $widget['params'] ) : $widget['params'] ) : NULL;
               
$widget_enabled = isset( $widget['enabled'] ) ? intval( $widget['enabled'] ) : 1;
               
$basic_widgets_insert_sql_rows[] = '( '.$wico_id.', '.$widget[0].', '.$widget_enabled.', '.$DB->quote( $widget_type ).', '.$DB->quote( $widget[2] ).', '.$DB->quote( $widget_params ).' )';
            }
        }

       
// Check if there are widgets to create:
       
if( ! empty( $basic_widgets_insert_sql_rows ) )
        {    
// Insert the widget records by single SQL query:
           
$DB->query( 'INSERT INTO T_widget__widget ( wi_wico_ID, wi_order, wi_enabled, wi_type, wi_code, wi_params ) '
                                 
.'VALUES '.implode( ', ', $basic_widgets_insert_sql_rows ) );
        }
    }


   
/**
     * Get name of default item type
     *
     * @return string Name of default ItemType object
     */
   
function get_default_item_type_name()
    {
       
$default_ItemType = $this->get_default_ItemType();

        return
$default_ItemType ? $default_ItemType->get_name() : T_('Post');
    }


   
/**
     * Get URL with protocol which is defined by collection setting "SSL"
     *
     * @param string Requested URL
     * @param string Forced URL
     */
   
function get_protocol_url( $url )
    {
        if( empty(
$this->ID ) )
        {
            return
$url;
        }

        switch(
$this->get_setting( 'http_protocol' ) )
        {    
// Force URL's protocol depending on collection setting "SSL":
           
case 'always_http':
               
$url = preg_replace( '#^https://#', 'http://', $url );
                break;
            case
'always_https':
               
$url = preg_replace( '#^http://#', 'https://', $url );
                break;
        }

        return
$url;
    }


   
/**
     * Get default new item type based on the collection's default category or current working category
     *
     * @return object|false ItemType object, false if default item type is disabled
     */
   
function & get_default_new_ItemType()
    {
        global
$cat;

       
// Get a working category:
       
$working_cat = $cat;
        if( empty(
$cat ) )
        {    
// Use default collection category when global category is not defined:
           
$working_cat = $this->get_default_cat_ID();
        }

       
$ChapterCache = & get_ChapterCache();
       
$working_Chapter = & $ChapterCache->get_by_ID( $working_cat, false, false );

        if( !
$working_Chapter ||
            ( (
$working_cat_ItemType = & $working_Chapter->get_ItemType() ) === false ) )
        {    
// The working category is not detected in DB or it has no default Item Type:
           
$r = false;
            return
$r;
        }

        if(
$working_cat_ItemType === NULL )
        {    
// If the working category uses the same as collection default:
           
$coll_default_ItemType = $this->get_default_ItemType();
            return
$coll_default_ItemType;
        }

       
// If the working category uses a custom Item Type:
       
return $working_cat_ItemType;
    }


   
/**
     * Get default item denomination depending on current collection type
     *
     * @param string Position where denomination will be used, can be one of the following: 'evobar_new', 'inskin_new_btn', 'title_new', 'title_update'
     * @return string Item denomination
     */
   
function get_item_denomination( $position = 'evobar_new' )
    {
        switch(
$this->get( 'type' ) )
        {
            case
'photo':
               
$denominations = array(
                       
'evobar_new'     => T_('Album'),
                       
'inskin_new_btn' => T_('New album'),
                       
'title_new'      => T_('New album'),
                       
'title_updated'  => T_('Updated album'),
                    );
                break;

            case
'group':
               
$denominations = array(
                       
'evobar_new'     => T_('Task'),
                       
'inskin_new_btn' => T_('New task'),
                       
'title_new'      => T_('New task'),
                       
'title_updated'  => T_('Updated task'),
                    );
                break;

            case
'forum':
               
$denominations = array(
                       
'evobar_new'     => T_('Topic'),
                       
'inskin_new_btn' => T_('New topic'),
                       
'title_new'      => T_('New topic'),
                       
'title_updated'  => T_('Updated topic'),
                    );
                break;

            case
'manual':
               
$denominations = array(
                       
'evobar_new'     => T_('Page'),
                       
'inskin_new_btn' => T_('New page'),
                       
'title_new'      => T_('New page'),
                       
'title_updated'  => T_('Updated page'),
                    );
                break;

            default:
               
$denominations = array(
                       
'evobar_new'     => T_('Post'),
                       
'inskin_new_btn' => T_('New post'),
                       
'title_new'      => T_('New post'),
                       
'title_updated'  => T_('Updated post'),
                    );
        }

        return isset(
$denominations[ $position ] ) ? $denominations[ $position ] : '';
    }


   
/**
     * Get last touched date of content in this collection
     *
     * @param string Date/Time format: leave empty to use locale default date format, use FALSE to don't format
     * @param boolean TRUE if you want GMT
     * @return string Last touched date
     */
   
function get_last_touched_date( $format = false, $useGM = false )
    {
        if( empty(
$this->ID ) )
        {    
// Collection must be saved in DB:
           
return false;
        }

        if( ! isset(
$this->last_touched_date ) )
        {    
// Load last touched date from DB:
           
global $DB;
           
$SQL = new SQL( 'Get last touched date for collection #'.$this->ID );
           
$SQL->SELECT( 'cat_last_touched_ts' );
           
$SQL->FROM( 'T_categories' );
           
$SQL->WHERE( 'cat_blog_ID = '.$this->ID );
           
$SQL->ORDER_BY( 'cat_last_touched_ts DESC' );
           
$SQL->LIMIT( '1' );
           
// Store date in cache:
           
$this->last_touched_date = $DB->get_var( $SQL );
        }

        if(
$format === false )
        {    
// Don't format:
           
return $this->last_touched_date;
        }

        if( empty(
$format ) )
        {    
// Use format of current locale:
           
$format = locale_datefmt();
        }

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


   
/**
     * Get a marketing popup container code if it is enabled for current requested page
     *
     * @return string|boolean Container code, FALSE if marketing popup is not enabled
     */
   
function get_marketing_popup_container()
    {
        if(
is_admin_page() )
        {    
// Don't display on back-office:
           
return false;
        }

        if(
$this->get_setting( 'marketing_popup_using' ) == 'never' )
        {    
// Marketing popup is disabled for all users:
           
return false;
        }

        if(
is_logged_in() && $this->get_setting( 'marketing_popup_using' ) == 'anonymous' )
        {    
// Marketing popup is enabled only for anonymous users:
           
return false;
        }

       
// Get widget container code depending on current disp:
       
global $disp;
       
$container_disp = in_array( $disp, array( 'front', 'posts', 'single', 'page', 'catdir' ) ) ? $disp : 'other_disps';
       
$container_code = $this->get_setting( 'marketing_popup_container_'.$container_disp );

        if( empty(
$container_code ) )
        {    
// Don't try to find a widget container without code:
           
return false;
        }

       
// Check if widget container realy exists in DB for current collection or it is a shared container:
       
$WidgetContainerCache = & get_WidgetContainerCache();
       
$WidgetContainer = & $WidgetContainerCache->get_by_coll_skintype_code( $this->ID, $this->get_skin_type(), $container_code );

        return
$WidgetContainer ? $container_code : false;
    }


   
/**
     * Initialize JS & CSS for marketing popup container
     */
   
function init_marketing_popup_container()
    {
       
$marketing_popup_container_code = $this->get_marketing_popup_container();
        if(
$marketing_popup_container_code )
        {    
// If marketing popup is enabled for current page and user:
            // Load CSS right in the current calling place:
           
require_css( 'ddexitpop.bmin.css', 'blog', NULL, NULL, '#', true );
           
// Initialize JS code to display a marketing popup:
           
$marketing_popup_show_frequency = $this->get_setting( 'marketing_popup_show_frequency' );
            if(
$marketing_popup_show_frequency == 'period' )
            {    
// Use period frequency like X hours or X days:
               
$marketing_popup_show_frequency = $this->get_setting( 'marketing_popup_show_period_val' )
                    .
$this->get_setting( 'marketing_popup_show_period_unit' );
            }
           
expose_var_to_js( 'evo_ddexitpop_config', array(
               
'container_code' => $marketing_popup_container_code,
               
'animation'      => $this->get_setting( 'marketing_popup_animation' ),
               
'show_repeat'    => (boolean)$this->get_setting( 'marketing_popup_show_repeat' ),
               
'show_frequency' => $marketing_popup_show_frequency,
            ) );
           
require_js_defer( 'build/ddexitpop.bmin.js', 'blog', false, '#', 'footerlines' );
        }
    }


   
/**
     * Get a count of must read items of this collection
     *
     * @param string 'unread' - Items which are not read by current User yet,
     *               'read' - Items which are already read by current User,
     *               'all' - All(Read and Unread) items with "must read" flag.
     * @return integer
     */
   
function get_mustread_items_count( $read_status = 'all' )
    {
        if( !
$this->get_setting( 'track_unread_content' ) )
        {    
// No must read items when tracking of unread content is enabled for this collection:
           
return 0;
        }

        if( !
in_array( $read_status, array( 'all', 'unread', 'read' ) ) )
        {    
// Ignore wrong param value:
           
return 0;
        }

        if( ! isset(
$this->mustread_items_count ) )
        {    
// Initialize array for cache:
           
$this->mustread_items_count = array();
        }

        if( ! isset(
$this->mustread_items_count[ $read_status ] ) )
        {    
// Get it from DB only first time and then cache in var:
           
$mustread_ItemList2 = new ItemList2( $this, $this->get_timestamp_min(), $this->get_timestamp_max() );

           
// Set additional debug info prefix for SQL queries in order to know what code executes it:
           
$mustread_ItemList2->query_title_prefix = 'Must Read Items (status = '.$read_status.')';

           
// Filter only the flagged items:
           
$mustread_ItemList2->set_default_filters( array(
                   
'itemtype_usage' => 'post,page,intro-front,intro-main,intro-cat,intro-tag,intro-sub,intro-all',
                   
'mustread' => $read_status
               
) );

           
// Run query initialization to get total rows:
           
$mustread_ItemList2->query_init();

           
$this->mustread_items_count[ $read_status ] = $mustread_ItemList2->total_rows;
        }

        return
$this->mustread_items_count[ $read_status ];
    }


   
/**
     * Get a selector to link this collection with other collections
     * which have same main or extra locale as requested
     *
     * @param string Field name
     * @param string Locale to search other collections
     * @return string
     */
   
function get_link_locale_selector( $field_name, $locale, $restrict_used_locales = true )
    {
        global
$DB;

        if(
$restrict_used_locales &&
            (
$this->get( 'locale' ) == $locale ||
             
in_array( $locale, array_keys( $this->get_locales() ) ) ) )
        {    
// Don't allow selector if this collection already uses the requested locale:
           
return T_('N/A');
        }

       
$linked_colls = $this->get_locales( 'coll' );

        if( !
check_user_perm( 'blogs', 'editall' ) )
        {    
// Display only the stored value because current User has no permission to edit all collections:
           
if( isset( $linked_colls[ $locale ] ) )
            {    
// Get the linked collection:
               
$BlogCache = & get_BlogCache();
               
$linked_Blog = & $BlogCache->get_by_ID( $linked_colls[ $locale ], false, false );
            }
            return empty(
$linked_Blog ) ? T_('None') : $linked_Blog->get( 'name' );
        }

       
$main_locale_SQL = new SQL( 'Get collections with locale '.$locale.' to link with coolection #'.$this->ID );
       
$main_locale_SQL->SELECT( 'blog_ID' );
       
$main_locale_SQL->FROM( 'T_blogs' );
       
$main_locale_SQL->WHERE( 'blog_locale = '.$DB->quote( $locale ) );
        if(
$this->ID > 0 )
        {
           
$main_locale_SQL->WHERE_and( 'blog_ID != '.$this->ID );
        }

       
$extra_locale_SQL = new SQL( 'Get collections with locale '.$locale.' to link with coolection #'.$this->ID );
       
$extra_locale_SQL->SELECT( 'cl_coll_ID' );
       
$extra_locale_SQL->FROM( 'T_coll_locales' );
       
$extra_locale_SQL->WHERE( 'cl_locale = '.$DB->quote( $locale ) );
       
$extra_locale_SQL->WHERE_and( 'cl_linked_coll_ID IS NULL' );
        if(
$this->ID > 0 )
        {
           
$extra_locale_SQL->WHERE_and( 'cl_coll_ID != '.$this->ID );
        }

       
$colls = $DB->get_col( 'SELECT blog_ID
            FROM ( '
.$main_locale_SQL->get().' UNION ' .$extra_locale_SQL->get().' ) AS locale_colls
            ORDER BY blog_ID'
);

        if( empty(
$colls ) )
        {    
// No collection with requested locale:
           
return T_('None');
        }

       
$BlogCache = get_BlogCache();
       
$BlogCache->load_list( $colls );

       
$r = '<select name="'.$field_name.'_link_coll['.$locale.']" class="form-control" style="width:100%">';
       
$r .= '<option value="">'.T_('None').'</option>';
        foreach(
$colls as $coll_ID )
        {
            if(
$locale_Blog = & $BlogCache->get_by_ID( $coll_ID, false, false ) )
            {
               
$r .= '<option value="'.$coll_ID.'"'
                   
.( isset( $linked_colls[ $locale ] ) && $linked_colls[ $locale ] == $coll_ID ? ' selected="selected"' : '' ).'>'
                   
.$locale_Blog->get( 'name' ).'</option>';
            }
        }
       
$r .= '</select>';

        return
$r;
    }


   
/**
     * Reset widgets for this collection
     *
     * @param string Skin type: 'all', 'normal', 'mobile', 'tablet'
     */
   
function reset_widgets( $skin_type = 'all' )
    {
        if( empty(
$this->ID ) )
        {    
// This function should be called only for created collection:
           
return;
        }

        global
$DB;

       
$all_skin_types = array( 'normal', 'mobile', 'tablet', 'alt' );

        if(
$skin_type == 'all' )
        {    
// Reset widgets for all skin types:
           
$skin_types = $all_skin_types;
        }
        elseif(
in_array( $skin_type, $all_skin_types ) )
        {    
// Reset widgets only for requested skin type:
           
$skin_types = array( $skin_type );
        }
        else
        {    
// No correct skin type is requested:
           
return;
        }

        foreach(
$skin_types as $skin_type )
        {    
// Remove previous widgets:
           
$DB->query( 'DELETE T_widget__container, T_widget__widget
                 FROM T_widget__container
                 LEFT JOIN T_widget__widget ON wico_ID = wi_wico_ID
                WHERE wico_coll_ID = '
.$DB->quote( $this->ID )
                    .( empty(
$skin_type ) ? '' :
               
' AND wico_skin_type = '.$DB->quote( $skin_type ) ) );

           
// Add default widgets:
           
$this->setup_default_widgets( $skin_type );
        }
    }
}

?>