imagecms/ImageCMS

View on GitHub
application/modules/search/search.php

Summary

Maintainability
F
4 days
Test Coverage
<?php

if (!defined('BASEPATH')) {
    exit('No direct script access allowed');
}

/**
 * Image CMS
 *
 * Search Module
 * TODO:
 *     refresh cache after $search_ttl expry.
 */
class Search extends MY_Controller
{

    public $search_ttl = 3600; //Search time to live in minutes.

    public $table = '';

    public $cache_on = FALSE;

    public $row_count = 15;

    public $table_pk = 'content.id';

    public $search_tpl = 'search';

    public $query_hash = '';

    public $search_title = '';

    public $hash_prefix = '';

    public $hash_data = FALSE;

    public $select = [];

    public $title_delimiter = ' - ';

    public $default_operator = 'where';

    public $min_s_len = 3; // Min. length to make search request.

    private $where = [];

    private $order_by = [];

    private $search_table = 'search';

    public function __construct() {
        parent::__construct();
        $lang = new MY_Lang();
        $lang->load('search');
    }

    // Search pages

    public function index($hash = '', $offset = 0) {
        $this->template->registerMeta('ROBOTS', 'NOINDEX, NOFOLLOW');

        $offset = (int) $offset;

        if ($hash != '') {
            $hash_data = $this->query($hash, $offset);
            $s_text = $this->search_title;
        } else {
            $s_text = $this->getSearchText();
        }

        $text_len = mb_strlen(trim($s_text), 'UTF-8');

        if ($text_len >= $this->min_s_len AND $text_len < 50) {
            $config = [
                       'table'    => 'content',
                       'order_by' => ['publish_date' => 'DESC'],
                       'select'   => [
                                      'content.*',
                                      'IF(route.parent_url <> \'\', concat(route.parent_url, \'/\', route.url), route.url) as full_url',
                                     ],
                      ];

            $this->init($config);

            $where = [
                      [
                       'post_status' => 'publish',
                       'operator'    => 'WHERE',
                      ],
                      [
                       'publish_date <=' => 'UNIX_TIMESTAMP()',
                       'backticks'       => FALSE,
                      ],
                      [
                       'lang' => $this->config->item('cur_lang'),
                      ],
                      [
                       'group1' => '(title LIKE "%' . $this->db->escape_str($s_text) . '%" OR prev_text LIKE "%' . $this->db->escape_str($s_text) . '%" OR full_text LIKE "%' . $this->db->escape_str($s_text) . '%" )',
                       'group'  => TRUE,
                      ],
                     ];

            /** Data for categories in search * */
            $dataForFoundInCategories = $this->countSearchResults($where);
            $dataForFoundInCategories = $dataForFoundInCategories->result_array();

            if ($hash == '') {
                $result = $this->execute($where, $offset);
            } else {
                $result = $this->query($hash, $offset);
            }

            if (!$this->search_title) {
                $this->search_title = $s_text;
            }

            //Pagination
            if ($result['total_rows'] > $this->row_count) {
                $this->load->library('Pagination');

                $paginationConfig['base_url'] = site_url('search/index/' . $result['hash'] . '/');
                $paginationConfig['total_rows'] = $result['total_rows'];
                $paginationConfig['per_page'] = $this->row_count;
                $paginationConfig['uri_segment'] = 4;
                include_once "./templates/{$this->config->item('template')}/paginations.php";
                $paginationConfig['page_query_string'] = FALSE;

                $this->pagination->initialize($paginationConfig);
                $this->template->assign('pagination', $this->pagination->create_links());
            }
            //End pagination
        } else {
            $result = FALSE;
        }

        if ($result === FALSE) {
            $data = FALSE;
        } else {
            $data = $result['query']->result_array();
        }

        if (isset($s_text)) {
            $this->template->assign('search_title', $s_text);
        }

        $data = $this->_highlight_text($data, $s_text);

        $this->core->set_meta_tags([lang('Search', 'search'), $this->search_title]);
        $this->core->core_data['data_type'] = 'search';
        $this->_display($data, $dataForFoundInCategories);
    }

    public function getSearchText() {
        $text = $this->input->post('text') ? $this->input->post('text') : $this->input->get('text');
        return trim($text);
    }

    /**
     * Highlight found text
     * @param array $data Pages to highlight
     * @param string $text
     * @return array
     */
    protected function _highlight_text($data, $text) {
        if (!$data) {
            return;
        }
        $dataCount = count($data);
        for ($i = 0; $i < $dataCount; $i++) {
            $tempText = strip_tags($data[$i]['prev_text'] . ' ' . $data[$i]['full_text']);
            $pos = mb_strpos($tempText, $text);
            $length = mb_strlen($tempText, 'UTF-8');
            $start = $pos - 40;
            $stop = $pos + 40;
            if ($start < 0) {
                $start = 0;
            }
            if ($stop > $length) {
                $stop = $length;
            }

            $tempText = mb_substr($tempText, $start, $stop, 'UTF-8');
            $tempText = str_replace($text, '<mark>' . $text . '</mark>', $tempText);
            $data[$i]['parsedText'] = '...' . mb_substr($tempText, 0, 500, 'utf-8') . '...';
        }

        return $data;
    }

    // Init search settings

    public function init($config = []) {
        foreach ($config as $key => $val) {
            if (isset($this->$key)) {
                $this->$key = $val;
            } else {
                $m = 'set_' . $key;

                if (method_exists($this, $m)) {
                    $this->$m($val);
                }
            }
        }
    }

    public function clear() {
        $this->search_ttl = 600;
        $this->table = '';
        $this->cache_on = FALSE;
        $this->default_operator = 'where';
        $this->where = [];
        $this->order_by = [];
        $this->search_table = 'search';
        $this->query_hash = '';
        $this->hash_data = FALSE;
    }

    // Search by hash

    public function query($hash = '', $offset = 0) {
        if (($hash_data = $this->hash_data($hash)) == FALSE) {
            $this->load->module('core');
            $this->core->error_404();
        }

        $this->table = $hash_data->table_name;
        $this->hash = $hash_data->hash;
        $this->order_by = unserialize($hash_data->order_by);
        $this->select = unserialize($hash_data->select_array);
        $this->search_title = $this->hash_data->search_title;

        return $this->execute(unserialize($hash_data->where_array), $offset);
    }

    // Search

    public function execute($where = [], $offset = 0) {
        $collect_ids = FALSE;

        if ($this->table == '') {
            $error = lang('Error. Select or specify the table for search', 'search');
            return $error;
        }

        $this->query_hash = $this->generate_hash($where);

        $hs = $this->hash_data();

        if ($this->hash_data->datetime + $this->search_ttl < time() AND $this->hash_data->datetime > 0) {
            $refresh = TRUE;
            $this->hash_data->datetime = time();
        } else {
            $refresh = FALSE;
        }

        if ($hs == FALSE OR $refresh == TRUE) {
            $collect_ids = TRUE;

            if ($refresh == FALSE) {
                // Store query data
                if (!$this->search_title) {
                    $this->search_title = $this->getSearchText();
                }

                $q_data = [
                           'hash'         => $this->query_hash,
                           'datetime'     => time(),
                           'where_array'  => serialize($where),
                           'select_array' => serialize($this->select),
                           'table_name'   => $this->table,
                           'order_by'     => serialize($this->order_by),
                           'row_count'    => $this->row_count,
                           'search_title' => $this->search_title,
                          ];

                $this->db->insert($this->search_table, $q_data);
            }
        } else {
            if (!is_array($this->hash_data->ids)) {
                $this->hash_data->ids = unserialize($this->hash_data->ids);
            }

            $where = [];
            $ids = [];

            for ($i = $offset; $i < $offset + $this->row_count; $i++) {
                if (isset($this->hash_data->ids[$i])) {
                    $ids[] = $this->hash_data->ids[$i];
                }
            }

            if (count($ids) > 0) {
                $this->db->where_in($this->table_pk, $ids);
            } else {
                return FALSE;
            }
        }

        // begin query
        if (count($where) > 0) {
            foreach ($where as $params) {
                // Set search operator. (where, like, or_where, etc..)
                if (isset($params['operator'])) {
                    $operator = strtolower($params['operator']);
                    unset($params['operator']);
                } else {
                    $operator = $this->default_operator;
                }

                // Protect field names with backticks.
                if (isset($params['backticks'])) {
                    $backticks = $params['backticks'];
                    unset($params['backticks']);
                } else {
                    $backticks = TRUE;
                }

                if (isset($params['group']) AND $params['group'] == TRUE) {
                    $use_group = TRUE;
                    unset($params['group']);
                } else {
                    $use_group = FALSE;
                }

                foreach ($params as $key => $val) {
                    if ($use_group == FALSE) {
                        $this->db->$operator($key, $val, $backticks);
                    } else {
                        $this->db->where($val);
                    }
                }
            }
        }

        // Set order_by params
        if (count($this->order_by) > 0) {
            foreach ($this->order_by as $key => $val) {
                $this->db->order_by($key, $val);
            }
        }

        // Add SELECT string
        if (count($this->select) > 0) {
            foreach ($this->select as $key => $val) {
                $this->db->select($val, false);
            }
        }

        if ($collect_ids == TRUE) {
            $ids = [];

            $this->db->select($this->table_pk);
            $this->db->join('route', 'route.id=content.route_id');
            $query = $this->db->get($this->table)->result_array();

            foreach ($query as $row) {
                $ids[] = $row['id'];
            }
            $this->db->where('hash', $this->query_hash);
            $this->db->update('search', ['datetime' => time(), 'ids' => serialize($ids), 'total_rows' => count($ids)]);

            return $this->execute($where, $offset);
        } else {
            if (!$this->search_title) {
                $this->search_title = $this->input->post('text');
            }
            $this->db->join('route', 'route.id=content.route_id');

            $data = [
                     'query'        => $this->db->get($this->table),
                     'total_rows'   => $this->hash_data->total_rows,
                     'hash'         => $this->query_hash,
                     'search_title' => $this->search_title,
                    ];

            return $data;
        }
    }

    private function countSearchResults($where) {
        // begin query
        if (count($where) > 0) {
            foreach ($where as $params) {
                // Set search operator. (where, like, or_where, etc..)
                if (isset($params['operator'])) {
                    $operator = strtolower($params['operator']);
                    unset($params['operator']);
                } else {
                    $operator = $this->default_operator;
                }

                // Protect field names with backticks.
                if (isset($params['backticks'])) {
                    $backticks = $params['backticks'];
                    unset($params['backticks']);
                } else {
                    $backticks = TRUE;
                }

                if (isset($params['group']) AND $params['group'] == TRUE) {
                    $use_group = TRUE;
                    unset($params['group']);
                } else {
                    $use_group = FALSE;
                }

                foreach ($params as $key => $val) {
                    if ($use_group == FALSE) {
                        $res = $this->db->$operator($key, $val, $backticks);
                    } else {
                        $res = $this->db->where($val);
                    }
                }
            }
        }

        return $res->get($this->table);
    }

    // Generate search hash

    private function generate_hash($where = []) {
        return sha1($this->hash_prefix . $this->table . serialize($this->order_by) . serialize($where) . $this->row_count . serialize($this->select));
    }

    /**
     * @return integer
     */
    private function hash_data($hash = '') {
        if ($hash == '') {
            $hash = $this->query_hash;
        }

        if ($this->hash_data != FALSE) {
            return $this->hash_data;
        }

        $this->db->limit(1);
        $this->db->where('hash', $hash);
        $query = $this->db->get($this->search_table);

        if ($query->num_rows == 1) {
            $this->hash_data = $query->row();
            return $query->row();
        } else {
            return FALSE;
        }
    }

    // Display search template file

    public function _display($pages = [], $foundInCategories = null) {
        /*         * Prepare categories for search results * */
        $categoriesInSearchResults = null;
        $tree = null;
        $categories = [];

        if ($foundInCategories != null) {
            $this->load->library('lib_category');
            foreach ($foundInCategories as $key => $value) {
                if (array_key_exists($value['category'], $categories)) {
                    $categories[$value['category']]['count'] ++;
                } else {
                    $value['count'] = 1;
                    $categories[$value['category']] = $value;
                }
            }

            $categoriesInSearchResults = $this->prepareCategoriesForSearchResults($categories);
            $tree = $this->lib_category->build();
            $categoriesInfo = $this->lib_category->unsorted();
        }

        if (count($pages) > 0) {
            ($hook = get_hook('core_return_category_pages')) ? eval($hook) : NULL;

            $this->template->add_array(
                [
                 'items'                     => $pages,
                 'categoriesInSearchResults' => $categoriesInSearchResults,
                 'tree'                      => $tree,
                 'countAll'                  => count($foundInCategories),
                 'categoriesInfo'            => $categoriesInfo,
                ]
            );
        }

        $this->template->show($this->search_tpl);
    }

    /**
     * Prepare categories for search results
     * @param array $foundInCategories
     * @return boolean|array
     */
    private function prepareCategoriesForSearchResults($foundInCategories) {
        $categoriesArray = [];
        $categoriesAll = $this->lib_category->unsorted();
        foreach ($categoriesAll as $key => $value) {
            /** Count of found pages in category * */
            if (array_key_exists($key, $foundInCategories)) {
                $categoriesArray[$key] = $foundInCategories[$key]['count'];
            }
            /** For fetched pages * */
            $mainCategory = $key;
            if (($fetchCategories = unserialize($categoriesAll[$mainCategory]['fetch_pages'])) != false) {
                foreach ($foundInCategories as $page) {
                    if (in_array($page['category'], $fetchCategories)) {
                        if (array_key_exists($mainCategory, $categoriesArray)) {
                            $categoriesArray[$mainCategory] = $categoriesArray[$mainCategory] + $page['count'];
                        } else {
                            $categoriesArray[$mainCategory] = $page['count'];
                        }
                    }
                }
            }
        }
        return $categoriesArray;
    }

    // Create search table

    public function _install() {
        if ($this->dx_auth->is_admin() == FALSE) {
            exit;
        }

        $this->load->dbforge();

        $fields = [
                   'id'           => [
                                      'type'           => 'INT',
                                      'constraint'     => 11,
                                      'auto_increment' => TRUE,
                                     ],
                   'hash'         => [
                                      'type'       => 'VARCHAR',
                                      'constraint' => 264,
                                     ],
                   'datetime'     => [
                                      'type'       => 'INT',
                                      'constraint' => 11,
                                     ],
                   'where_array'  => ['type' => 'TEXT'],
                   'select_array' => ['type' => 'TEXT'],
                   'table_name'   => [
                                      'type'       => 'VARCHAR',
                                      'constraint' => 100,
                                     ],
                   'order_by'     => ['type' => 'TEXT'],
                   'row_count'    => [
                                      'type'       => 'INT',
                                      'constraint' => 11,
                                     ],
                   'total_rows'   => [
                                      'type'       => 'INT',
                                      'constraint' => 11,
                                     ],
                   'ids'          => ['type' => 'TEXT'],
                   'search_title' => [
                                      'type'       => 'VARCHAR',
                                      'constraint' => '250',
                                     ],
                  ];

        $this->dbforge->add_key('id', TRUE);
        $this->dbforge->add_field($fields);
        $this->dbforge->create_table('search', TRUE);
    }

    public function _deinstall() {
        if ($this->dx_auth->is_admin() == FALSE) {
            exit;
        }

        $this->load->dbforge();
        $this->dbforge->drop_table('search');
    }

}

/* End of file search.php */