Вход Регистрация
Файл: system/core/model.php
Строк: 1642
<?php
class cmsModel {

    public 
$name;

    public 
$db;

    
/**
     * Типы MySQL JOIN
     */
    
const LEFT_JOIN = 'LEFT JOIN';
    const 
RIGHT_JOIN = 'RIGHT JOIN';
    const 
INNER_JOIN = 'INNER JOIN';
    const 
STRAIGHT_JOIN = 'STRAIGHT_JOIN';
    const 
LEFT_OUTER_JOIN = 'LEFT OUTER JOIN';
    const 
RIGHT_OUTER_JOIN = 'RIGHT OUTER JOIN';
    const 
NATURAL_LEFT_JOIN = 'NATURAL LEFT JOIN';
    const 
NATURAL_LEFT_OUTER_JOIN = 'NATURAL LEFT OUTER JOIN';
    const 
NATURAL_RIGHT_JOIN = 'NATURAL RIGHT JOIN';
    const 
NATURAL_RIGHT_OUTER_JOIN = 'NATURAL RIGHT OUTER JOIN';

    
/**
     * Уровни изоляций транзакций
     */
    
const READ_UNCOMMITTED = 'READ UNCOMMITTED';
    const 
READ_COMMITTED = 'READ COMMITTED';
    const 
REPEATABLE_READ = 'REPEATABLE READ';
    const 
SERIALIZABLE = 'SERIALIZABLE';

    
/**
     * Префикс по умолчанию таблиц контента
     */
    
const DEFAULT_TABLE_PREFIX = 'con_';
    
/**
     * Постфикс по умолчанию таблиц категорий контента
     */
    
const DEFAULT_TABLE_CATEGORY_POSTFIX = '_cats';

    
/**
     * Префикс таблиц контента
     * @var string
     */
    
public $table_prefix = cmsModel::DEFAULT_TABLE_PREFIX;
    public 
$table_category_postfix = cmsModel::DEFAULT_TABLE_CATEGORY_POSTFIX;

    
//условия для выборок
    
public $table      = '';
    public 
$select     = array('i.*');
    public 
$distinct   = '';
    public 
$straight_join = '';
    public 
$join       = '';
    public 
$where      = '';
    public 
$where_separator  = 'AND';
    public 
$group_by   = '';
    public 
$order_by   = '';
    public 
$read_type  = '';
    public 
$index_action = '';
    public 
$limit      = 1000;
    public 
$perpage    = 50;

    public 
$is_transaction_started = false;

    public 
$keep_filters = false;
    public 
$filter_on  = false;

    protected static 
$global_localized = false;

    protected 
$localized = false;
    protected 
$privacy_filter_disabled = false;
    protected 
$privacy_filtered = false;
    protected 
$privacy_filter_value = 0;
    protected 
$approved_filter_disabled = false;
    protected 
$hidden_parents_filter_disabled = true;
    protected 
$delete_filter_disabled = false;
    protected 
$approved_filtered = false;
    protected 
$available_filtered = false;
    protected 
$hp_filtered = false;
    protected 
$joined_session_online = array();

    protected static 
$cached = array();

    private 
$cache_key = false;

    protected 
$lang;
    protected 
$default_lang;

    public function 
__construct(){

        
$this->name = strtolower(str_replace('model', '', get_called_class()));

        
$this->db = cmsCore::getInstance()->db;

        
$this->lang = cmsCore::getLanguageName();
        
$this->default_lang = cmsConfig::get('language');

        
$this->localized = self::$global_localized;

    }

    public static function 
__set_state($data) {
        
$class = 'model' . string_to_camel('_', $data['name']);
        return new 
$class();
    }

//============================================================================//
//============================================================================//

    
public function useCache($key){
        
$this->cache_key = $key; return $this;
    }

    protected function 
stopCache(){
        
$this->cache_key = false;  return $this;
    }

//============================================================================//
//============================================================================//

    
public function getContentTypeTableName($name){
        return 
$this->table_prefix . $name;
    }

    public function 
setTablePrefix($prefix){
        
$this->table_prefix = $prefix;
        return 
$this;
    }

    public function 
setTableCategoryPostfix($postfix){
        
$this->table_category_postfix = $postfix;
        return 
$this;
    }

    public function 
getContentCategoryTableName($name){
        return 
$this->getContentTypeTableName($name).$this->table_category_postfix;
    }

    
/**
     * @todo вынести все структуры таблиц из кода в отдельные файлы-конфиги
     */
    
public function getContentTableStruct(){

        return array(
            
'id'                 => array('type' => 'primary'),
            
'title'              => array('type' => 'varchar', 'size' => 100, 'fulltext' => true),
            
'content'            => array('type' => 'text'),
            
'photo'              => array('type' => 'text'),
            
'slug'               => array('type' => 'varchar', 'index' => true, 'size' => 100),
            
'seo_keys'           => array('type' => 'varchar', 'size' => 256),
            
'seo_desc'           => array('type' => 'varchar', 'size' => 256),
            
'seo_title'          => array('type' => 'varchar', 'size' => 256),
            
'tags'               => array('type' => 'varchar', 'size' => 1000),
            
'template'           => array('type' => 'varchar', 'size' => 150),
            
'date_pub'           => array('type' => 'timestamp', 'index' => array('date_pub','parent_id', 'user_id'), 'composite_index' => array(4,3,2,1), 'default_current' => true),
            
'date_last_modified' => array('type' => 'timestamp'),
            
'date_pub_end'       => array('type' => 'timestamp', 'index' => true),
            
'is_pub'             => array('type' => 'bool', 'index' => 'date_pub', 'composite_index' => 0, 'default' => 1),
            
'hits_count'         => array('type' => 'int', 'default' => 0, 'unsigned' => true),
            
'user_id'            => array('type' => 'int', 'index' => 'user_id', 'composite_index' => 0, 'unsigned' => true),
            
'parent_id'          => array('type' => 'int', 'index' => 'parent_id', 'composite_index' => 0, 'unsigned' => true),
            
'parent_type'        => array('type' => 'varchar', 'size' => 32, 'index' => 'parent_id', 'composite_index' => 1),
            
'parent_title'       => array('type' => 'varchar', 'size' => 100),
            
'parent_url'         => array('type' => 'varchar', 'size' => 255),
            
'is_parent_hidden'   => array('type' => 'bool', 'index' => 'date_pub', 'composite_index' => 1),
            
'category_id'        => array('type' => 'int', 'index' => true, 'default' => 1, 'unsigned' => true),
            
'folder_id'          => array('type' => 'int', 'index' => true, 'unsigned' => true),
            
'is_comments_on'     => array('type' => 'bool', 'default' => 1),
            
'comments'           => array('type' => 'int', 'default' => 0, 'unsigned' => true),
            
'rating'             => array('type' => 'int', 'default' => 0),
            
'is_deleted'         => array('type' => 'bool', 'index' => 'date_pub', 'composite_index' => 2),
            
'is_approved'        => array('type' => 'bool', 'index' => 'date_pub', 'composite_index' => 3, 'default' => 1),
            
'approved_by'        => array('type' => 'int', 'index' => true, 'unsigned' => true),
            
'date_approved'      => array('type' => 'timestamp'),
            
'is_private'         => array('type' => 'bool', 'default' => 0)
        );

    }
    
/**
     * @todo вынести все структуры таблиц из кода в отдельные файлы-конфиги
     */
    
public function getFieldsTableStruct(){

        return array(
            
'id'            => array('type' => 'primary'),
            
'ctype_id'      => array('type' => 'int', 'unsigned' => true),
            
'name'          => array('type' => 'varchar', 'size' => 40),
            
'title'         => array('type' => 'varchar', 'size' => 100),
            
'hint'          => array('type' => 'varchar', 'size' => 200),
            
'ordering'      => array('type' => 'int', 'index' => 'is_enabled', 'composite_index' => 1, 'unsigned' => true),
            
'is_enabled'    => array('type' => 'bool', 'index' => 'is_enabled', 'composite_index' => 0, 'default' => 1),
            
'fieldset'      => array('type' => 'varchar', 'size' => 32),
            
'type'          => array('type' => 'varchar', 'size' => 16),
            
'is_in_list'    => array('type' => 'bool'),
            
'is_in_item'    => array('type' => 'bool'),
            
'is_in_filter'  => array('type' => 'bool'),
            
'is_private'    => array('type' => 'bool'),
            
'is_fixed'      => array('type' => 'bool'),
            
'is_fixed_type' => array('type' => 'bool'),
            
'is_system'     => array('type' => 'bool'),
            
'values'        => array('type' => 'text'),
            
'options'       => array('type' => 'text'),
            
'groups_read'   => array('type' => 'text'),
            
'groups_add'    => array('type' => 'text'),
            
'groups_edit'   => array('type' => 'text'),
            
'filter_view'   => array('type' => 'text')
        );

    }
    
/**
     * @todo вынести все структуры таблиц из кода в отдельные файлы-конфиги
     */
    
public function getPropsTableStruct(){

        return array(
            
'id'           => array('type' => 'primary'),
            
'ctype_id'     => array('type' => 'int', 'unsigned' => true),
            
'title'        => array('type' => 'varchar', 'size' => 100),
            
'fieldset'     => array('type' => 'varchar', 'size' => 32),
            
'type'         => array('type' => 'varchar', 'size' => 16),
            
'is_in_filter' => array('type' => 'bool', 'index' => true),
            
'values'       => array('type' => 'text'),
            
'options'      => array('type' => 'text')
        );

    }
    
/**
     * @todo вынести все структуры таблиц из кода в отдельные файлы-конфиги
     */
    
public function getPropsBindTableStruct(){

        return array(
            
'id'       => array('type' => 'primary'),
            
'prop_id'  => array('type' => 'int', 'index' => true, 'unsigned' => true),
            
'cat_id'   => array('type' => 'int', 'index' => true, 'unsigned' => true),
            
'ordering' => array('type' => 'int', 'index' => true, 'unsigned' => true),
        );

    }
    
/**
     * @todo вынести все структуры таблиц из кода в отдельные файлы-конфиги
     */
    
public function getPropsValuesTableStruct(){

        return array(
            
'prop_id' => array('type' => 'int', 'index' => true, 'unsigned' => true),
            
'item_id' => array('type' => 'int', 'index' => true, 'unsigned' => true),
            
'value'   => array('type' => 'varchar', 'size' => 255),
        );

    }

    public function 
checkCorrectEqualSlug($table_name, $slug, $item_id, $max_len = 255) {

        
$get_scount = function($slug) use($item_id, $table_name){
            return 
$this->filterNotEqual('id', $item_id)->
                
filterLike('slug', $slug)->
                
getCount($table_name, 'id', true);
        };

        if(
$get_scount($slug)){
            if(
mb_strlen($slug) >= $max_len){
                
$slug = mb_substr($slug, 0, ($max_len - 1));
            }
            
$i = 2;
            while(
$get_scount($slug.$i)){
                
$i++;
                if(
mb_strlen($slug.$i) > $max_len){
                    
$slug = mb_substr($slug, 0, ($max_len - strlen($i)));
                }
            }
            
$slug .= $i;
        }

        return 
$slug;
    }

//============================================================================//
//============================================================================//

    
public function getRootCategory($ctype_name){

        return 
$this->db->getFields($this->getContentCategoryTableName($ctype_name), 'parent_id=0');

    }

    public function 
getCategory($ctype_name, $id, $by_field='id'){

        
$this->useCache('content.categories');

        
$category = $this->getItemByField($this->getContentCategoryTableName($ctype_name), $by_field, $id);

        if (!
$category) { return false; }

        
$category['path'] = $this->getCategoryPath($ctype_name, $category);

        if(!empty(
$category['allow_add'])){
            
$category['allow_add'] = cmsModel::yamlToArray($category['allow_add']);
        }

        return 
$category;

    }

    public function 
getCategoryBySLUG($ctype_name, $slug){

        return 
$this->getCategory($ctype_name, $slug, 'slug');

    }

    public function 
getCategorySLUG($category, $ctype_name){

        
$slug = '';

        foreach(
$category['path'] as $c){
            if (
$c['id'] == 1) { continue; }
            if (
$slug) { $slug .= '/'; }
            
$slug .= lang_slug( empty($c['slug_key']) ? str_replace('/', '', $c['title']) : $c['slug_key'] );
        }

        
$slug = mb_substr($slug, 0, 255);

        
$slug = $this->checkCorrectEqualSlug($this->getContentCategoryTableName($ctype_name), $slug, $category['id'], 255);

        return 
$slug;
    }

//============================================================================//
//============================================================================//

    
public function getCategoriesTree($ctype_name, $is_show_root=true) {

        if (!
$is_show_root){
            
$this->filterGt('parent_id', 0);
        }

        
$this->orderBy('ns_left');

        
$this->useCache('content.categories');

        return 
$this->get($this->getContentCategoryTableName($ctype_name), function($node, $model){
            if (
$node['ns_level']==0) { $node['title'] = LANG_ROOT_CATEGORY; }
            if(!empty(
$node['allow_add'])){
                
$node['allow_add'] = cmsModel::yamlToArray($node['allow_add']);
            }
            return 
$node;
        });

    }

//============================================================================//
//============================================================================//

    
public function getSubCategories($ctype_name, $parent_id = 1, $item_callback = false) {

        
$this->filterEqual('parent_id', $parent_id);
        
$this->orderBy('ns_left');

        
$this->useCache('content.categories');

        return 
$this->get($this->getContentCategoryTableName($ctype_name), $item_callback);
    }

    public function 
getSubCategoriesTree($ctype_name, $parent_id=1, $level=1) {

        
$parent = $this->getCategory($ctype_name, $parent_id);

        
$this->
            
filterGt('ns_left', $parent['ns_left'])->
            
filterLt('ns_right', $parent['ns_right']);

        if (
$level){
            
$this->filterLtEqual('ns_level', $parent['ns_level'] + $level);
        }

        
$this->orderBy('ns_left');

        
$this->useCache('content.categories');

        return 
$this->get($this->getContentCategoryTableName($ctype_name));

    }

//============================================================================//
//============================================================================//

    
public function getCategoryPath($ctype_name, $category) {

        if (!isset(
$category['ns_left'])){
            
$category = $this->getCategory($ctype_name, $category['id']);
        }

        
$this->
            
filterLtEqual('ns_left', $category['ns_left'])->
            
filterGtEqual('ns_right', $category['ns_right'])->
            
filterLtEqual('ns_level', $category['ns_level'])->
            
filterGt('ns_level', 0)->
            
orderBy('ns_left');

        
$this->useCache('content.categories');

        return 
$this->get($this->getContentCategoryTableName($ctype_name));

    }

//============================================================================//
//============================================================================//

    
public function addCategory($ctype_name, $category){

        
$table_name = $this->getContentCategoryTableName($ctype_name);

        
$this->db->nestedSets->setTable($table_name);

        
$category['id'] = $this->db->nestedSets->addNode($category['parent_id']);

        if (!
$category['id']){ return false; }

        
$this->update($table_name, $category['id'], $category);

        
$category['path'] = $this->getCategoryPath($ctype_name, $category);

        
$category['slug'] = $this->getCategorySLUG($category, $ctype_name);

        
$this->update($table_name, $category['id'], array(
            
'slug' => $category['slug']
        ));

        
cmsCache::getInstance()->clean('content.categories');

        return 
$category;
    }

//============================================================================//
//============================================================================//

    
public function updateCategory($ctype_name, $id, $category){

        
cmsCache::getInstance()->clean('content.categories');

        
$table_name = $this->getContentCategoryTableName($ctype_name);

        
$category_old = $this->getCategory($ctype_name, $id);

        if (
$category_old['parent_id'] != $category['parent_id']){
            
$this->db->nestedSets->setTable($table_name);
            
$this->db->nestedSets->moveNode($id, $category['parent_id']);
        }

        
$this->update($table_name, $id, $category);

        
$category['id'] = $id;
        
$category['path'] = $this->getCategoryPath($ctype_name, array('id' => $id));
        
$category['slug'] = $this->getCategorySLUG($category, $ctype_name);

        
$this->update($table_name, $id, array(
            
'slug' => $category['slug']
        ));

        
$subcats = $this->getSubCategoriesTree($ctype_name, $id, false);

        if (
$subcats){
            foreach(
$subcats as $subcat){
                
$subcat['path'] = $this->getCategoryPath($ctype_name, array('id' => $subcat['id']));
                
$subcat['slug'] = $this->getCategorySLUG($subcat, $ctype_name);
                
$this->update($table_name, $subcat['id'], array('slug' => $subcat['slug']));
            }
        }

        return 
$category;

    }

//============================================================================//
//============================================================================//

    
public function updateCategoryTree($ctype_name, $tree, $categories_count){

        
cmsCache::getInstance()->clean('content.categories');

        
$this->updateCategoryTreeNode($ctype_name, $tree);
        
$this->updateCategoryTreeNodeSlugs($ctype_name, $tree);

        
$root_keys = array(
            
'ns_left' => 1,
            
'ns_right' => 1 + ($categories_count*2) + 1
        
);

        
$this->update($this->getContentCategoryTableName($ctype_name), 1, $root_keys);

        return 
true;

    }

    public function 
updateCategoryTreeNode($ctype_name, $tree){

        
$table_name = $this->getContentCategoryTableName($ctype_name);

        foreach(
$tree as $node){

            
$this->update($table_name, $node['key'], array(
                
'parent_id' => $node['parent_key'],
                
'ns_left' => $node['left'],
                
'ns_right' => $node['right'],
                
'ns_level' => $node['level'],
            ));

            if (!empty(
$node['children'])){
                
$this->updateCategoryTreeNode($ctype_name, $node['children']);
            }

        }

        return 
true;

    }

    public function 
updateCategoryTreeNodeSlugs($ctype_name, $tree){

        
$table_name = $this->getContentCategoryTableName($ctype_name);

        foreach(
$tree as $node){

            
$path = $this->getCategoryPath($ctype_name, array(
                
'id' => $node['key'],
                
'parent_id' => $node['parent_key'],
                
'ns_left' => $node['left'],
                
'ns_right' => $node['right'],
                
'ns_level' => $node['level']
            ));

            
$slug = $this->getCategorySLUG(array(
                
'path' => $path,
                
'title' => $node['title']
            ), 
$ctype_name);

            
$this->update($table_name, $node['key'], array(
                
'slug' => $slug
            
));

            if (!empty(
$node['children'])){
                
$this->updateCategoryTreeNodeSlugs($ctype_name, $node['children']);
            }

        }

        return 
true;

    }

//============================================================================//
//============================================================================//

    
public function deleteCategory($ctype_name, $id){

        
//
        // Эта функция должна быть переопределена и вызываться
        // из дочернего класса чтобы после нее удалять все записи
        // из категории
        //

        
$table_name = $this->getContentCategoryTableName($ctype_name);

        
$this->db->nestedSets->setTable($table_name);
        
$this->db->nestedSets->deleteNode($id);

        
cmsCache::getInstance()->clean('content.categories');

        return 
true;

    }

//============================================================================//
//============================================================================//

    
public function delete($table_name, $id, $by_field='id'){
        
$this->filterEqual($by_field, $id);
        return 
$this->deleteFiltered($table_name);
    }

    public function 
deleteFiltered($table_name){
        
$where = $this->where;
        
$this->resetFilters();
        return 
$this->db->delete($table_name, $where);
    }

//============================================================================//
//============================================================================//

    
public function update($table_name, $id, $data, $skip_check_fields = false, $array_as_json = false){
        
$this->filterEqual('id', $id);
        return 
$this->updateFiltered($table_name, $data, $skip_check_fields, $array_as_json);
    }

    public function 
updateFiltered($table_name, $data, $skip_check_fields = false, $array_as_json = false){
        
$where = $this->where;
        
$this->resetFilters();
        return 
$this->db->update($table_name, $where, $data, $skip_check_fields, $array_as_json);
    }

//============================================================================//
//============================================================================//

    
public function insert($table_name, $data, $array_as_json = false, $ignore = false){
        return 
$this->db->insert($table_name, $data, false, $array_as_json, $ignore);
    }

    public function 
insertOrUpdate($table_name, $insert_data, $update_data = false){
        return 
$this->db->insertOrUpdate($table_name, $insert_data, $update_data);
    }

//============================================================================//
//============================================================================//

    
public function replaceFieldString($table_name, $search, $replace, $field){

        
$search = $this->db->escape($search);
        
$replace = $this->db->escape($replace);

        return 
$this->db->query("UPDATE `{#}{$table_name}` SET `{$field}` = REPLACE(`{$field}`, '{$search}', '$replace') WHERE `{$field}` LIKE '%{$search}%'");

    }

//============================================================================//
//============================================================================//

    
public function lockFilters(){
        
$this->keep_filters = true;
        return 
$this;
    }

    public function 
unlockFilters(){
        
$this->keep_filters = false;
        return 
$this;
    }

    public function 
resetFilters(){

        
$this->select       = array('i.*');
        
$this->group_by     = '';
        
$this->order_by     = '';
        
$this->index_action = '';
        
$this->limit        = '';
        
$this->read_type    = '';
        
$this->join         = '';
        
$this->distinct     = '';
        
$this->straight_join = '';
        
$this->joined_session_online = array();

        if (
$this->keep_filters) { return $this; }

        
$this->filter_on          = false;
        
$this->where              = '';
        
$this->privacy_filtered   = false;
        
$this->privacy_filter_value = 0;
        
$this->approved_filtered  = false;
        
$this->available_filtered = false;
        
$this->hp_filtered        = false;

        return 
$this;

    }

    public function 
localizedOn() {
        
$this->localized = true; return $this;
    }

    public function 
localizedOff() {
        
$this->localized = false; return $this;
    }

    public function 
localizedRestore() {
        
$this->localized = self::$global_localized; return $this;
    }

    public static function 
globalLocalizedOn() {
        
self::$global_localized = true;
    }

    public static function 
globalLocalizedOff() {
        
self::$global_localized = false;
    }

    public function 
replaceTranslatedField($item, $table_name = false) {

        
// предполагается, что язык в настройках -
        // основной язык и основные тексты хранятся
        // в ячейках без постфикса
        
if ($this->lang == $this->default_lang) {
            return 
$item;
        }

        if(!
is_array($item)){ return $item; }

        
$postfix = '_' . $this->lang;

        foreach (
$item as $key => $value) {

            
$lang_key = $key . $postfix;

            if (!isset(
$item[$lang_key])) {

                if(
is_array($value) && $value){
                    
$item[$key] = $this->replaceTranslatedField($value, $table_name);
                }

                continue;
            }

            
$item[$key] = $item[$lang_key];

        }

        return 
$item;

    }

    public function 
setStraightJoin() {
        
$this->straight_join = self::STRAIGHT_JOIN; return $this;
    }

    public function 
distinctSelect() {
        
$this->distinct = 'DISTINCT'; return $this;
    }

    public function 
filter($condition){
        if (
$this->filter_on){
            
$this->where .= ' '.$this->where_separator.' ('.$condition.')';
        } else {
            
$this->where .= '('.$condition.')';
            
$this->filter_on = true;
        }
        
$this->where_separator = ' AND ';
        return 
$this;
    }

    public function 
filterStart(){
        if (
$this->filter_on){
            
$this->where .= ' '.$this->where_separator.' (';
        } else {
            
$this->where .= '(';
        }
        
$this->filter_on = false;
        return 
$this;
    }

    public function 
filterEnd(){
        
$this->where .= ' ) ';
        return 
$this;
    }

    public function 
filterAnd(){
        
$this->where_separator = ' AND ';
        return 
$this;
    }

    public function 
filterOr(){
        
$this->where_separator = ' OR ';
        return 
$this;
    }

    public function 
filterNotNull($field){
        if (
strpos($field, '.') === false){ $field = 'i.' . $field; }
        
$this->filter($field.' IS NOT NULL');
        return 
$this;
    }

    public function 
filterIsNull($field){
        if (
strpos($field, '.') === false){ $field = 'i.' . $field; }
        
$this->filter($field.' IS NULL');
        return 
$this;
    }

    public function 
filterEqual($field, $value, $binary = false){
        if (
strpos($field, '.') === false){ $field = 'i.' . $field; }
        if (
is_null($value)){
            
$this->filter($field.' IS NULL');
        } else {
            
$value = $this->db->escape($value);
            
$this->filter(($binary ? ' BINARY ' : '')."$field = '$value'");
        }
        return 
$this;
    }

    public function 
filterFunc($field, $value, $sign='='){
        if (
strpos($field, '.') === false){ $field = 'i.' . $field; }
        
$this->filter("$field {$sign} $value");
        return 
$this;
    }

    public function 
filterNotEqual($field, $value){
        if (
strpos($field, '.') === false){ $field = 'i.' . $field; }
        if (
is_null($value)){
            
$this->filter($field.' IS NOT NULL');
        } else {
            
$value = $this->db->escape($value);
            
$this->filter("$field <> '$value'");
        }
        return 
$this;
    }

    public function 
filterGt($field, $value){
        if (
strpos($field, '.') === false){ $field = 'i.' . $field; }
        
$value = $this->db->escape($value);
        
$this->filter("$field > '$value'");
        return 
$this;
    }

    public function 
filterLt($field, $value){
        if (
strpos($field, '.') === false){ $field = 'i.' . $field; }
        
$value = $this->db->escape($value);
        
$this->filter("$field < '$value'");
        return 
$this;
    }

    public function 
filterGtEqual($field, $value){
        if (
strpos($field, '.') === false){ $field = 'i.' . $field; }
        
$value = $this->db->escape($value);
        
$this->filter("$field >= '$value'");
        return 
$this;
    }

    public function 
filterLtEqual($field, $value){
        if (
strpos($field, '.') === false){ $field = 'i.' . $field; }
        
$value = $this->db->escape($value);
        
$this->filter("$field <= '$value'");
        return 
$this;
    }

    public function 
filterLike($field, $value){
        if (
strpos($field, '.') === false){ $field = 'i.' . $field; }
        
$value = $this->db->escape($value);
        
$this->filter("$field LIKE '$value'");
        return 
$this;
    }

    public function 
filterNotLike($field, $value){
        if (
strpos($field, '.') === false){ $field = 'i.' . $field; }
        
$value = $this->db->escape($value);
        
$this->filter("$field NOT LIKE '$value'");
        return 
$this;
    }

    public function 
filterBetween($field, $start, $end){
        if (
strpos($field, '.') === false){ $field = 'i.' . $field; }
        
$start = $this->db->escape($start);
        
$end = $this->db->escape($end);
        
$this->filter("$field BETWEEN '$start' AND '$end'");
        return 
$this;
    }

    public function 
filterDateYounger($field, $value, $interval='DAY'){
        if (
strpos($field, '.') === false){ $field = 'i.' . $field; }
        
$value = $this->db->escape($value);
        
$interval = $this->db->escape($interval);
        
$this->filter("$field >= DATE_SUB(NOW(), INTERVAL {$value} {$interval})");
        return 
$this;
    }

    public function 
filterTimestampYounger($field, $value, $interval='DAY'){
        if (
strpos($field, '.') === false){ $field = 'i.' . $field; }
        
$value = (int)$value;
        
$interval = $this->db->escape($interval);
        
$this->filter("TIMESTAMPDIFF({$interval}, {$field}, NOW()) <= {$value}");
        return 
$this;
    }

    public function 
filterDateOlder($field, $value, $interval='DAY'){
        if (
strpos($field, '.') === false){ $field = 'i.' . $field; }
        
$value = $this->db->escape($value);
        
$interval = $this->db->escape($interval);
        
$this->filter("$field < DATE_SUB(NOW(), INTERVAL {$value} {$interval})");
        return 
$this;
    }

    public function 
filterTimestampGt($field, $value){
        if (
strpos($field, '.') === false){ $field = 'i.' . $field; }
        
$field = "UNIX_TIMESTAMP({$field})";
        
$value = $this->db->escape($value);
        
$this->filter("{$field} > '{$value}'");
        return 
$this;
    }

    public function 
filterTimestampLt($field, $value){
        if (
strpos($field, '.') === false){ $field = 'i.' . $field; }
        
$field = "UNIX_TIMESTAMP({$field})";
        
$value = $this->db->escape($value);
        
$this->filter("{$field} < '{$value}'");
        return 
$this;
    }

    public function 
filterIn($field, $value){
        if (
strpos($field, '.') === false){ $field = 'i.' . $field; }
        if (
is_array($value)){
            if(!
$value){ return $this; }
            foreach(
$value as $k=>$v){
                
$v = $this->db->escape(strval($v));
                
$value[$k] = "'{$v}'";
            }
            
$value = implode(',', $value);
        } else {
            
$value = $this->db->escape($value);
            
$value = "'{$value}'";
        }
        
$this->filter("{$field} IN ({$value})");
        return 
$this;
    }

    public function 
filterNotIn($field, $value){
        if (
strpos($field, '.') === false){ $field = 'i.' . $field; }
        if (
is_array($value)){
            if(!
$value){ return $this; }
            foreach(
$value as $k=>$v){
                
$v = $this->db->escape($v);
                
$value[$k] = "'{$v}'";
            }
            
$value = implode(',', $value);
        } else {
            
$value = $this->db->escape($value);
            
$value = "'{$value}'";
        }
        
$this->filter("{$field} NOT IN ({$value})");
        return 
$this;
    }

    public function 
filterRelated($field, $value, $lang = false){

        if(
mb_strlen($value) <= 3){
            return 
$this->filterLike($field, $this->db->escape($value).'%');
        }

        
$query = array();

        
$words = explode(' ', str_replace(array('"',''','+','*','-','%',')','(','.',',','!','?'), ' ', $value));

        $stopwords = string_get_stopwords($lang ? $lang : cmsConfig::get('
language'));

        foreach($words as $word){

            $word = mb_strtolower(trim($word));

            if (mb_strlen($word)<3 || is_numeric($word)) { continue; }
            if($stopwords && in_array($word, $stopwords)){ continue; }
            if (mb_strlen($word)==3) { $query[] = $this->db->escape($word); continue; }

            if (mb_strlen($word) >= 12) {
                $word = mb_substr($word, 0, mb_strlen($word) - 3).'
*';
            } else if (mb_strlen($word) >= 10) {
                $word = mb_substr($word, 0, mb_strlen($word) - 2).'
*';
            } else if (mb_strlen($word) >= 6) {
                $word = mb_substr($word, 0, mb_strlen($word) - 1).'
*';
            }

            $query[] = $this->db->escape($word);

        }

        if (empty($query)) {
            $ft_query = '"' . 
$this->db->escape($value) . '"';
        } else {

            usort($query, function ($a, $b){
                return mb_strlen($b)-mb_strlen($a);
            });
            $query = array_slice($query, 0, 5);

            $ft_query  = '
>"' . $this->db->escape($value).'" <(';
            $ft_query .= implode(' ', $query).'
)';
        }

        if (strpos($field, '
.') === false){ $field = 'i.' . $field; }

        $search_param = "MATCH({$field}) AGAINST ('
{$ft_query}' IN BOOLEAN MODE)";

        $this->select($search_param, '
fsort');

        $this->order_by = '
fsort desc';

        return $this->filter($search_param);

    }

    public function filterCategory($ctype_name, $category, $is_recursive=false){

        $table_name      = $this->getContentCategoryTableName($ctype_name);
        $bind_table_name = $table_name . '
_bind';

        if (!$is_recursive){

            $this->joinInner($bind_table_name, '
b FORCE INDEX (item_id)', 'b.item_id = i.id')->filterEqual('b.category_id', $category['id']);

        } else {

            // для корневой категории фильтрация не нужна
            if(!$category['
parent_id']){
                return $this;
            }

            /**
             * Нам нужны только уникальные значения
             * Закомментировано потому что DISTINCT дает нагрузку
             * @todo сделать анализ кол-ва записей, вложенности категорий и динамически включать или отключать
             * В общих случаях, когда дерево категорий имеет первый и второй уровень раскомментировать не нужно
             * Для малых БД, где повсеместно используется принадлежность к нескольким категориям ниже второго уровня
             * имеет смысл раскомментировать строку ниже
             */
            //$this->distinctSelect();

            $this->joinInner($bind_table_name, '
b FORCE INDEX (item_id)', 'b.item_id = i.id');
            $this->joinInner($table_name, '
c', 'c.id = b.category_id');
            $this->filterGtEqual('
c.ns_left', $category['ns_left']);
            $this->filterLtEqual('
c.ns_right', $category['ns_right']);

        }

        return $this;

    }

    public function filterCategoryId($ctype_name, $category_id, $is_recursive=false){

        if (!$is_recursive){

            if($category_id){
                return $this->filterCategory($ctype_name, array('
id'=>$category_id));
            }

        } else {

            $category = $this->getCategory($ctype_name, $category_id);
            if($category){
                return $this->filterCategory($ctype_name, $category, true);
            }

        }

        return $this;

    }

    public function disablePrivacyFilter(){
        $this->privacy_filter_disabled = true;
        $this->privacy_filter_value = 0;
        return $this;
    }

    public function disablePrivacyFilterForFriends(){
        $this->privacy_filter_value = array(0, 1);
        return $this;
    }

    public function enablePrivacyFilter(){
        $this->privacy_filter_disabled = false;
        $this->privacy_filter_value = 0;
        return $this;
    }

    public function isEnablePrivacyFilter(){
        return $this->privacy_filter_disabled === false;
    }

    public function filterPrivacy(){

        if ($this->privacy_filtered) { return $this; }

        // Этот фильтр может применяться при подсчете числа записей
        // и при выборке самих записей
        // используем флаг чтобы фильтр не применился дважды
        $this->privacy_filtered = true;

        if(is_array($this->privacy_filter_value)){
            return $this->filterIn('
i.is_private', $this->privacy_filter_value);
        }

        return $this->filterEqual('
i.is_private', $this->privacy_filter_value);

    }

    public function enableDeleteFilter(){
        $this->delete_filter_disabled = false;
        return $this;
    }

    public function disableDeleteFilter(){
        $this->delete_filter_disabled = true;
        return $this;
    }

    public function enableApprovedFilter(){
        $this->approved_filter_disabled = false;
        return $this;
    }

    public function disableApprovedFilter(){
        $this->approved_filter_disabled = true;
        return $this;
    }

    public function enableHiddenParentsFilter(){
        $this->hidden_parents_filter_disabled = false;
        return $this;
    }

    public function disableHiddenParentsFilter(){
        $this->hidden_parents_filter_disabled = true;
        return $this;
    }

    public function isEnableHiddenParentsFilter(){
        return $this->hidden_parents_filter_disabled === false;
    }

    public function joinModerationsTasks($ctype_name){
        $this->select('
IF(t.id IS NULL AND i.is_approved < 1, 1, NULL)', 'is_draft');
        $this->select('
t.is_new_item');
        return $this->joinLeft('
moderators_tasks', 't', "t.item_id = i.id AND t.ctype_name = '{$ctype_name}'");
    }

    public function filterByModeratorTask($moderator_id, $ctype_name, $is_admin = false){

        $this->select('
m.is_new_item');

        $this->joinInner('
moderators_tasks', 'm', 'm.item_id = i.id');

        $this->filterEqual('
m.ctype_name', $ctype_name);

        if(!$is_admin){
            $this->filterEqual('
m.moderator_id', $moderator_id);
        }

        return $this;

    }

    public function filterAvailableOnly(){

        if ($this->available_filtered) { return $this; }

        $this->available_filtered = true;

        return $this->filterIsNull('
is_deleted');

    }

    public function filterDeleteOnly(){

        return $this->filterEqual('
is_deleted', 1);

    }

    public function filterApprovedOnly(){

        if ($this->approved_filtered) { return $this; }

        // Этот фильтр может применяться при подсчете числа записей
        // и при выборке самих записей
        // используем флаг чтобы фильтр не применился дважды
        $this->approved_filtered = true;

        return $this->filterEqual('
is_approved', 1);

    }

    public function filterHiddenParents(){

        if ($this->hp_filtered) { return $this; }

        $this->hp_filtered = true;

        return $this->filterIsNull('
is_parent_hidden');

    }

    public function filterSubscribe($user_id){
        return $this->filterFriends($user_id, 0);
    }

    public function filterFriendsAndSubscribe($user_id){
        return $this->filterFriends($user_id, null);
    }

    public function filterFriends($user_id, $is_mutual = 1){

        $this->joinInner('
{users}_friends', 'fr', 'fr.friend_id = i.user_id');

        $this->filterEqual('
fr.user_id', (int)$user_id);

        if($is_mutual !== null){
            $this->filterEqual('
fr.is_mutual', $is_mutual);
        } else {
            // подписчики (null) и друзья (1)
            $this->filterStart();
                $this->filterEqual('
fr.is_mutual', 1);
                    $this->filterOr();
                $this->filterIsNull('
fr.is_mutual');
            $this->filterEnd();
        }

        return $this;

    }

    public function filterFriendsPrivateOnly($user_id){

        // фильтр приватности при этом не нужен
        $this->privacy_filtered = true;

        return $this->filterEqual('
i.is_private', 1)->filterFriends($user_id);

    }

    public function filterOnlineUsers() {
        return $this->filterNotNull('
online.user_id')->filterTimestampYounger('online.date_created', cmsUser::USER_ONLINE_INTERVAL, 'SECOND');
    }

    public function applyDatasetFilters($dataset, $ignore_sorting = false, $allowed_fields = []){

        if (!empty($dataset['
filters'])){

            foreach($dataset['
filters'] as $filter){

                // Если заданы разрешенные поля, проверяем
                // валидация
                if($allowed_fields && !in_array($filter['
field'], $allowed_fields, true)){
                    continue;
                }

                if (isset($filter['
callback']) && is_callable($filter['callback'])){
                    $filter['
callback']($this, $dataset); continue;
                }

                if (!isset($filter['
value'])) { continue; }
                if (($filter['
value'] === '') && !in_array($filter['condition'], array('nn', 'ni'))) { continue; }
                if (empty($filter['
condition'])) { continue; }

                if ($filter['
value'] !== '' && !is_array($filter['value'])) { $filter['value'] = string_replace_user_properties($filter['value']); }

                switch($filter['
condition']){

                    // общие условия
                    case '
eq': $this->filterEqual($filter['field'], $filter['value']); break;
                    case '
gt': $this->filterGt($filter['field'], $filter['value']); break;
                    case '
lt': $this->filterLt($filter['field'], $filter['value']); break;
                    case '
ge': $this->filterGtEqual($filter['field'], $filter['value']); break;
                    case '
le': $this->filterLtEqual($filter['field'], $filter['value']); break;
                    case '
nn': $this->filterNotNull($filter['field']); break;
                    case '
ni': $this->filterIsNull($filter['field']); break;

                    // строки
                    case '
lk': $this->filterLike($filter['field'], '%'.$filter['value'].'%'); break;
                    case '
ln': $this->filterNotLike($filter['field'], '%'.$filter['value'].'%'); break;
                    case '
lb': $this->filterLike($filter['field'], $filter['value'] . '%'); break;
                    case '
lf': $this->filterLike($filter['field'], '%' . $filter['value']); break;

                    // даты
                    case '
dy': $this->filterDateYounger($filter['field'], $filter['value']); break;
                    case '
do': $this->filterDateOlder($filter['field'], $filter['value']); break;

                    // массив
                    case '
in':
                        if(!is_array($filter['
value'])){ $filter['value'] = explode(',', $filter['value']); }
                        $this->filterIn($filter['
field'], $filter['value']);
                        break;

                }

            }

        }

        if (!empty($dataset['
sorting']) && !$ignore_sorting){
            $this->orderByList($dataset['
sorting']);
        }

        if(!empty($dataset['
index'])){
            $this->forceIndex($dataset['
index'], 2);
        }

        return true;

    }

    public function selectList($fields, $is_this_only = false, $translated_table = false){
        if($is_this_only){ $this->select = array(); }
        foreach($fields as $field => $alias){
            if($translated_table){
                $this->selectTranslatedField($field, $translated_table, $alias);
            } else {
                $this->select($field, $alias);
            }
        }
        return $this;
    }

    public function select($field, $as=false){
        $this->select[] = $as ? $field.' 
as `'.$as.'`' : $field;
        return $this;
    }

    public function selectTranslatedField($field, $table, $as = false){

        if ($this->lang == $this->default_lang) {
            return $this->select($field, $as);
        }

        $field_name = $field.'
_'.$this->lang;

        $select_name = (strpos($field, '
.') === false ? $field : ltrim(strrchr($field, '.'), '.'));

        if(!$this->db->isFieldExists($table, $select_name)){
            $field_name = $field;
        }

        return $this->select($field_name, $select_name);

    }

    public function selectOnly($field, $as=false){
        $this->select = array();
        $this->select[] = $as ? $field.' 
as `'.$as.'`' : $field;
        return $this;
    }

    public function join($table_name, $as, $on){
        return $this->joinInner($table_name, $as, $on);
    }

    public function joinInner($table_name, $as, $on){
        $this->join .= self::INNER_JOIN.' 
{#}'.$table_name.' as '.$as.' ON '.$on.PHP_EOL;
        
return $this;
    }

    public function 
joinLeft($table_name, $as, $on){
        
$this->join .= self::LEFT_JOIN.' {#}'.$table_name.' as '.$as.' ON '.$on.PHP_EOL;
        return 
$this;
    }

    public function 
joinExcludingLeft($table_name, $as, $right_key, $left_key, $join_where = ''){
        
$this->join .= self::LEFT_JOIN.' {#}'.$table_name.' as '.$as.' ON '.$left_key.'='.$right_key.($join_where ? ' AND '.$join_where : '').PHP_EOL;
        
$this->filter($right_key.' IS NULL');
        return 
$this;
    }

    public function 
joinRight($table_name, $as, $on){
        
$this->join .= self::RIGHT_JOIN.' {#}'.$table_name.' as '.$as.' ON '.$on.PHP_EOL;
        return 
$this;
    }

    public function 
joinExcludingRight($table_name, $as, $right_key, $left_key, $join_where = ''){
        
$this->join .= self::RIGHT_JOIN.' {#}'.$table_name.' as '.$as.' ON '.$left_key.'='.$right_key.($join_where ? ' AND '.$join_where : '').PHP_EOL;
        
$this->filter($left_key.' IS NULL');
        return 
$this;
    }

    public function 
joinLeftOuter($table_name, $as, $on){
        
$this->join .= self::LEFT_OUTER_JOIN.' {#}'.$table_name.' as '.$as.' ON '.$on.PHP_EOL;
        return 
$this;
    }

    public function 
joinRightOuter($table_name, $as, $on){
        
$this->join .= self::RIGHT_OUTER_JOIN.' {#}'.$table_name.' as '.$as.' ON '.$on.PHP_EOL;
        return 
$this;
    }

    
/**
     *  Присоединяет к выборке таблицу пользователей
     *
     * @param string $on_field Имя поля основной таблицы, содержащее id пользователя
     * @param array $user_fields Поля, необходимые для выборки из таблицы пользователей
     * @param boolean $join_direction Как присоединять таблицу: left|right|inner
     * @param string $as Псевдоним присоединяемой таблицы
     * @return $this
     */
    
public function joinUser($on_field = 'user_id', $user_fields = [], $join_direction = false, $as = 'u') {

        if (!
$user_fields) {
            
$user_fields = [
                
$as . '.nickname'   => 'user_nickname',
                
$as . '.slug'       => 'user_slug',
                
$as . '.is_deleted' => 'user_is_deleted',
                
$as . '.groups'     => 'user_groups',
                
$as . '.avatar'     => 'user_avatar'
            
];
        }

        foreach (
$user_fields as $field => $alias) {
            
$this->select($field, $alias);
        }

        switch (
$join_direction) {

            case 
'left':
                
$this->joinLeft('{users}', $as, $as . '.id = i.' . $on_field);
                break;

            case 
'right':
                
$this->joinRight('{users}', $as, $as . '.id = i.' . $on_field);
                break;

            default:
                
$this->join('{users}', $as, $as . '.id = i.' . $on_field);
                break;
        }

        return 
$this;
    }

    public function 
joinUserLeft($on_field='user_id', $user_fields=array()){
        return 
$this->joinUser($on_field, $user_fields, 'left');
    }

    public function 
joinUserRight($on_field='user_id', $user_fields=array()){
        return 
$this->joinUser($on_field, $user_fields, 'right');
    }

    public function 
joinSessionsOnline($as = 'u') {

        if(!empty(
$this->joined_session_online[$as])){ return $this; }

        
$this->joinLeft('sessions_online', 'online', 'online.user_id = '.$as.'.id');
        
$this->select('IF(online.date_created IS NOT NULL AND TIMESTAMPDIFF(SECOND, online.date_created, NOW()) <= '.cmsUser::USER_ONLINE_INTERVAL.', 1, 0)', 'is_online');

        
$this->joined_session_online[$as] = true;

        return 
$this;

    }

    public function 
groupBy($field){
        if (
strpos($field, '.') === false){ $field = 'i.' . $field; }
        
$this->group_by = $field;
        return 
$this;
    }

    
/**
     * Управляет индексами в запросе
     * @param string $index_name Название индекса в БД
     * @param string $action FORCE | IGNORE | USE
     * @param int $for 1 - FOR JOIN, 2 - FOR ORDER BY, 3 - FOR GROUP BY
     * @return cmsModel
     */
    
protected function indexHint($index_name, $action, $for='') {
        switch (
$for) {
            case 
1:
                
$for_action = 'FOR JOIN';
                break;
            case 
2:
                
$for_action = 'FOR ORDER BY';
                break;
            case 
3:
                
$for_action = 'FOR GROUP BY';
                break;
            default:
                
$for_action = '';
                break;
        }
        
$this->index_action .= "{$action} INDEX {$for_action} ({$index_name})n";
        return 
$this;
    }

    public function 
forceIndex($index_name, $for='') {
        return 
$this->indexHint($index_name, 'FORCE', $for);
    }

    public function 
ignoreIndex($index_name, $for='') {
        return 
$this->indexHint($index_name, 'IGNORE', $for);
    }

    public function 
useIndex($index_name, $for='') {
        return 
$this->indexHint($index_name, 'USE', $for);
    }

    public function 
orderBy($field, $direction='', $is_force_index_by_field = false){
        if(
strpos($field, '(') !== false){ return $this; } // в названии поля не может быть функции
        
if($direction){
            
$direction = strtolower($direction) === 'desc' ? 'desc' : 'asc';
        }
        if (
strpos($field, '.') === false){ $field = 'i.'.$field; }
        
$this->order_by = $field.' '.$direction;
        return 
$this;
    }

    public function 
orderByList($list){

        
$this->order_by = '';

        if (
is_array($list)){

            foreach(
$list as $o){

                if(
strpos($o['by'], '(') !== false){ continue; }

                
$field     = $o['by'];
                
$direction = strtolower($o['to']) === 'desc' ? 'desc' : 'asc';

                if (empty(
$o['strict']) && strpos($field, '.') === false){ $field = 'i.'.$field; }
                if (
$this->order_by) { $this->order_by .= ', '; }
                
$this->order_by .= $field.' '.$direction;

            }

        }

        return 
$this;

    }

    public function 
limit($from, $howmany=0) {
        
$this->limit = (int)$from;
        
$howmany     = (int)$howmany;
        if (
$this->limit < 0) { $this->limit = 0; }
        if (
$howmany){
            if (
$howmany <= 0){ $howmany = 15; }
            
$this->limit .= ', '. $howmany;
        }
        return 
$this;
    }

    public function 
limitPage($page, $perpage=0) {
        
$page    = (int) $page;
        
$perpage = (int) $perpage;
        if (
$perpage <= 0) { $perpage = $this->perpage; }
        
$this->limit(($page-1)*$perpage, $perpage);
        return 
$this;
    }

    public function 
limitPagePlus($page, $perpage=0) {
        
$page    = (int) $page;
        
$perpage = (int) $perpage;
        if (
$perpage <= 0) { $perpage = $this->perpage; }
        
$this->limit(($page-1)*$perpage, $perpage+1);
        return 
$this;
    }

    public function 
setPerPage($perpage){
        
$this->perpage = (int)$perpage;
        return 
$this;
    }

    public function 
setReadType($type){
        
$this->read_type = $type;
        return 
$this;
    }

//============================================================================//
//============================== Транзакции ==================================//

    
public function processTransaction($payload_callback, $after_autocommit_on = false) {

        
// нам не нужно, чтобы внутри транзакции при ошибке запроса
        // всё умирало
        
$this->db->query_quiet = true;

        
// флаг результата выполнения
        
$success = true;

        
// мы внутри транзакции?
        
$is_autocommit_on = $this->db->isAutocommitOn();

        
// выключаем автокоммит, чтобы все запросы были в транзакции
        // если автокоммит выключен, то мы уже в транзакции
        
if ($is_autocommit_on) {
            
$this->db->autocommitOff();
        }

        try {

            
$success = call_user_func_array($payload_callback, array($this));

        } catch (
Exception $e) {

            
error_log($e->getMessage());

            
$success = false;

        }

        
$this->db->query_quiet = null;

        if (
$is_autocommit_on || $after_autocommit_on) {

            
$this->endTransaction($success);

        }

        return 
$success;

    }

    public function 
startTransaction() {

        
$this->is_transaction_started = true;

        
$this->db->autocommitOff();

        return 
$this;

    }

    public function 
endTransaction($success) {

        if (
$success) {

            
$this->db->commit();

        } else {

            
$this->db->rollback();

        }

        
$this->db->autocommitOn();

        
$this->is_transaction_started = false;

        return 
$this;

    }

    public function 
forUpdate() {
        return 
$this->setReadType('FOR UPDATE');
    }

    public function 
lockInShareMode() {
        return 
$this->setReadType('LOCK IN SHARE MODE');
    }

    public function 
setTransactionIsolationLevel($level) {
        
$this->db->query("SET TRANSACTION ISOLATION LEVEL {$level};"); return $this;
    }

//============================================================================//
//============================================================================//

    
public function getField($table_name, $row_id, $field_name){

        
$this->filterEqual('id', $row_id);
        return 
$this->getFieldFiltered($table_name, $field_name);

    }

    public function 
getFieldFiltered($table_name, $field_name){

        
$this->select = array('i.'.$field_name.' as '.$field_name);

        
$this->table = $table_name;

        
$this->limit(1);

        
$sql = $this->getSQL();

        
$this->resetFilters();

        
$result = $this->db->query($sql);

        if (!
$this->db->numRows($result)){ return false; }

        
$item = $this->db->fetchAssoc($result);

        if(
$this->localized){
            
$item = $this->replaceTranslatedField($item, $table_name);
        }

        
$this->db->freeResult($result);

        return 
$item[ $field_name ];

    }

//============================================================================//
//============================================================================//

    
public function getItem($table_name, $item_callback = false){

        
$select = implode(', ', $this->select);

        
$sql = "SELECT {$select}
                FROM {#}
{$table_name} i
                
{$this->index_action}";

        if (
$this->join){ $sql .= $this->join; }

        if (
$this->where){ $sql .= 'WHERE '.$this->where.PHP_EOL; }

        if (
$this->order_by){ $sql .= 'ORDER BY '.$this->order_by.PHP_EOL; }

        
$sql .= 'LIMIT 1';

        if (
$this->read_type){
            
$sql .= PHP_EOL.$this->read_type;
        }

        
$this->resetFilters();

        
// если указан ключ кеша для этого запроса
        // то пробуем получить результаты из кеша
        
if ($this->cache_key){

            
$cache_key = $this->cache_key . '.' . md5($sql);
            
$cache = cmsCache::getInstance();

            
$item = $cache->get($cache_key);

            if (
$item){

                if(
is_callable($item_callback)){
                    
$item = call_user_func_array($item_callback, array($item, $this));
                }

                if(
$this->localized){
                    
$item = $this->replaceTranslatedField($item, $table_name);
                }

                
$this->stopCache();

                return 
$item;
            }

        }

        
$result = $this->db->query($sql);

        if (!
$this->db->numRows($result)){ return false; }

        
$item = $this->db->fetchAssoc($result);

        
// для кеша формируем массив без обработки коллбэком
        
if ($this->cache_key){
            
$_item = $item;
        }

        if(
is_callable($item_callback)){
            
$item = call_user_func_array($item_callback, array($item, $this));
        }

        if(
$this->localized){
            
$item = $this->replaceTranslatedField($item, $table_name);
        }

        
// если указан ключ кеша для этого запроса
        // то сохраняем результаты в кеше
        
if ($this->cache_key){
            
$cache->set($cache_key, $_item);
            
$this->stopCache();
        }

        
$this->db->freeResult($result);

        return 
$item;

    }

    public function 
getItemById($table_name, $id, $item_callback=false){
        
$this->filterEqual('id', $id);
        return 
$this->getItem($table_name, $item_callback);
    }

    public function 
getItemByField($table_name, $field_name, $field_value, $item_callback=false){
        
$this->filterEqual($field_name, $field_value);
        return 
$this->getItem($table_name, $item_callback);
    }

//============================================================================//
//============================================================================//

    
public function getCount($table_name, $by_field = 'id', $reset = false){

        
$sql = "SELECT {$this->straight_join} COUNT({$this->distinct} i.{$by_field} ) as count
                FROM {#}
{$table_name} i
                
{$this->index_action}";

        if (
$this->join){ $sql .= $this->join; }

        if (
$this->where){ $sql .= 'WHERE '.$this->where.PHP_EOL; }

        if (
$this->group_by){ $sql .= 'GROUP BY '.$this->group_by.PHP_EOL; }

        if(
$reset){
            
$this->resetFilters();
        }

        
// если указан ключ кеша для этого запроса
        // то пробуем получить результаты из кеша
        
if ($this->cache_key){

            
$cache_key = $this->cache_key . '.' . md5($sql);
            
$cache = cmsCache::getInstance();

            if (
false !== ($result = $cache->get($cache_key))){
                
$this->stopCache();
                return 
$result;
            }

        }

        
$result = $this->db->query($sql);

        if (!
$this->db->numRows($result)){
            
$count = 0;
        } else {
            
$item = $this->db->fetchAssoc($result);
            
$count = (int)$item['count'];
        }

        
// если указан ключ кеша для этого запроса
        // то сохраняем результаты в кеше
        
if ($this->cache_key){
            
$cache->set($cache_key, $count);
            
$this->stopCache();
        }

        
$this->db->freeResult($result);

        return 
$count;

    }

//============================================================================//
//============================================================================//

    /**
     * Возвращает записи из базы, применяя все наложенные ранее фильтры
     * @return array
     */
    
public function get($table_name, $item_callback=false, $key_field='id'){

        
$this->table = $table_name;

        
$items = $_items = array();

        
$sql = $this->getSQL();

        
// сбрасываем фильтры
        
$this->resetFilters();

        
// если указан ключ кеша для этого запроса
        // то пробуем получить результаты из кеша
        
if ($this->cache_key){

            
$cache_key = $this->cache_key . '.' . md5($sql);

            
$cache = cmsCache::getInstance();

            
$_items = $cache->get($cache_key);

            if (
$_items !== false){

                
$this->stopCache();

                
// обрабатываем коллбэком
                
if (is_callable($item_callback)){

                    foreach (
$_items as $key => $item) {

                        
$item = call_user_func_array($item_callback, array($item, $this));
                        if (
$item === false){ continue; }

                        if(
$this->localized){
                            
$item = $this->replaceTranslatedField($item, $table_name);
                        }

                        
$items[$key] = $item;

                    }

                } else {
                    return 
$_items;
                }

                return 
$items;

            }

        }

        
$result = $this->db->query($sql);

        
// если запрос ничего не вернул, возвращаем ложь
        
if (!$this->db->numRows($result)){ return false; }

        
// перебираем все вернувшиеся строки
        
while($item = $this->db->fetchAssoc($result)){

            
$key = ($key_field && isset($item[$key_field])) ? $item[$key_field] : false;

            
// для кеша формируем массив без обработки коллбэком
            
if ($this->cache_key){
                if (
$key){
                    
$_items[$key] = $item;
                } else {
                    
$_items[] = $item;
                }
            }

            
// если задан коллбек для обработки строк,
            // то пропускаем строку через него
            
if (is_callable($item_callback)){
                
$item = call_user_func_array($item_callback, array($item, $this));
                if (
$item === false){ continue; }
            }

            if(
$this->localized){
                
$item = $this->replaceTranslatedField($item, $table_name);
            }

            
// добавляем обработанную строку в результирующий массив
            
if ($key){
                
$items[$key] = $item;
            } else {
                
$items[] = $item;
            }

        }

        
// если указан ключ кеша для этого запроса
        // то сохраняем результаты в кеше
        // сохраняем не обработанный коллбэком массив
        
if ($this->cache_key){
            
$cache->set($cache_key, $_items);
            
$this->stopCache();
        }

        
$this->db->freeResult($result);

        
// возвращаем строки
        
return $items;

    }

//============================================================================//
//============================================================================//

    
public function getSQL(){

        
$select = implode(', ', $this->select);

        
$sql = "SELECT {$this->distinct} {$this->straight_join} {$select}
                FROM {#}
{$this->table} i
                
{$this->index_action}";

        if (
$this->join){ $sql .= $this->join; }

        if (
$this->where){ $sql .= 'WHERE '.$this->where.PHP_EOL; }

        if (
$this->group_by){ $sql .= 'GROUP BY '.$this->group_by.PHP_EOL; }

        if (
$this->order_by){ $sql .= 'ORDER BY '.$this->order_by.PHP_EOL; }

        if (
$this->limit){ $sql .= 'LIMIT '.$this->limit.PHP_EOL; }

        if (
$this->read_type){ $sql .= $this->read_type.PHP_EOL; }

        return 
$sql;

    }

//============================================================================//
//============================================================================//
    /**
     * Возвращает максимальное или минимальное
     * значение поля таблицы
     *
     * @param string $table Таблица
     * @param string $field Название поля
     * @param integer $default Значение по умолчанию
     * @param string $dir Направление: MAX или MIN
     * @return integer
     */
    
public function getMax($table, $field, $default = 0, $dir = 'MAX') {

        
$sql = "SELECT {$dir}(i.{$field}) as {$field}
                FROM {#}
{$table} i
                "
;

        if (
$this->where) {
            
$sql .= 'WHERE ' . $this->where . PHP_EOL;
        }

        
$sql .= 'LIMIT 1';

        
$result = $this->db->query($sql);

        
$this->resetFilters();

        if (!
$this->db->numRows($result)) {
            return 
$default;
        }

        
$max = $this->db->fetchAssoc($result);

        
$this->db->freeResult($result);

        return 
$max[$field] ?: 0;
    }

    public function 
getMin($table, $field, $default = 0) {
        return 
$this->getMax($table, $field, $default, 'MIN');
    }

    
/**
     * Возвращает максимальный порядковый номер в таблице
     * @param string $table
     * @param string $where
     * @return int
     */
    
public function getMaxOrdering($table){

        return 
$this->getMax($table, 'ordering');

    }

    
/**
     * Возращает следующий порядковый номер в таблице для новых записей
     * @param string $table
     * @param string $where
     * @return int
     */
    
public function getNextOrdering($table){

        return 
$this->getMaxOrdering($table) + 1;

    }

    
/**
     * Пересчитывает порядковые номера в таблице
     * @param string $table_name
     * @param string $where
     * @return bool
     */
    
public function reorder($table_name){

        
$sql = "SELECT i.id as id, i.ordering as ordering
                FROM {#}
{$table_name} i
                "
;

        if (
$this->where){ $sql .= 'WHERE '.$this->where.PHP_EOL; }

        
$sql .= 'ORDER BY ordering';

        
$result = $this->db->query($sql);

        
$this->resetFilters();

        if (!
$this->db->numRows($result)){
            
$this->db->freeResult($result);
            return 
false;
        }

        
$ordering = 0;

        while(
$item = $this->db->fetchAssoc($result)){

            
$ordering += 1;
            
$this->db->query("UPDATE {#}{$table_name} SET ordering = {$ordering} WHERE id = '{$item['id']}'");

        }

        
$this->db->freeResult($result);

        return 
true;

    }

    
/**
     * Расставляет порядковые номера для списка из ID записей
     * @param string $table_name
     * @param string $list
     * @param array $additional_fields Список дополнительных полей и их значений, которые нужно обновлять вместе с ordering
     * @return bool
     */
    
public function reorderByList($table_name, $list, $additional_fields=false){

        
$ordering = 0;

        
$additional_set = array();

        if (
is_array($additional_fields)){
            foreach(
$additional_fields as $field=>$value){
                
$value = $this->db->escape($value);
                
$additional_set[] = "{$field} = '{$value}'";
            }
        }

        if (
$additional_set){
            
$additional_set = ', ' . implode(', ', $additional_set);
        } else {
            
$additional_set = '';
        }

        foreach(
$list as $id){

            
$ordering += 1;

            
$id = $this->db->escape($id);

            
$query = "UPDATE {#}{$table_name}
                      SET ordering = '
{$ordering}' {$additional_set}
                      WHERE id = '
{$id}'";

            
$this->db->query($query);

        }

        return 
true;

    }

//============================================================================//
//============================================================================//

    /**
     * Применяет к модели фильтры, переданные из просмотра
     * таблицы со списком записей
     * @param array $grid
     * @param array $filter
     * @return bool
     */
    
public function applyGridFilter($grid, $filter){

        
// применяем сортировку
        
if (!empty($filter['order_by'])) {
            if (!empty(
$grid['columns'][$filter['order_by']]['order_by'])){
                
$filter['order_by'] = $grid['columns'][$filter['order_by']]['order_by'];
            }
            
$this->orderBy($filter['order_by'], $filter['order_to']);
        }

        
// устанавливаем страницу
        
if (!empty($filter['page'])){
            
$perpage = !empty($filter['perpage']) ? (int)$filter['perpage'] : $this->perpage;
            
$this->limitPage((int)$filter['page'], $perpage);
        }

        
//
        // проходим по каждой колонке таблицы
        // и проверяем не передан ли фильтр для нее
        //
        
foreach($grid['columns'] as $field => $column){
            if (!empty(
$column['filter']) && $column['filter'] != 'none' && isset($filter[$field])){

                if (
$filter[$field] || (string)$filter[$field] === '0'){

                    if (!empty(
$column['filter_by'])){
                        
$filter_field = $column['filter_by'];
                    } else {
                        
$filter_field = $field;
                    }

                    switch (
$column['filter']){
                        case 
'in': $this->filterIn($filter_field, explode(',', $filter[$field])); break;
                        case 
'exact': $this->filterEqual($filter_field, $filter[$field]); break;
                        case 
'like': $this->filterLike($filter_field, "%{$filter[$field]}%"); break;
                        case 
'date':
                            
$date = date('Y-m-d', strtotime($filter[$field]));
                            
$this->filterLike($filter_field, "%{$date}%"); break;
                    }

                }

            }
        }

        return 
$this;

    }

//============================================================================//
//============================================================================//

    
public function increment($table, $field, $step=1){

        
$sign = $step > 0 ? '+' : '-';
        
$step = abs($step);

        
$sql = "UPDATE {#}{$table} i
                SET i.
{$field} = i.{$field} {$sign} {$step}
                "
;

        if (
$this->where){ $sql .= 'WHERE '.$this->where; }

        
$this->resetFilters();

        return 
$this->db->query($sql, false, true);

    }

    public function 
decrement($table, $field, $step=1){
        return 
$this->increment($table, $field, $step * -1);
    }

    public function 
deleteController($name) {

        if(
is_numeric($name)){
            
$controller = $this->getItemById('controllers', $name);
            
$name = $controller['name'];
        }

        
$this->filterEqual('listener', $name)->deleteFiltered('events');

        
cmsCache::getInstance()->clean('events');

        
$rule_ids = $this->selectOnly('id')->filterEqual('controller', $name)->get('perms_rules', function($item, $model){
            return 
$item['id'];
        }, 
false);

        if(
$rule_ids){
            
$this->filterIn('rule_id', $rule_ids)->deleteFiltered('perms_users');
            
$this->filterEqual('controller', $name)->deleteFiltered('perms_rules');
        }

        
$this->filterEqual('controller', $name)->deleteFiltered('scheduler_tasks');

        return 
$this->filterEqual('name', $name)->deleteFiltered('controllers');
    }

    public function 
fieldsAfterStore($item, $fields, $action = 'add') {

        foreach(
$fields as $field){
            
$field['handler']->afterStore($item, $this, $action);
        }

        return 
$this;

    }

    
/**
     * Возвращает количество массив количества записей в черновиках
     * Для нужных контроллеров должна быть переопределена
     * в их моделях
     *
     * @param integer $user_id
     * @return integer | array
     */
    
public function getDraftCounts($user_id){
        return 
0;
    }

//============================================================================//
//============================================================================//

    /**
     * Сортирует элементы массива $items в виде плоского дерева
     * на основании связей через parent_id
     * @param array $items
     * @param array $result_tree
     * @param int $parent_id
     * @param int $level
     */
    
public static function buildTreeRecursive($items, &$result_tree, $parent_id=0, $level=1){
        
$level++;
        foreach(
$items as $item){
            if (
$item['parent_id']==$parent_id){
                
$item['level'] = $level-1;
                if (!isset(
$result_tree[$item['id']])){
                    
$result_tree[$item['id']] = $item;
                }
                
self::buildTreeRecursive($items, $result_tree, $item['id'], $level);
            }
        }
    }

//============================================================================//
//============================================================================//

    /**
     * Преобразует массив в YAML
     * @param array $input_array
     * @param integer $indent
     * @param integer $word_wrap
     * @return string
     */
    
public static function arrayToYaml($input_array, $indent = 2, $word_wrap = 40) {

        
$array = [];

        if(
cmsConfig::get('native_yaml')){

            if(!empty(
$input_array)){
                
$array = $input_array;
            }

            return 
yaml_emit($array, YAML_UTF8_ENCODING);
        }

        if(!empty(
$input_array)){

            foreach (
$input_array as $key => $value) {
                
$_k = str_replace(array('[',']'), '', $key); // был фатальный баг, если в ключах эти символы
                
$array[$_k] = $value;
            }

        }

        return 
Spyc::YAMLDump($array, $indent, $word_wrap);

    }

    
/**
     * Преобразует YAML в массив
     * @param string $yaml
     * @return array
     */
    
public static function yamlToArray($yaml) {

        if(!
$yaml){ return array(); }

        if(
is_array($yaml)){ return $yaml; }

        if(
$yaml === "---n- 0n"){ return array(); }
        if(
$yaml === "---n- "0"n...n"){ return array(); }

        if(
cmsConfig::get('native_yaml')){
            return 
yaml_parse($yaml);
        }

        return 
Spyc::YAMLLoadString($yaml);

    }

    
/**
     * Преобразует массив в строку
     * @param array $input_array
     * @return string
     */
    
public static function arrayToString($input_array) {
        if(!
is_array($input_array)){
            return 
null;
        }
        return 
json_encode($input_array);
    }

    
/**
     * Преобразует строку в массив
     * @param string $string
     * @return array
     */
    
public static function stringToArray($string) {
        if(!
$string){ return array(); }
        return (array)
json_decode($string, true);
    }

    
/**
     * Кеширует данные в пределах запроса
     * @param string $key Ключ
     * @param mixed $data Данные
     */
    
public static function cacheResult($key, $data) {
        
self::$cached[$key] = $data;
    }
    public static function 
getCachedResult($key) {
        if(isset(
self::$cached[$key])){
            return 
self::$cached[$key];
        }
        return 
null;
    }

}
Онлайн: 1
Реклама