public function get_custom($key = false, $opts = false)
 {
     if ($key === false) {
         return ' ';
     }
     if ($opts === false) {
         return $this->get($key);
     }
     $db = PerchDB::fetch();
     $Perch = Perch::fetch();
     if (isset($opts['page'])) {
         $page = $opts['page'];
     } else {
         $page = $Perch->get_page();
     }
     $where = $this->_get_page_finding_where($page);
     $region_key_for_cache = $key;
     if (is_array($key)) {
         $region_key_for_cache = implode('-', $key);
     }
     if (is_array($page)) {
         $cache_key = implode('|', $page) . ':' . $region_key_for_cache;
     } else {
         $cache_key = $page . ':' . $region_key_for_cache;
     }
     if (array_key_exists($cache_key, $this->custom_region_cache)) {
         $regions = $this->custom_region_cache[$cache_key];
     } else {
         $sql = 'SELECT regionID, regionTemplate, regionPage';
         if ($this->preview) {
             if ($this->preview_rev !== false && $this->preview_contentID != 'all') {
                 $sql .= ', IF(regionID=' . (int) $this->preview_contentID . ', ' . (int) $this->preview_rev . ', regionLatestRev) AS rev';
             } else {
                 $sql .= ', regionLatestRev AS rev';
             }
         } else {
             $sql .= ', regionRev AS rev';
         }
         $sql .= ' FROM ' . $this->table . ' WHERE ';
         if (is_array($key)) {
             $sql .= '(';
             $key_items = array();
             foreach ($key as $k) {
                 $key_items[] = 'regionKey=' . $db->pdb($k);
             }
             $sql .= implode(' OR ', $key_items);
             $sql .= ')';
         } else {
             $sql .= 'regionKey=' . $db->pdb($key);
         }
         $sql .= ' AND (' . implode(' OR ', $where) . ' OR regionPage=' . $db->pdb('*') . ')';
         $regions = $db->get_rows($sql);
         $this->custom_region_cache[$cache_key] = $regions;
     }
     if (!PerchUtil::count($regions)) {
         $str_key = $key;
         if (is_array($key)) {
             $str_key = implode(', ', $key);
         }
         PerchUtil::debug('No matching content regions found. Check region name (' . $str_key . ') and page path options.', 'error');
     }
     $region_path_cache = array();
     if (PerchUtil::count($regions)) {
         foreach ($regions as $region) {
             $region_path_cache[$region['regionID']] = $region['regionPage'];
         }
     }
     $filter_mode = false;
     $content = array();
     // find specific _id
     if (isset($opts['_id'])) {
         $item_id = (int) $opts['_id'];
         $Paging = false;
         $sql = 'SELECT  c.itemID, c.regionID, c.pageID, c.itemJSON FROM ' . PERCH_DB_PREFIX . 'content_items c WHERE c.itemID=' . $this->db->pdb((int) $item_id) . ' ';
         if (PerchUtil::count($regions)) {
             $where = array();
             foreach ($regions as $region) {
                 $where[] = '(c.regionID=' . $this->db->pdb($region['regionID']) . ' AND c.itemRev=' . $this->db->pdb((int) $region['rev']) . ')';
             }
             $sql .= ' AND (' . implode(' OR ', $where) . ')';
         } else {
             $sql .= ' AND c.regionID IS NULL ';
         }
         $sql .= ' LIMIT 1 ';
         $rows = $db->get_rows($sql);
     } else {
         $sortval = ' idx2.indexValue as sortval ';
         if (isset($opts['paginate']) && $opts['paginate']) {
             if (isset($opts['pagination-var'])) {
                 $Paging = new PerchPaging($opts['pagination-var']);
             } else {
                 $Paging = new PerchPaging();
             }
             $sql = $Paging->select_sql();
         } else {
             $sql = 'SELECT';
         }
         $sql .= ' * FROM ( SELECT  idx.itemID, c.regionID, idx.pageID, c.itemJSON, ' . $sortval . ' FROM ' . PERCH_DB_PREFIX . 'content_index idx 
                         JOIN ' . PERCH_DB_PREFIX . 'content_items c ON idx.itemID=c.itemID AND idx.itemRev=c.itemRev AND idx.regionID=c.regionID
                         JOIN ' . PERCH_DB_PREFIX . 'content_index idx2 ON idx.itemID=idx2.itemID AND idx.itemRev=idx2.itemRev  ';
         if (isset($opts['sort'])) {
             $sql .= ' AND idx2.indexKey=' . $db->pdb($opts['sort']) . ' ';
         } else {
             $sql .= ' AND idx2.indexKey=' . $db->pdb('_order') . ' ';
         }
         if (PerchUtil::count($regions)) {
             $where = array();
             foreach ($regions as $region) {
                 $where[] = '(idx.regionID=' . $this->db->pdb((int) $region['regionID']) . ' AND idx.itemRev=' . $this->db->pdb((int) $region['rev']) . ')';
             }
             $where_clause = ' WHERE (' . implode(' OR ', $where) . ')';
         } else {
             $where_clause = ' WHERE idx.regionID IS NULL ';
         }
         $sql .= $where_clause;
         // Categories
         if (isset($opts['category'])) {
             $cats = $opts['category'];
             if (!is_array($cats)) {
                 $cats = array($cats);
             }
             $match = 'any';
             if (isset($opts['category-match'])) {
                 $match = strtolower($opts['category-match']) == 'any' ? 'any' : 'all';
             }
             $pos = array();
             $neg = array();
             if (count($cats)) {
                 foreach ($cats as $cat) {
                     if (substr($cat, 0, 1) == '!') {
                         $neg[] = substr($cat, 1);
                     } else {
                         $pos[] = $cat;
                     }
                 }
                 $sql .= $this->_get_category_sql($pos, false, $match, $where_clause);
                 $sql .= $this->_get_category_sql($neg, true, $match, $where_clause);
             }
         }
         // if not picking an _id, check for a filter
         if (isset($opts['filter']) && (isset($opts['value']) || is_array($opts['filter']))) {
             // if it's not a multi-filter, make it look like one to unify what we're working with
             if (!is_array($opts['filter']) && isset($opts['value'])) {
                 $filters = array(array('filter' => $opts['filter'], 'value' => $opts['value'], 'match' => isset($opts['match']) ? $opts['match'] : 'eq', 'match-type' => isset($opts['match-type']) ? $opts['match-type'] : 'alpha'));
                 $filter_mode = 'AND';
             } else {
                 $filters = $opts['filter'];
                 $filter_mode = 'AND';
                 if (isset($opts['match']) && strtolower($opts['match']) == 'or') {
                     $filter_mode = 'OR';
                 }
             }
             $where = array();
             foreach ($filters as $filter) {
                 $key = $filter['filter'];
                 $val = $filter['value'];
                 $match = isset($filter['match']) ? $filter['match'] : 'eq';
                 if (is_numeric($val)) {
                     $val = (double) $val;
                 }
                 switch ($match) {
                     case 'eq':
                     case 'is':
                     case 'exact':
                         $where[] = '(idx.indexKey=' . $db->pdb($key) . ' AND idx.indexValue=' . $db->pdb($val) . ')';
                         break;
                     case 'neq':
                     case 'ne':
                     case 'not':
                         $where[] = '(idx.indexKey=' . $db->pdb($key) . ' AND idx.indexValue != ' . $db->pdb($val) . ')';
                         break;
                     case 'gt':
                         $where[] = '(idx.indexKey=' . $db->pdb($key) . ' AND idx.indexValue > ' . $db->pdb($val) . ')';
                         break;
                     case 'gte':
                         $where[] = '(idx.indexKey=' . $db->pdb($key) . ' AND idx.indexValue >= ' . $db->pdb($val) . ')';
                         break;
                     case 'lt':
                         $where[] = '(idx.indexKey=' . $db->pdb($key) . ' AND idx.indexValue < ' . $db->pdb($val) . ')';
                         break;
                     case 'lte':
                         $where[] = '(idx.indexKey=' . $db->pdb($key) . ' AND idx.indexValue <= ' . $db->pdb($val) . ')';
                         break;
                     case 'contains':
                         $v = str_replace('/', '\\/', $val);
                         $where[] = '(idx.indexKey=' . $db->pdb($key) . ' AND idx.indexValue REGEXP ' . $db->pdb('[[:<:]]' . $v . '[[:>:]]') . ')';
                         break;
                     case 'notcontains':
                         $v = str_replace('/', '\\/', $val);
                         $where[] = '(idx.indexKey=' . $db->pdb($key) . ' AND idx.indexValue NOT REGEXP ' . $db->pdb('[[:<:]]' . $v . '[[:>:]]') . ')';
                         break;
                     case 'regex':
                     case 'regexp':
                         $v = str_replace('/', '\\/', $val);
                         $where[] = '(idx.indexKey=' . $db->pdb($key) . ' AND idx.indexValue REGEXP ' . $db->pdb($v) . ')';
                         break;
                     case 'between':
                     case 'betwixt':
                         $vals = explode(',', $val);
                         if (PerchUtil::count($vals) == 2) {
                             $vals[0] = trim($vals[0]);
                             $vals[1] = trim($vals[1]);
                             if (is_numeric($vals[0]) && is_numeric($vals[1])) {
                                 $vals[0] = (double) $vals[0];
                                 $vals[1] = (double) $vals[1];
                             }
                             $where[] = '(idx.indexKey=' . $db->pdb($key) . ' AND (idx.indexValue > ' . $db->pdb($vals[0]) . ' AND idx.indexValue < ' . $db->pdb($vals[1]) . '))';
                         }
                         break;
                     case 'eqbetween':
                     case 'eqbetwixt':
                         $vals = explode(',', $val);
                         if (PerchUtil::count($vals) == 2) {
                             $vals[0] = trim($vals[0]);
                             $vals[1] = trim($vals[1]);
                             if (is_numeric($vals[0]) && is_numeric($vals[1])) {
                                 $vals[0] = (double) $vals[0];
                                 $vals[1] = (double) $vals[1];
                             }
                             $where[] = '(idx.indexKey=' . $db->pdb($key) . ' AND (idx.indexValue >= ' . $db->pdb($vals[0]) . ' AND idx.indexValue <= ' . $db->pdb($vals[1]) . '))';
                         }
                         break;
                     case 'in':
                     case 'within':
                         $vals = explode(',', $val);
                         if (PerchUtil::count($vals)) {
                             $where[] = '(idx.indexKey=' . $db->pdb($key) . ' AND idx.indexValue IN (' . $db->implode_for_sql_in($vals) . '))';
                         }
                         break;
                 }
             }
             $sql .= ' AND (' . implode($where, ' OR ') . ') ';
         }
         $sql .= ' AND idx.itemID=idx2.itemID AND idx.itemRev=idx2.itemRev
                 ) as tbl GROUP BY itemID, pageID, itemJSON, sortval ';
         // DM added ', pageID, itemJSON, sortval' for MySQL 5.7
         if ($filter_mode == 'AND' && PerchUtil::count($filters) > 1) {
             $sql .= ' HAVING count(*)=' . PerchUtil::count($filters) . ' ';
         }
         // sort
         if (isset($opts['sort'])) {
             $direction = 'ASC';
             if (isset($opts['sort-order'])) {
                 switch ($opts['sort-order']) {
                     case 'DESC':
                     case 'desc':
                         $direction = 'DESC';
                         break;
                     case 'RAND':
                     case 'rand':
                         $direction = 'RAND';
                         break;
                     default:
                         $direction = 'ASC';
                         break;
                 }
             }
             if ($direction == 'RAND') {
                 $sql .= ' ORDER BY RAND()';
             } else {
                 if (isset($opts['sort-type']) && $opts['sort-type'] == 'numeric') {
                     $sql .= ' ORDER BY sortval * 1 ' . $direction . ' ';
                 } else {
                     $sql .= ' ORDER BY sortval ' . $direction . ' ';
                 }
             }
         } else {
             if (isset($opts['sort-type']) && $opts['sort-type'] == 'numeric') {
                 $sql .= ' ORDER BY sortval * 1 ASC ';
             } else {
                 $sql .= ' ORDER BY sortval ASC ';
             }
         }
         // Pagination
         if (isset($opts['paginate']) && $opts['paginate']) {
             if (isset($opts['pagination-var'])) {
                 $Paging = new PerchPaging($opts['pagination-var']);
             } else {
                 $Paging = new PerchPaging();
             }
             $Paging->set_per_page(isset($opts['count']) ? (int) $opts['count'] : 10);
             $opts['count'] = $Paging->per_page();
             $opts['start'] = $Paging->lower_bound() + 1;
         } else {
             $Paging = false;
         }
         // limit
         if (isset($opts['count']) || isset($opts['start'])) {
             // count
             if (isset($opts['count'])) {
                 $count = (int) $opts['count'];
             } else {
                 $count = false;
             }
             // start
             if (isset($opts['start'])) {
                 $start = (int) $opts['start'] - 1;
             } else {
                 $start = 0;
             }
             if (is_object($Paging)) {
                 $sql .= $Paging->limit_sql();
             } else {
                 $sql .= ' LIMIT ' . $start;
                 if ($count) {
                     $sql .= ', ' . $count;
                 }
             }
         }
         $rows = $db->get_rows($sql);
         if (is_object($Paging)) {
             $total_count = $this->db->get_value($Paging->total_count_sql());
             $Paging->set_total($total_count);
         }
     }
     // transform json
     if (PerchUtil::count($rows)) {
         $content = array();
         foreach ($rows as $item) {
             if (trim($item['itemJSON']) != '') {
                 $tmp = PerchUtil::json_safe_decode($item['itemJSON'], true);
                 if (isset($region_path_cache[$item['regionID']])) {
                     $tmp['_page'] = $region_path_cache[$item['regionID']];
                     $tmp['_pageID'] = $item['pageID'];
                 }
                 if (isset($item['sortval'])) {
                     $tmp['_sortvalue'] = $item['sortval'];
                 }
                 // 'each' callback
                 if (isset($opts['each'])) {
                     if (is_callable($opts['each'])) {
                         $tmp = $opts['each']($tmp);
                     }
                 }
                 $content[] = $tmp;
             }
         }
     }
     if (isset($opts['skip-template']) && $opts['skip-template'] == true) {
         if (isset($opts['raw']) && $opts['raw'] == true) {
             if (PerchUtil::count($content)) {
                 foreach ($content as &$item) {
                     if (PerchUtil::count($item)) {
                         foreach ($item as &$field) {
                             if (is_array($field) && isset($field['raw'])) {
                                 $field = $field['raw'];
                             }
                         }
                     }
                 }
             }
             return $content;
         }
     }
     // template
     if (isset($opts['template'])) {
         $template = $opts['template'];
     } else {
         $template = $regions[0]['regionTemplate'];
     }
     $Template = new PerchTemplate('content/' . $template, 'content');
     if (!$Template->file) {
         return 'The template <code>' . PerchUtil::html($template) . '</code> could not be found.';
     }
     // post process
     $processed_vars = array();
     foreach ($content as $item) {
         $tmp = $item;
         if ($tmp) {
             $processed_vars[] = $tmp;
         }
         unset($tmp);
     }
     // Paging to template
     if (is_object($Paging) && $Paging->enabled()) {
         $paging_array = $Paging->to_array($opts);
         // merge in paging vars
         foreach ($processed_vars as &$item) {
             foreach ($paging_array as $key => $val) {
                 $item[$key] = $val;
             }
         }
     }
     if (PerchUtil::count($processed_vars)) {
         if (isset($opts['split-items']) && $opts['split-items']) {
             $html = $Template->render_group($processed_vars, false);
         } else {
             $html = $Template->render_group($processed_vars, true);
         }
     } else {
         $Template->use_noresults();
         $html = $Template->render(array());
     }
     if (isset($opts['skip-template']) && $opts['skip-template'] == true) {
         $out = array();
         if (PerchUtil::count($processed_vars)) {
             $category_field_ids = $Template->find_all_tag_ids('categories');
             foreach ($processed_vars as &$item) {
                 if (PerchUtil::count($item)) {
                     foreach ($item as $key => &$field) {
                         if (in_array($key, $category_field_ids)) {
                             $field = $this->_process_category_field($field);
                         }
                         if (is_array($field) && isset($field['processed'])) {
                             $field = $field['processed'];
                         }
                         if (is_array($field) && isset($field['_default'])) {
                             $field = $field['_default'];
                         }
                     }
                 }
             }
         }
         for ($i = 0; $i < PerchUtil::count($content); $i++) {
             $out[] = array_merge($content[$i], $processed_vars[$i]);
         }
         if (isset($opts['return-html']) && $opts['return-html'] == true) {
             $out['html'] = $html;
         }
         return $out;
     }
     return $html;
 }
 public function get_filtered_listing_from_index($opts, $where_callback, $pre_template_callback = null)
 {
     $Perch = Perch::fetch();
     $index_table = PERCH_DB_PREFIX . $this->index_table;
     $where = array();
     $filter_mode = false;
     $single_mode = false;
     $content = array();
     // find specific _id
     if (isset($opts['_id'])) {
         $item_id = (int) $opts['_id'];
         $Paging = false;
         $sql = 'SELECT main.* FROM ' . $this->table . ' main WHERE main.' . $this->pk . '=' . $this->db->pdb($item_id) . ' LIMIT 1 ';
         $rows = $this->db->get_rows($sql);
         $single_mode = true;
     } else {
         $sortval = ' idx2.indexValue as sortval ';
         if (isset($opts['paginate']) && $opts['paginate']) {
             if (isset($opts['pagination-var'])) {
                 $Paging = new PerchPaging($opts['pagination-var']);
             } else {
                 $Paging = new PerchPaging();
             }
             $sql = $Paging->select_sql();
         } else {
             $sql = 'SELECT';
         }
         $sql .= ' tbl.* FROM ( SELECT  idx.itemID, main.*, ' . $sortval . ' FROM ' . $index_table . ' idx
                         JOIN ' . $this->table . ' main ON idx.itemID=main.' . $this->pk . ' AND idx.itemKey=' . $this->db->pdb($this->pk) . '
                         JOIN ' . $index_table . ' idx2 ON idx.itemID=idx2.itemID AND idx.itemKey=' . $this->db->pdb($this->pk) . ' ';
         if (isset($opts['sort'])) {
             $sql .= ' AND idx2.indexKey=' . $this->db->pdb($opts['sort']) . ' ';
         } else {
             $sql .= ' AND idx2.indexKey=' . $this->db->pdb('_id') . ' ';
         }
         $where_clause = ' idx.itemKey=' . $this->db->pdb($this->pk) . ' ';
         // Categories
         if (isset($opts['category']) && !$this->bypass_categories) {
             $cats = $opts['category'];
             if (!is_array($cats)) {
                 $cats = array($cats);
             }
             $match = 'any';
             if (isset($opts['category-match'])) {
                 $match = strtolower($opts['category-match']) == 'any' ? 'any' : 'all';
             }
             $pos = array();
             $neg = array();
             if (count($cats)) {
                 foreach ($cats as $cat) {
                     if (substr($cat, 0, 1) == '!') {
                         $neg[] = substr($cat, 1);
                     } else {
                         $pos[] = $cat;
                     }
                 }
                 $sql .= $this->_get_filter_sub_sql('_category', $pos, false, $match, true, $where_clause);
                 $sql .= $this->_get_filter_sub_sql('_category', $neg, true, $match, true, $where_clause);
             }
         }
         // Tags
         if (isset($opts['tag']) && !$this->bypass_tags) {
             $cats = $opts['tag'];
             if (!is_array($cats)) {
                 $cats = array($cats);
             }
             $match = 'any';
             if (isset($opts['tag-match'])) {
                 $match = strtolower($opts['tag-match']) == 'any' ? 'any' : 'all';
             }
             $pos = array();
             $neg = array();
             if (count($cats)) {
                 foreach ($cats as $cat) {
                     if (substr($cat, 0, 1) == '!') {
                         $neg[] = substr($cat, 1);
                     } else {
                         $pos[] = $cat;
                     }
                 }
                 $sql .= $this->_get_filter_sub_sql('_tag', $pos, false, $match, false, $where_clause);
                 $sql .= $this->_get_filter_sub_sql('_tag', $neg, true, $match, false, $where_clause);
             }
         }
         // Runtime restrictons
         if (!$Perch->admin && count($this->runtime_restrictons)) {
             foreach ($this->runtime_restrictons as $res) {
                 $sql .= $this->_get_filter_sub_sql($res['field'], $res['values'], $res['negative_match'], $res['match'], $res['fuzzy'], $where_clause);
             }
         }
         // if not picking an _id, check for a filter
         if (isset($opts['filter']) && (isset($opts['value']) || is_array($opts['filter']))) {
             // if it's not a multi-filter, make it look like one to unify what we're working with
             if (!is_array($opts['filter']) && isset($opts['value'])) {
                 $filters = array(array('filter' => $opts['filter'], 'value' => $opts['value'], 'match' => isset($opts['match']) ? $opts['match'] : 'eq', 'match-type' => isset($opts['match-type']) ? $opts['match-type'] : 'alpha'));
                 $filter_mode = 'AND';
             } else {
                 $filters = $opts['filter'];
                 $filter_mode = 'AND';
                 if (isset($opts['match']) && strtolower($opts['match']) == 'or') {
                     $filter_mode = 'OR';
                 }
             }
             $where = array();
             foreach ($filters as $filter) {
                 $key = $filter['filter'];
                 $val = $filter['value'];
                 $match = isset($filter['match']) ? $filter['match'] : 'eq';
                 if (is_numeric($val)) {
                     $val = (double) $val;
                 }
                 switch ($match) {
                     case 'eq':
                     case 'is':
                     case 'exact':
                         $where[] = '(idx.indexKey=' . $this->db->pdb($key) . ' AND idx.indexValue=' . $this->db->pdb($val) . ')';
                         break;
                     case 'neq':
                     case 'ne':
                     case 'not':
                     case '!eq':
                         $where[] = '(idx.indexKey=' . $this->db->pdb($key) . ' AND idx.indexValue != ' . $this->db->pdb($val) . ')';
                         break;
                     case 'gt':
                         $where[] = '(idx.indexKey=' . $this->db->pdb($key) . ' AND idx.indexValue > ' . $this->db->pdb($val) . ')';
                         break;
                     case '!gt':
                         $where[] = '(idx.indexKey=' . $this->db->pdb($key) . ' AND idx.indexValue !> ' . $this->db->pdb($val) . ')';
                         break;
                     case 'gte':
                         $where[] = '(idx.indexKey=' . $this->db->pdb($key) . ' AND idx.indexValue >= ' . $this->db->pdb($val) . ')';
                         break;
                     case '!gte':
                         $where[] = '(idx.indexKey=' . $this->db->pdb($key) . ' AND idx.indexValue !>= ' . $this->db->pdb($val) . ')';
                         break;
                     case 'lt':
                         $where[] = '(idx.indexKey=' . $this->db->pdb($key) . ' AND idx.indexValue < ' . $this->db->pdb($val) . ')';
                         break;
                     case '!lt':
                         $where[] = '(idx.indexKey=' . $this->db->pdb($key) . ' AND idx.indexValue !< ' . $this->db->pdb($val) . ')';
                         break;
                     case 'lte':
                         $where[] = '(idx.indexKey=' . $this->db->pdb($key) . ' AND idx.indexValue <= ' . $this->db->pdb($val) . ')';
                         break;
                     case '!lte':
                         $where[] = '(idx.indexKey=' . $this->db->pdb($key) . ' AND idx.indexValue !<= ' . $this->db->pdb($val) . ')';
                         break;
                     case 'contains':
                         $v = str_replace('/', '\\/', $val);
                         $where[] = '(idx.indexKey=' . $this->db->pdb($key) . ' AND idx.indexValue REGEXP ' . $this->db->pdb('[[:<:]]' . $v . '[[:>:]]') . ')';
                         break;
                     case 'notcontains':
                     case '!contains':
                         $v = str_replace('/', '\\/', $val);
                         $where[] = '(idx.indexKey=' . $this->db->pdb($key) . ' AND idx.indexValue NOT REGEXP ' . $this->db->pdb('[[:<:]]' . $v . '[[:>:]]') . ')';
                         break;
                     case 'regex':
                     case 'regexp':
                         $v = str_replace('/', '\\/', $val);
                         $where[] = '(idx.indexKey=' . $this->db->pdb($key) . ' AND idx.indexValue REGEXP ' . $this->db->pdb($v) . ')';
                         break;
                     case 'between':
                     case 'betwixt':
                         $vals = explode(',', $val);
                         if (PerchUtil::count($vals) == 2) {
                             $vals[0] = trim($vals[0]);
                             $vals[1] = trim($vals[1]);
                             if (is_numeric($vals[0]) && is_numeric($vals[1])) {
                                 $vals[0] = (double) $vals[0];
                                 $vals[1] = (double) $vals[1];
                             }
                             $where[] = '(idx.indexKey=' . $this->db->pdb($key) . ' AND (idx.indexValue > ' . $this->db->pdb($vals[0]) . ' AND idx.indexValue < ' . $this->db->pdb($vals[1]) . '))';
                         }
                         break;
                     case '!between':
                     case '!betwixt':
                         $vals = explode(',', $val);
                         if (PerchUtil::count($vals) == 2) {
                             $vals[0] = trim($vals[0]);
                             $vals[1] = trim($vals[1]);
                             if (is_numeric($vals[0]) && is_numeric($vals[1])) {
                                 $vals[0] = (double) $vals[0];
                                 $vals[1] = (double) $vals[1];
                             }
                             $where[] = '(idx.indexKey=' . $this->db->pdb($key) . ' AND (idx.indexValue !> ' . $this->db->pdb($vals[0]) . ' AND idx.indexValue !< ' . $this->db->pdb($vals[1]) . '))';
                         }
                         break;
                     case 'eqbetween':
                     case 'eqbetwixt':
                         $vals = explode(',', $val);
                         if (PerchUtil::count($vals) == 2) {
                             $vals[0] = trim($vals[0]);
                             $vals[1] = trim($vals[1]);
                             if (is_numeric($vals[0]) && is_numeric($vals[1])) {
                                 $vals[0] = (double) $vals[0];
                                 $vals[1] = (double) $vals[1];
                             }
                             $where[] = '(idx.indexKey=' . $this->db->pdb($key) . ' AND (idx.indexValue >= ' . $this->db->pdb($vals[0]) . ' AND idx.indexValue <= ' . $this->db->pdb($vals[1]) . '))';
                         }
                         break;
                     case '!eqbetween':
                     case '!eqbetwixt':
                         $vals = explode(',', $val);
                         if (PerchUtil::count($vals) == 2) {
                             $vals[0] = trim($vals[0]);
                             $vals[1] = trim($vals[1]);
                             if (is_numeric($vals[0]) && is_numeric($vals[1])) {
                                 $vals[0] = (double) $vals[0];
                                 $vals[1] = (double) $vals[1];
                             }
                             $where[] = '(idx.indexKey=' . $this->db->pdb($key) . ' AND (idx.indexValue !>= ' . $this->db->pdb($vals[0]) . ' AND idx.indexValue !<= ' . $this->db->pdb($vals[1]) . '))';
                         }
                         break;
                     case 'in':
                     case 'within':
                         $vals = explode(',', $val);
                         if (PerchUtil::count($vals)) {
                             $where[] = '(idx.indexKey=' . $this->db->pdb($key) . ' AND idx.indexValue IN (' . $this->db->implode_for_sql_in($vals) . '))';
                         }
                         break;
                     case '!in':
                     case '!within':
                         $vals = explode(',', $val);
                         if (PerchUtil::count($vals)) {
                             $where[] = '(idx.indexKey=' . $this->db->pdb($key) . ' AND idx.indexValue NOT IN (' . $this->db->implode_for_sql_in($vals) . '))';
                         }
                         break;
                 }
             }
         }
         $sql .= ' WHERE 1=1 ';
         if (PerchUtil::count($where)) {
             $sql .= ' AND (' . implode($where, ' OR ') . ') ';
         }
         $sql .= ' AND idx.itemID=idx2.itemID AND idx.itemKey=idx2.itemKey
                     GROUP BY idx.itemID
                 ) as tbl ';
         $where = array();
         if (is_callable($where_callback)) {
             // load up Query object
             $Query = new PerchQuery();
             $Query->select = $sql;
             $Query->where = $where;
             // do callback
             $Query = $where_callback($Query);
             // retrieve
             $sql = $Query->select;
             $where = $Query->where;
         }
         if (PerchUtil::count($where)) {
             $sql .= ' WHERE (' . implode($where, ' AND ') . ') ';
         }
         $sql .= 'GROUP BY itemID ';
         if ($filter_mode == 'AND' && PerchUtil::count($filters) > 1) {
             $sql .= ' HAVING count(*)=' . PerchUtil::count($filters) . ' ';
         }
         // sort
         if (isset($opts['sort'])) {
             $direction = 'ASC';
             if (isset($opts['sort-order'])) {
                 switch ($opts['sort-order']) {
                     case 'DESC':
                     case 'desc':
                         $direction = 'DESC';
                         break;
                     case 'RAND':
                     case 'rand':
                         $direction = 'RAND';
                         break;
                     default:
                         $direction = 'ASC';
                         break;
                 }
             }
             if ($direction == 'RAND') {
                 $sql .= ' ORDER BY RAND()';
             } else {
                 if (isset($opts['sort-type']) && $opts['sort-type'] == 'numeric') {
                     $sql .= ' ORDER BY sortval * 1 ' . $direction . ' ';
                 } else {
                     $sql .= ' ORDER BY sortval ' . $direction . ' ';
                 }
             }
         } else {
             if (isset($opts['sort-type']) && $opts['sort-type'] == 'numeric') {
                 $sql .= ' ORDER BY sortval * 1 ASC ';
             } else {
                 $sql .= ' ORDER BY sortval ASC ';
             }
         }
         // Pagination
         if (isset($opts['paginate']) && $opts['paginate']) {
             if (is_object($opts['paginate'])) {
                 $Paging = $opts['paginate'];
             } else {
                 if (isset($opts['pagination-var'])) {
                     $Paging = new PerchPaging($opts['pagination-var']);
                 } else {
                     $Paging = new PerchPaging();
                 }
                 $Paging->set_per_page(isset($opts['count']) ? (int) $opts['count'] : 10);
             }
             $opts['count'] = $Paging->per_page();
             $opts['start'] = $Paging->lower_bound() + 1;
         } else {
             $Paging = false;
         }
         // limit
         if (isset($opts['count']) || isset($opts['start'])) {
             // count
             if (isset($opts['count'])) {
                 $count = (int) $opts['count'];
             } else {
                 $count = false;
             }
             // start
             if (isset($opts['start'])) {
                 $start = (int) $opts['start'] - 1;
             } else {
                 $start = 0;
             }
             if (is_object($Paging)) {
                 $sql .= $Paging->limit_sql();
             } else {
                 $sql .= ' LIMIT ' . $start;
                 if ($count) {
                     $sql .= ', ' . $count;
                 }
             }
         }
         $rows = $this->db->get_rows($sql);
         if (is_object($Paging)) {
             $total_count = $this->db->get_value($Paging->total_count_sql());
             $Paging->set_total($total_count);
         }
         // pre-template callback
         if (PerchUtil::count($rows) && $pre_template_callback && is_callable($pre_template_callback)) {
             $rows = $pre_template_callback($rows);
         }
         // each
         if (PerchUtil::count($rows) && isset($opts['each']) && is_callable($opts['each'])) {
             $content = array();
             foreach ($rows as $item) {
                 $tmp = $opts['each']($item);
                 $content[] = $tmp;
             }
             $rows = $content;
         }
         $items = $this->return_instances($rows);
     }
     if (isset($opts['return-objects']) && $opts['return-objects']) {
         return $items;
     }
     $render_html = true;
     if (isset($opts['skip-template']) && $opts['skip-template'] == true) {
         $render_html = false;
         if (isset($opts['return-html']) && $opts['return-html'] == true) {
             $render_html = true;
         }
     }
     // template
     if (is_callable($opts['template'])) {
         $callback = $opts['template'];
         $template = $callback($items);
     } else {
         $template = $opts['template'];
     }
     if (is_object($this->api)) {
         $Template = $this->api->get('Template');
         $Template->set($template, $this->namespace);
     } else {
         $Template = new PerchTemplate($template, $this->namespace);
     }
     if ($render_html) {
         if (isset($Paging) && is_object($Paging) && $Paging->enabled()) {
             $paging_array = $Paging->to_array($opts);
             // merge in paging vars
             if (PerchUtil::count($items)) {
                 foreach ($items as &$Item) {
                     foreach ($paging_array as $key => $val) {
                         $Item->squirrel($key, $val);
                     }
                 }
             }
         }
         if (PerchUtil::count($items)) {
             if (isset($opts['split-items']) && $opts['split-items']) {
                 $html = $Template->render_group($items, false);
             } else {
                 $html = $Template->render_group($items, true);
             }
         } else {
             $Template->use_noresults();
             $html = $Template->render(array());
         }
     }
     if (isset($opts['skip-template']) && $opts['skip-template'] == true) {
         if ($single_mode) {
             return $Item->to_array();
         }
         $processed_vars = array();
         if (PerchUtil::count($items)) {
             foreach ($items as $Item) {
                 $processed_vars[] = $Item->to_array();
             }
         }
         if (PerchUtil::count($processed_vars)) {
             $category_field_ids = $Template->find_all_tag_ids('categories');
             //PerchUtil::debug($category_field_ids, 'notice');
             foreach ($processed_vars as &$item) {
                 if (PerchUtil::count($item)) {
                     foreach ($item as $key => &$field) {
                         if (in_array($key, $category_field_ids)) {
                             $field = $this->_process_category_field($field);
                         }
                         if (is_array($field) && isset($field['processed'])) {
                             $field = $field['processed'];
                         }
                         if (is_array($field) && isset($field['_default'])) {
                             $field = $field['_default'];
                         }
                     }
                 }
             }
         }
         if (isset($opts['return-html']) && $opts['return-html'] == true) {
             $processed_vars['html'] = $html;
         }
         return $processed_vars;
     }
     if (is_array($html)) {
         // split-items
         if (PerchUtil::count($html)) {
             $Template = new PerchTemplate();
             foreach ($html as &$html_item) {
                 if (strpos($html_item, '<perch:') !== false) {
                     $html_item = $Template->apply_runtime_post_processing($html_item);
                 }
             }
         }
     } else {
         if (strpos($html, '<perch:') !== false) {
             $Template = new PerchTemplate();
             $html = $Template->apply_runtime_post_processing($html);
         }
     }
     return $html;
 }
 public function get_custom($albumID, $opts = array(), $Versions = false)
 {
     if ($albumID === false) {
         $sql = 'SELECT i.* FROM ' . $this->table . ' i, ' . PERCH_DB_PREFIX . 'gallery_albums a WHERE i.albumID=a.albumID ORDER BY a.albumOrder ASC, i.imageOrder ASC';
     } else {
         $sql = 'SELECT * FROM ' . $this->table . ' WHERE albumID = ' . $this->db->pdb($albumID) . ' ORDER BY ' . $this->default_sort_column . ' ASC';
     }
     $rows = $this->db->get_rows($sql);
     $objects = $this->return_instances($rows);
     $content = array();
     if (PerchUtil::count($objects)) {
         foreach ($objects as $Object) {
             $content[] = $Object->to_array(false, $Versions);
         }
     }
     // find specific _id
     if (isset($opts['_id'])) {
         if (PerchUtil::count($content)) {
             $out = array();
             foreach ($content as $item) {
                 if (isset($item['_id']) && $item['_id'] == $opts['_id']) {
                     $out[] = $item;
                     break;
                 }
             }
             $content = $out;
         }
     } else {
         // if not picking an _id, check for a filter
         if (isset($opts['filter']) && isset($opts['value'])) {
             if (PerchUtil::count($content)) {
                 $out = array();
                 $key = $opts['filter'];
                 $val = $opts['value'];
                 $match = isset($opts['match']) ? $opts['match'] : 'eq';
                 foreach ($content as $item) {
                     if (isset($item[$key])) {
                         switch ($match) {
                             case 'eq':
                             case 'is':
                             case 'exact':
                                 if ($item[$key] == $val) {
                                     $out[] = $item;
                                 }
                                 break;
                             case 'neq':
                             case 'ne':
                             case 'not':
                                 if ($item[$key] != $val) {
                                     $out[] = $item;
                                 }
                                 break;
                             case 'gt':
                                 if ($item[$key] > $val) {
                                     $out[] = $item;
                                 }
                                 break;
                             case 'gte':
                                 if ($item[$key] >= $val) {
                                     $out[] = $item;
                                 }
                                 break;
                             case 'lt':
                                 if ($item[$key] < $val) {
                                     $out[] = $item;
                                 }
                                 break;
                             case 'lte':
                                 if ($item[$key] <= $val) {
                                     $out[] = $item;
                                 }
                                 break;
                             case 'contains':
                                 $value = str_replace('/', '\\/', $val);
                                 if (preg_match('/\\b' . $value . '\\b/i', $item[$key])) {
                                     $out[] = $item;
                                 }
                                 break;
                             case 'regex':
                             case 'regexp':
                                 if (preg_match($val, $item[$key])) {
                                     $out[] = $item;
                                 }
                                 break;
                             case 'between':
                             case 'betwixt':
                                 $vals = explode(',', $val);
                                 if (PerchUtil::count($vals) == 2) {
                                     if ($item[$key] > trim($vals[0]) && $item[$key] < trim($vals[1])) {
                                         $out[] = $item;
                                     }
                                 }
                                 break;
                             case 'eqbetween':
                             case 'eqbetwixt':
                                 $vals = explode(',', $val);
                                 if (PerchUtil::count($vals) == 2) {
                                     if ($item[$key] >= trim($vals[0]) && $item[$key] <= trim($vals[1])) {
                                         $out[] = $item;
                                     }
                                 }
                                 break;
                             case 'in':
                             case 'within':
                                 $vals = explode(',', $val);
                                 if (PerchUtil::count($vals)) {
                                     foreach ($vals as $value) {
                                         if ($item[$key] == trim($value)) {
                                             $out[] = $item;
                                             break;
                                         }
                                     }
                                 }
                                 break;
                         }
                     }
                 }
                 $content = $out;
             }
         }
     }
     // sort
     if (isset($opts['sort'])) {
         if (isset($opts['sort-order']) && $opts['sort-order'] == 'DESC') {
             $desc = true;
         } else {
             $desc = false;
         }
         $content = PerchUtil::array_sort($content, $opts['sort'], $desc);
     }
     if (isset($opts['sort-order']) && $opts['sort-order'] == 'RAND') {
         shuffle($content);
     }
     // Pagination
     if (isset($opts['paginate'])) {
         if (isset($opts['pagination_var'])) {
             $Paging = new PerchPaging($opts['pagination_var']);
         } else {
             $Paging = new PerchPaging();
         }
         $Paging->set_per_page(isset($opts['count']) ? (int) $opts['count'] : 10);
         $opts['count'] = $Paging->per_page();
         $opts['start'] = $Paging->lower_bound() + 1;
         $Paging->set_total(PerchUtil::count($content));
     } else {
         $Paging = false;
     }
     // limit
     if (isset($opts['count']) || isset($opts['start'])) {
         // count
         if (isset($opts['count'])) {
             $count = (int) $opts['count'];
         } else {
             $count = PerchUtil::count($content);
         }
         // start
         if (isset($opts['start'])) {
             if ($opts['start'] === 'RAND') {
                 $start = rand(0, PerchUtil::count($content) - 1);
             } else {
                 $start = (int) $opts['start'] - 1;
             }
         } else {
             $start = 0;
         }
         // loop through
         $out = array();
         for ($i = $start; $i < $start + $count; $i++) {
             if (isset($content[$i])) {
                 $out[] = $content[$i];
             } else {
                 break;
             }
         }
         $content = $out;
     }
     // Paging to template
     if (is_object($Paging) && $Paging->enabled()) {
         $paging_array = $Paging->to_array($opts);
         // merge in paging vars
         foreach ($content as &$item) {
             foreach ($paging_array as $key => $val) {
                 $item[$key] = $val;
             }
         }
     }
     return $content;
 }