/** * Standard aed_module table function. * * @param array Details to go to build_url for link to the next screen. * @return array A pair: The choose table, Whether re-ordering is supported from this screen. */ function nice_get_choose_table($url_map) { require_code('templates_results_table'); $current_ordering = get_param('sort', 'title ASC', true); list($sortable, $sort_order) = array(substr($current_ordering, 0, strrpos($current_ordering, ' ')), substr($current_ordering, strrpos($current_ordering, ' ') + 1)); $sortables = array('title' => do_lang_tempcode('TITLE')); if (db_has_subqueries($GLOBALS['SITE_DB']->connection_read)) { $sortables['(SELECT COUNT(*) FROM ' . get_table_prefix() . 'newsletter n JOIN ' . get_table_prefix() . 'newsletter_subscribe s ON n.id=s.newsletter_id WHERE code_confirm=0)'] = do_lang_tempcode('COUNT_MEMBERS'); } if (strtoupper($sort_order) != 'ASC' && strtoupper($sort_order) != 'DESC' || !array_key_exists($sortable, $sortables)) { log_hack_attack_and_exit('ORDERBY_HACK'); } global $NON_CANONICAL_PARAMS; $NON_CANONICAL_PARAMS[] = 'sort'; $header_row = results_field_title(array(do_lang_tempcode('TITLE'), do_lang_tempcode('COUNT_MEMBERS'), do_lang_tempcode('ACTIONS')), $sortables, 'sort', $sortable . ' ' . $sort_order); $fields = new ocp_tempcode(); require_code('form_templates'); list($rows, $max_rows) = $this->get_entry_rows(false, $current_ordering); foreach ($rows as $row) { $edit_link = build_url($url_map + array('id' => $row['id']), '_SELF'); $num_readers = $GLOBALS['SITE_DB']->query_value('newsletter n JOIN ' . get_table_prefix() . 'newsletter_subscribe s ON n.id=s.newsletter_id', 'COUNT(*)', array('code_confirm' => 0)); $fields->attach(results_entry(array(get_translated_text($row['title']), integer_format($num_readers), protect_from_escaping(hyperlink($edit_link, do_lang_tempcode('EDIT'), false, true, '#' . strval($row['id'])))), true)); } return array(results_table(do_lang($this->menu_label), get_param_integer('start', 0), 'start', either_param_integer('max', 20), 'max', $max_rows, $header_row, $fields, $sortables, $sortable, $sort_order), false); }
/** * The UI to translate content. * * @return tempcode The UI */ function interface_content() { $title = get_page_title('TRANSLATE_CONTENT'); if (!multi_lang()) { warn_exit(do_lang_tempcode('MULTILANG_OFF')); } $max = get_param_integer('max', 100); $lang = choose_language($title); if (is_object($lang)) { return $lang; } // Fiddle around in order to find what we haven't translated. Subqueries and self joins don't work well enough across different db's if (!db_has_subqueries($GLOBALS['SITE_DB']->connection_read)) { $_done_id_list = collapse_2d_complexity('id', 'text_original', $GLOBALS['SITE_DB']->query_select('translate', array('id', 'text_original'), array('language' => $lang, 'broken' => 0))); $done_id_list = ''; foreach (array_keys($_done_id_list) as $done_id) { if ($done_id_list != '') { $done_id_list .= ','; } $done_id_list .= strval($done_id); } $and_clause = $done_id_list == '' ? '' : 'AND id NOT IN (' . $done_id_list . ')'; $query = 'FROM ' . get_table_prefix() . 'translate WHERE ' . db_string_not_equal_to('language', $lang) . ' ' . $and_clause . ' AND ' . db_string_not_equal_to('text_original', '') . ' ORDER BY importance_level'; $to_translate = $GLOBALS['SITE_DB']->query('SELECT * ' . $query, $max); } else { $query = 'FROM ' . get_table_prefix() . 'translate a LEFT JOIN ' . get_table_prefix() . 'translate b ON a.id=b.id AND b.broken=0 AND ' . db_string_equal_to('b.language', $lang) . ' WHERE b.id IS NULL AND ' . db_string_not_equal_to('a.language', $lang) . ' AND ' . db_string_not_equal_to('a.text_original', ''); $to_translate = $GLOBALS['SITE_DB']->query('SELECT a.* ' . $query . (can_arbitrary_groupby() ? ' GROUP BY a.id' : '') . ' ORDER BY a.importance_level', $max); } $total = $GLOBALS['SITE_DB']->query_value_null_ok_full('SELECT COUNT(*) ' . $query); if (count($to_translate) == 0) { inform_exit(do_lang_tempcode('NOTHING_TO_TRANSLATE')); } require_all_lang($lang, true); require_all_open_lang_files($lang); // Make our translation page require_code('lang2'); $lines = ''; $intertrans = $this->get_intertran_conv($lang); $actions = make_string_tempcode(' '); $last_level = NULL; $too_many = count($to_translate) == $max; $ids_to_lookup = array(); foreach ($to_translate as $it) { $ids_to_lookup[] = $it['id']; } $names = find_lang_content_names($ids_to_lookup); foreach ($to_translate as $i => $it) { if ($it['importance_level'] == 0) { continue; } // Corrupt data $id = $it['id']; $old = $it['text_original']; $current = $this->find_lang_matches($old, $lang); $priority = $last_level === $it['importance_level'] ? NULL : do_lang('PRIORITY_' . strval($it['importance_level'])); $name = $names[$id]; if (is_null($name)) { continue; } // Orphaned string if ($intertrans != '') { $actions = do_template('TRANSLATE_ACTION', array('_GUID' => 'f625cf15c9db5e5af30fc772a7f0d5ff', 'LANG_FROM' => $it['language'], 'LANG_TO' => $lang, 'NAME' => 'trans_' . strval($id), 'OLD' => $old)); } $line = do_template('TRANSLATE_LINE_CONTENT', array('_GUID' => '87a0f5298ce9532839f3206cd0e06051', 'NAME' => $name, 'ID' => strval($id), 'OLD' => $old, 'CURRENT' => $current, 'ACTIONS' => $actions, 'PRIORITY' => $priority)); $lines .= $line->evaluate(); /*XHTMLXHTML*/ $last_level = $it['importance_level']; } $url = build_url(array('page' => '_SELF', 'type' => '_content', 'lang' => $lang), '_SELF'); require_code('lang2'); return do_template('TRANSLATE_SCREEN_CONTENT_SCREEN', array('_GUID' => 'af732c5e595816db1c6f025c4b8fa6a2', 'MAX' => integer_format($max), 'TOTAL' => integer_format($total - $max), 'LANG_ORIGINAL_NAME' => get_site_default_lang(), 'LANG_NICE_ORIGINAL_NAME' => lookup_language_full_name(get_site_default_lang()), 'LANG_NICE_NAME' => lookup_language_full_name($lang), 'TOO_MANY' => $too_many, 'INTERTRANS' => $intertrans, 'LANG' => $lang, 'LINES' => $lines, 'TITLE' => $title, 'URL' => $url)); }
/** * Get a list of members who have enabled this notification (i.e. have chosen to or are defaulted to). * (No pagination supported, as assumed there are only a small set of members here.) * * @param ID_TEXT Notification code * @param ?SHORT_TEXT The category within the notification code (NULL: none) * @param ?array List of member IDs we are restricting to (NULL: no restriction). This effectively works as a intersection set operator against those who have enabled. * @param integer Start position (for pagination) * @param integer Maximum (for pagination) * @return array A pair: Map of members to their notification setting, and whether there may be more */ function _all_members_who_have_enabled($only_if_enabled_on__notification_code, $only_if_enabled_on__category, $to_member_ids, $start, $max) { global $NO_DB_SCOPE_CHECK; $bak = $NO_DB_SCOPE_CHECK; $NO_DB_SCOPE_CHECK = true; $initial_setting = $this->get_initial_setting($only_if_enabled_on__notification_code, $only_if_enabled_on__category); $has_by_default = $initial_setting != A_NA; $clause_1 = db_string_equal_to('l_notification_code', substr($only_if_enabled_on__notification_code, 0, 80)); $clause_2 = is_null($only_if_enabled_on__category) ? db_string_equal_to('l_code_category', '') : '(' . db_string_equal_to('l_code_category', '') . ' OR ' . db_string_equal_to('l_code_category', $only_if_enabled_on__category) . ')'; $clause_3 = '1=1'; if (!is_null($to_member_ids)) { if (count($to_member_ids) == 0) { return array(array(), false); } $clause_3 = '('; foreach ($to_member_ids as $member_id) { if ($clause_3 != '(') { $clause_3 .= ' OR '; } $clause_3 .= 'l_member_id=' . strval($member_id); } $clause_3 .= ')'; } $db = substr($only_if_enabled_on__notification_code, 0, 4) == 'ocf_' ? $GLOBALS['FORUM_DB'] : $GLOBALS['SITE_DB']; $test = $GLOBALS['SITE_DB']->query_value_null_ok('notification_lockdown', 'l_setting', array('l_notification_code' => substr($only_if_enabled_on__notification_code, 0, 80))); if (!is_null($test) && get_forum_type() == 'ocf') { $query_stub = 'SELECT m.id AS l_member_id,' . strval($test) . ' AS l_setting FROM ' . $db->get_table_prefix() . 'f_members m WHERE ' . str_replace('l_member_id', 'id', $clause_3); $query_stem = ''; } else { if ($has_by_default && get_forum_type() == 'ocf' && db_has_subqueries($db->connection_read)) { $query_stub = 'SELECT m.id AS l_member_id,l_setting FROM ' . $db->get_table_prefix() . 'f_members m LEFT JOIN ' . $db->get_table_prefix() . 'notifications_enabled l ON ' . $clause_1 . ' AND ' . $clause_2 . ' AND ' . $clause_3 . ' AND m.id=l.l_member_id WHERE ' . str_replace('l_member_id', 'm.id', $clause_3) . ' AND '; $query_stem = 'NOT EXISTS(SELECT * FROM ' . $db->get_table_prefix() . 'notifications_enabled l WHERE m.id=l.l_member_id AND ' . $clause_1 . ' AND ' . $clause_2 . ' AND ' . $clause_3 . ' AND l_setting=' . strval(A_NA) . ')'; } else { $query_stub = 'SELECT l_member_id,l_setting FROM ' . $db->get_table_prefix() . 'notifications_enabled WHERE '; $query_stem = $clause_1 . ' AND ' . $clause_2 . ' AND ' . $clause_3 . ' AND l_setting<>' . strval(A_NA); } } $results = $db->query($query_stub . $query_stem, $max, $start); foreach ($results as $i => $r) { if (is_null($results[$i]['l_setting'])) { $results[$i]['l_setting'] = $initial_setting; } } $NO_DB_SCOPE_CHECK = $bak; $possibly_has_more = count($results) == $max; return array(collapse_2d_complexity('l_member_id', 'l_setting', $results), $possibly_has_more); }
/** * Get a nice formatted XHTML list of all the children beneath the specified CEDI page. This function is recursive. * * @param ?AUTO_LINK The CEDI page to select by default (NULL: none) * @param ?AUTO_LINK The CEDI page to look beneath (NULL: the root) * @param string Tree built up so far, in recursion (blank: starting recursion) * @param boolean Whether to include orphaned pages in the tree * @param boolean Whether to create a compound list (gets pairs: tempcode, and comma-separated list of children) * @param boolean Whether to use titles in IDs after a ! (used on tree edit page) * @return mixed Tempcode for the list / pair of tempcode and compound */ function cedi_show_tree($select = NULL, $id = NULL, $tree = '', $include_orphans = true, $use_compound_list = false, $ins_format = false) { if (is_null($id)) { $id = db_get_first_id(); } if ($GLOBALS['SITE_DB']->query_value('seedy_pages', 'COUNT(*)') > 1000) { return new ocp_tempcode(); } $cedi_seen = array(db_get_first_id()); $title = get_translated_text($GLOBALS['SITE_DB']->query_value('seedy_pages', 'title', array('id' => $id))); $out = _cedi_show_tree($cedi_seen, $select, $id, $tree, $title, $use_compound_list, $ins_format); if ($include_orphans) { if (!db_has_subqueries($GLOBALS['SITE_DB']->connection_read)) { $cedi_seen = array(); get_cedi_page_tree($cedi_seen, is_null($id) ? NULL : intval($id)); // To build up $cedi_seen $where = ''; foreach ($cedi_seen as $seen) { if ($where != '') { $where .= ' AND '; } $where .= 'p.id<>' . strval((int) $seen); } $orphans = $GLOBALS['SITE_DB']->query('SELECT p.id,text_original,p.title FROM ' . get_table_prefix() . 'seedy_pages p LEFT JOIN ' . get_table_prefix() . 'translate t ON ' . db_string_equal_to('language', user_lang()) . ' AND t.id=p.title WHERE ' . $where . ' ORDER BY add_date DESC', 50); } else { $orphans = $GLOBALS['SITE_DB']->query('SELECT p.id,text_original,p.title FROM ' . get_table_prefix() . 'seedy_pages p LEFT JOIN ' . get_table_prefix() . 'translate t ON ' . db_string_equal_to('language', user_lang()) . ' AND t.id=p.title WHERE p.id<>' . strval(db_get_first_id()) . ' AND NOT EXISTS(SELECT * FROM ' . get_table_prefix() . 'seedy_children WHERE child_id=p.id) ORDER BY add_date DESC', 50); if (count($orphans) < 50) { global $M_SORT_KEY; $M_SORT_KEY = 'text_original'; usort($orphans, 'multi_sort'); } } foreach ($orphans as $orphan) { if (!has_category_access(get_member(), 'seedy_page', strval($orphan['id']))) { continue; } if ($GLOBALS['RECORD_LANG_STRINGS_CONTENT'] || is_null($orphan['text_original'])) { $orphan['text_original'] = get_translated_text($orphan['title']); } $title = $orphan['text_original']; //$out->attach(form_input_list_entry(strval($orphan['id']),($select==$orphan['id']),do_template('CEDI_LIST_TREE_LINE',array('_GUID'=>'e3eb3decfac32382cdcb5b745ef0ad7e','DEPTH'=>'?','TITLE'=>$title,'ID'=>$orphan['id'])))); // $out.='<option value="'.$orphan['id'].'"> ? '.$title.'</option>'; $out->attach(form_input_list_entry($ins_format ? strval($orphan['id']) . '!' . $title : strval($orphan['id']), false, do_lang('CEDI_ORPHANED') . ' > ' . $title)); } } return $out; }
/** * Standard aed_module table function. * * @param array Details to go to build_url for link to the next screen. * @return array A pair: The choose table, Whether re-ordering is supported from this screen. */ function nice_get_choose_table($url_map) { require_code('templates_results_table'); $current_ordering = get_param('sort', 'id ASC', true); list($sortable, $sort_order) = array(substr($current_ordering, 0, strrpos($current_ordering, ' ')), substr($current_ordering, strrpos($current_ordering, ' ') + 1)); $sortables = array('id' => do_lang_tempcode('CODENAME'), 't_is_textual' => do_lang_tempcode('BANNER_IS_TEXTUAL'), 't_image_width' => do_lang_tempcode('WIDTH'), 't_image_height' => do_lang_tempcode('HEIGHT'), 't_max_file_size' => do_lang_tempcode('_FILE_SIZE'), 't_comcode_inline' => do_lang_tempcode('COMCODE_INLINE')); if (db_has_subqueries($GLOBALS['SITE_DB']->connection_read)) { $sortables['(SELECT COUNT(*) FROM ' . get_table_prefix() . 'banners WHERE b_type=r.id)'] = do_lang_tempcode('COUNT_TOTAL'); } if (strtoupper($sort_order) != 'ASC' && strtoupper($sort_order) != 'DESC' || !array_key_exists($sortable, $sortables)) { log_hack_attack_and_exit('ORDERBY_HACK'); } global $NON_CANONICAL_PARAMS; $NON_CANONICAL_PARAMS[] = 'sort'; $header_row = results_field_title(array(do_lang_tempcode('CODENAME'), do_lang_tempcode('BANNER_IS_TEXTUAL'), do_lang_tempcode('WIDTH'), do_lang_tempcode('HEIGHT'), do_lang_tempcode('_FILE_SIZE'), do_lang_tempcode('COMCODE_INLINE'), do_lang_tempcode('COUNT_TOTAL'), do_lang_tempcode('ACTIONS')), $sortables, 'sort', $sortable . ' ' . $sort_order); $fields = new ocp_tempcode(); require_code('form_templates'); list($rows, $max_rows) = $this->get_entry_rows(false, $current_ordering); foreach ($rows as $row) { $edit_link = build_url($url_map + array('id' => $row['id']), '_SELF'); $total = integer_format($GLOBALS['SITE_DB']->query_value('banners', 'COUNT(*)', array('b_type' => $row['id']))); $fields->attach(results_entry(array($row['id'] == '' ? do_lang('GENERAL') : $row['id'], $row['t_is_textual'] == 1 ? do_lang_tempcode('YES') : do_lang_tempcode('NO'), integer_format($row['t_image_width']), integer_format($row['t_image_height']), clean_file_size($row['t_max_file_size'] * 1024), $row['t_comcode_inline'] == 1 ? do_lang_tempcode('YES') : do_lang_tempcode('NO'), $total, protect_from_escaping(hyperlink($edit_link, do_lang_tempcode('EDIT'), false, true, '#' . $row['id']))), true)); } return array(results_table(do_lang($this->menu_label), get_param_integer('start', 0), 'start', get_param_integer('max', 20), 'max', $max_rows, $header_row, $fields, $sortables, $sortable, $sort_order), false); }
/** * Get an array of maps for the topic in the given forum. * * @param object Link to the real forum driver * @param integer The topic ID * @param integer The comment count will be returned here by reference * @param ?integer Maximum comments to returned (NULL: no limit) * @param integer Comment to start at * @param boolean Whether to mark the topic read * @param boolean Whether to show in reverse * @param boolean Whether to only load minimal details if it is a threaded topic * @param ?array List of post IDs to load (NULL: no filter) * @param boolean Whether to load spacer posts * @return mixed The array of maps (Each map is: title, message, member, date) (-1 for no such forum, -2 for no such topic) */ function _helper_get_forum_topic_posts($this_ref, $topic_id, &$count, $max, $start, $mark_read = true, $reverse = false, $light_if_threaded = false, $post_ids = NULL, $load_spacer_posts_too = false) { if (is_null($topic_id)) { $count = 0; return -2; } require_code('ocf_topics'); $is_threaded = $this_ref->topic_is_threaded($topic_id); $extra_where = ''; if (!is_null($post_ids)) { if (count($post_ids) == 0) { $count = 0; return array(); } $extra_where = ' AND ('; foreach ($post_ids as $i => $id) { if ($i != 0) { $extra_where .= ' OR '; } $extra_where .= 'p.id=' . strval($id); } $extra_where .= ')'; } $where = '(' . ocf_get_topic_where($topic_id) . ')'; if (!$load_spacer_posts_too) { $where .= not_like_spacer_posts('t.text_original'); } $where .= $extra_where; if (!has_specific_permission(get_member(), 'see_unvalidated')) { $where .= ' AND (p_validated=1 OR ((p_poster<>' . strval($GLOBALS['FORUM_DRIVER']->get_guest_id()) . ' OR ' . db_string_equal_to('p_ip_address', get_ip_address()) . ') AND p_poster=' . strval((int) get_member()) . '))'; } $index = strpos(get_db_type(), 'mysql') !== false && !is_null($GLOBALS['SITE_DB']->query_value_null_ok('db_meta_indices', 'i_name', array('i_table' => 'f_posts', 'i_name' => 'in_topic'))) ? 'USE INDEX (in_topic)' : ''; $order = $reverse ? 'p_time DESC,p.id DESC' : 'p_time ASC,p.id ASC'; if ($is_threaded && db_has_subqueries($this_ref->connection->connection_read)) { $order = ($reverse ? 'compound_rating ASC' : 'compound_rating DESC') . ',' . $order; } if ($light_if_threaded && $is_threaded) { $select = 'p.id,p.p_parent_id,p.p_intended_solely_for,p.p_poster'; } else { $select = 'p.*,text_parsed,text_original'; if (!db_has_subqueries($GLOBALS['FORUM_DB']->connection_read)) { $select .= ',h.h_post_id'; } } if ($is_threaded && db_has_subqueries($this_ref->connection->connection_read)) { $select .= ',COALESCE((SELECT AVG(rating) FROM ' . $this_ref->connection->get_table_prefix() . 'rating WHERE ' . db_string_equal_to('rating_for_type', 'post') . ' AND rating_for_id=p.id),5) AS compound_rating'; } if (!db_has_subqueries($GLOBALS['FORUM_DB']->connection_read)) { $rows = $this_ref->connection->query('SELECT ' . $select . ' FROM ' . $this_ref->connection->get_table_prefix() . 'f_posts p ' . $index . ' LEFT JOIN ' . $this_ref->connection->get_table_prefix() . 'translate t ON t.id=p.p_post LEFT JOIN ' . $GLOBALS['FORUM_DB']->get_table_prefix() . 'f_post_history h ON (h.h_post_id=p.id AND h.h_action_date_and_time=p.p_last_edit_time) WHERE ' . $where . ' ORDER BY ' . $order, $max, $start); } else { $rows = $this_ref->connection->query('SELECT ' . $select . ', (SELECT h_post_id FROM ' . $GLOBALS['FORUM_DB']->get_table_prefix() . 'f_post_history h WHERE (h.h_post_id=p.id) LIMIT 1) AS h_post_id FROM ' . $this_ref->connection->get_table_prefix() . 'f_posts p ' . $index . ' LEFT JOIN ' . $this_ref->connection->get_table_prefix() . 'translate t ON t.id=p.p_post WHERE ' . $where . ' ORDER BY ' . $order, $max, $start); } $count = $this_ref->connection->query_value_null_ok_full('SELECT COUNT(*) FROM ' . $this_ref->connection->get_table_prefix() . 'f_posts p ' . $index . ' LEFT JOIN ' . $this_ref->connection->get_table_prefix() . 'translate t ON t.id=p.p_post WHERE ' . $where); $out = array(); foreach ($rows as $myrow) { if (is_null($myrow['p_intended_solely_for']) || $myrow['p_intended_solely_for'] == get_member() || $myrow['p_intended_solely_for'] == $this_ref->get_guest_id() && $this_ref->is_staff(get_member())) { $temp = $myrow; // Takes all OCF properties // Then sanitised for normal forum driver API too (involves repetition) $temp['parent_id'] = $myrow['p_parent_id']; if (!$light_if_threaded || !$is_threaded) { $temp['title'] = $myrow['p_title']; $message = new ocp_tempcode(); if (get_page_name() == 'search' || is_null($myrow['text_parsed']) || $myrow['text_parsed'] == '' || $myrow['p_post'] == 0) { $message = get_translated_tempcode($myrow['p_post'], $GLOBALS['FORUM_DB']); } else { if (!$message->from_assembly($myrow['text_parsed'], true)) { $message = get_translated_tempcode($myrow['p_post'], $GLOBALS['FORUM_DB']); } } $temp['message'] = $message; $temp['message_comcode'] = get_translated_text($myrow['p_post'], $GLOBALS['FORUM_DB']); $temp['user'] = $myrow['p_poster']; if ($myrow['p_poster_name_if_guest'] != '') { $temp['username'] = $myrow['p_poster_name_if_guest']; } $temp['date'] = $myrow['p_time']; } $out[] = $temp; } } if ($mark_read) { require_code('ocf_topics'); ocf_ping_topic_read($topic_id); } return $out; }
/** * Turn an ocFilter (a filter specifying which records to match) into an SQL query fragment. * * @param string The filter * @param string The database's ID field for the record-set we're matching * @param ?string The database's table that contains parent/child relationships in the record-set's category-set (the category-set is equal to the record-set if we're matching categories, but not if we're matching entries) (NULL: don't support subtree [*-style] searches) * @param ?string The database's field name for the category-set's parent-category-ID (NULL: don't support subtree [*-style] searches beyond the tree base) * @param ?string The database's field name for the record-set's container-category specifier (NULL: don't support subtree [*-style] searches) * @param ?string The database's field name for the category-set's category-ID (NULL: don't support subtree [*-style] searches beyond the tree base) * @param boolean Whether the record-set IDs are numeric * @param boolean Whether the category-set IDs are numeric * @param ?object Database connection to use (NULL: website) * @return string SQL query fragment. Note that brackets will be put around this automatically if required, so there's no need to do this yourself. */ function ocfilter_to_sqlfragment($filter, $field_name, $parent_spec__table_name = NULL, $parent_spec__parent_name = NULL, $parent_field_name = NULL, $parent_spec__field_name = NULL, $numeric_record_set_ids = true, $numeric_category_set_ids = true, $db = NULL) { if (is_null($db)) { $db = $GLOBALS['SITE_DB']; } if ($filter == '') { return '1=2'; } if ($filter == '*') { return '1=1'; } if ($parent_spec__table_name !== 'catalogue_categories') { if ($filter == strval(db_get_first_id()) . '*') { return '1=1'; } } if (is_null($parent_spec__table_name)) { if (!is_null($parent_spec__parent_name) || !is_null($parent_field_name) || !is_null($parent_spec__field_name)) { fatal_exit(do_lang_tempcode('INTERNAL_ERROR')); } } $out_or = ''; $out_and = ''; $cached_mappings = mixed(); $tokens = explode(',', $filter); $matches = array(); foreach ($tokens as $token) { $token = trim($token); if ($token == '*') { if ($out_or != '') { $out_or .= ' OR '; } $out_or .= '1=1'; } elseif (preg_match('#^\\!(.+)$#', $token, $matches) != 0) { if ($out_and != '') { $out_and .= ' AND '; } $out_and .= _ocfilter_neq($field_name, $matches[1], $numeric_record_set_ids); } elseif ($numeric_record_set_ids && preg_match('#^(\\d+)\\-(\\d+)$#', $token, $matches) != 0) { for ($i = intval($matches[1]); $i <= intval($matches[2]); $i++) { if ($out_or != '') { $out_or .= ' OR '; } $out_or .= _ocfilter_eq($field_name, strval($i), $numeric_record_set_ids); } } elseif ($numeric_record_set_ids && preg_match('#^(\\d+)\\+$#', $token, $matches) != 0) { if ($out_or != '') { $out_or .= ' OR '; } $out_or .= $field_name . '>=' . strval(intval($matches[1])); } elseif (preg_match('#^(.+)\\*$#', $token, $matches) != 0 && $parent_spec__parent_name !== NULL) { if ($parent_spec__table_name == 'catalogue_categories' && db_has_subqueries($db->connection_read)) { /*static $counter=0; $out_join.=' JOIN '.$db->get_table_prefix().'catalogue_cat_treecache t'.strval($counter).' ON t'.strval($counter).'.cc_id='.$parent_field_name.' AND t'.strval($counter).'.cc_ancestor_id='.strval(intval($matches[1])); $counter++;*/ // MySQL should be smart enough to not enumerate the 'IN' clause here, which would be bad - instead it can jump into the embedded WHERE clause on each test iteration $this_details = $db->query_select('catalogue_categories cc JOIN ' . $db->get_table_prefix() . 'catalogues c ON c.c_name=cc.c_name', array('cc_parent_id', 'cc.c_name', 'c_is_tree'), array('id' => intval($matches[1])), '', 1); if ($this_details[0]['c_is_tree'] == 0) { $out_or .= _ocfilter_eq($parent_field_name, $matches[1], $numeric_category_set_ids); } elseif (is_null($this_details[0]['cc_parent_id'])) { $out_or .= db_string_equal_to('c_name', $this_details[0]['c_name']); } else { $out_or .= $parent_field_name . ' IN (SELECT cc_id FROM ' . $db->get_table_prefix() . 'catalogue_cat_treecache WHERE cc_ancestor_id=' . strval(intval($matches[1])) . ')'; } } else { $subtree = _ocfilter_subtree_fetch($matches[1], $parent_spec__table_name, $parent_spec__parent_name, $parent_spec__field_name, $numeric_category_set_ids, $db, $cached_mappings); foreach ($subtree as $ii) { if ($out_or != '') { $out_or .= ' OR '; } $out_or .= _ocfilter_eq($parent_field_name, is_integer($ii) ? strval($ii) : $ii, $numeric_category_set_ids); } } } elseif (preg_match('#^(.+)\\~$#', $token, $matches) != 0 && $parent_spec__parent_name !== NULL) { $subtree = _ocfilter_subtree_fetch($matches[1], $parent_spec__table_name, $parent_spec__parent_name, $parent_spec__field_name, $numeric_category_set_ids, $db, $cached_mappings); foreach ($subtree as $ii) { if ($out_and != '') { $out_and .= ' AND '; } $out_and .= _ocfilter_neq($parent_field_name, is_integer($ii) ? strval($ii) : $ii, $numeric_category_set_ids); } } else { if ($out_or != '') { $out_or .= ' OR '; } $out_or .= _ocfilter_eq($field_name, $token, $numeric_record_set_ids); } } if ($out_or == '') { return $out_and == '' ? '0=1' : $out_and; } if ($out_and == '') { return $out_or == '' ? '0=1' : '(' . $out_or . ')'; } return '(' . $out_or . ') AND (' . $out_and . ')'; }
/** * Standard modular run function for ajax-tree hooks. Generates XML for a tree list, which is interpreted by Javascript and expanded on-demand (via new calls). * * @param ?ID_TEXT The ID to do under (NULL: root) * @param array Options being passed through * @param ?ID_TEXT The ID to select by default (NULL: none) * @return string XML in the special category,entry format */ function run($id, $options, $default = NULL) { unset($options); require_code('cedi'); require_lang('cedi'); $cedi_seen = array(); $tree = get_cedi_page_tree($cedi_seen, is_null($id) ? NULL : intval($id), NULL, NULL, true, false, is_null($id) ? 0 : 1); $stripped_id = $id; $out = ''; if (!has_actual_page_access(NULL, 'cedi')) { $tree = array(); } foreach ($tree as $t) { $_id = strval($t['id']); if ($stripped_id === $_id) { continue; } // Possible when we look under as a root $title = $t['title']; $has_children = $t['child_count'] != 0; $selectable = true; $tag = 'category'; // category $out .= '<' . $tag . ' id="' . $_id . '" title="' . xmlentities($title) . '" has_children="' . ($has_children ? 'true' : 'false') . '" selectable="' . ($selectable ? 'true' : 'false') . '"></' . $tag . '>'; } if (is_null($id)) { if (!db_has_subqueries($GLOBALS['SITE_DB']->connection_read)) { $where = ''; $cedi_seen = array(); get_cedi_page_tree($cedi_seen, is_null($id) ? NULL : intval($id)); // To build up $cedi_seen foreach ($cedi_seen as $seen) { if ($where != '') { $where .= ' AND '; } $where .= 'p.id<>' . strval((int) $seen); } $orphans = $GLOBALS['SITE_DB']->query('SELECT p.id,text_original,p.title FROM ' . get_table_prefix() . 'seedy_pages p LEFT JOIN ' . get_table_prefix() . 'translate t ON ' . db_string_equal_to('language', user_lang()) . ' AND t.id=p.title WHERE ' . $where . ' ORDER BY add_date DESC', 50); } else { $orphans = $GLOBALS['SITE_DB']->query('SELECT p.id,text_original,p.title FROM ' . get_table_prefix() . 'seedy_pages p LEFT JOIN ' . get_table_prefix() . 'translate t ON ' . db_string_equal_to('language', user_lang()) . ' AND t.id=p.title WHERE NOT EXISTS(SELECT * FROM ' . get_table_prefix() . 'seedy_children WHERE child_id=p.id) ORDER BY add_date DESC', 50); if (count($orphans) < 50) { global $M_SORT_KEY; $M_SORT_KEY = 'text_original'; usort($orphans, 'multi_sort'); } } foreach ($orphans as $orphan) { if (!has_category_access(get_member(), 'seedy_page', strval($orphan['id']))) { continue; } if ($orphan['id'] == db_get_first_id()) { continue; } if ($GLOBALS['RECORD_LANG_STRINGS_CONTENT'] || is_null($orphan['text_original'])) { $orphan['text_original'] = get_translated_text($orphan['title']); } $_id = strval($orphan['id']); $title = $orphan['text_original']; $has_children = $GLOBALS['SITE_DB']->query_value('seedy_children', 'COUNT(*)', array('parent_id' => $orphan['id'])) != 0; $selectable = true; $tag = 'category'; // category $out .= '<' . $tag . ' id="' . $_id . '" title="' . xmlentities($title) . '" has_children="' . ($has_children ? 'true' : 'false') . '" selectable="' . ($selectable ? 'true' : 'false') . '"></' . $tag . '>'; } } $tag = 'result'; // result return '<' . $tag . '>' . $out . '</' . $tag . '>'; }
/** * The UI to do a search. * * @return tempcode The UI */ function form() { global $NON_CANONICAL_PARAMS; $id = get_param('id', ''); $title = get_page_title('SEARCH_TITLE'); require_code('templates_internalise_screen'); if ($id != '') { require_code('hooks/modules/search/' . filter_naughty_harsh($id), true); $object = object_factory('Hook_search_' . filter_naughty_harsh($id)); $info = $object->info(); if (!is_null($info)) { $title = get_page_title('_SEARCH_TITLE', true, array($info['lang'])); } breadcrumb_set_parents(array(array('_SELF:_SELF', do_lang_tempcode('SEARCH_FOR')))); breadcrumb_set_self($info['lang']); $under = get_param('search_under', '!', true); if (!is_null($info) && method_exists($object, 'get_tree')) { $object->get_tree($under); } if (!is_null($info)) { $test_tpl = internalise_own_screen($title); } else { $test_tpl = NULL; } } else { $test_tpl = internalise_own_screen($title); } if (is_object($test_tpl)) { return $test_tpl; } require_javascript('javascript_ajax'); require_javascript('javascript_ajax_people_lists'); $content = get_param('content', NULL, true); $user_label = do_lang_tempcode('SEARCH_USER'); $days_label = do_lang_tempcode('SUBMITTED_WITHIN'); $extra_sort_fields = array(); if ($id != '') { $url_map = array('page' => '_SELF', 'type' => 'results', 'id' => $id, 'specific' => 1); $catalogue_name = get_param('catalogue_name', ''); if ($catalogue_name != '') { $url_map['catalogue_name'] = $catalogue_name; } $force_non_tabular = get_param_integer('force_non_tabular', 0); if ($force_non_tabular == 1) { $url_map['force_non_tabular'] = 1; } $url = build_url($url_map, '_SELF', NULL, false, true); require_code('hooks/modules/search/' . filter_naughty_harsh($id), true); $object = object_factory('Hook_search_' . filter_naughty_harsh($id)); $info = $object->info(); if (is_null($info)) { warn_exit(do_lang_tempcode('SEARCH_HOOK_NOT_AVAILABLE')); } if (array_key_exists('user_label', $info)) { $user_label = $info['user_label']; } if (array_key_exists('days_label', $info)) { $days_label = $info['days_label']; } $extra_sort_fields = array_key_exists('extra_sort_fields', $info) ? $info['extra_sort_fields'] : array(); $under = NULL; if (method_exists($object, 'ajax_tree')) { require_javascript('javascript_tree_list'); require_javascript('javascript_more'); $ajax = true; $under = get_param('search_under', '', true); list($ajax_hook, $ajax_options) = $object->ajax_tree(); require_code('hooks/systems/ajax_tree/' . $ajax_hook); $tree_hook_object = object_factory('Hook_' . $ajax_hook); $simple_content = $tree_hook_object->simple(NULL, $ajax_options, preg_replace('#,.*$#', '', $under)); $nice_label = $under; if (!is_null($under)) { $simple_content_evaluated = $simple_content->evaluate(); $matches = array(); if (preg_match('#<option [^>]*value="' . str_replace('#', '\\#', preg_quote($under)) . '(' . (strpos($under, ',') === false ? ',' : '') . '[^"]*)?"[^>]*>([^>]* > )?([^>]*)</option>#', $simple_content_evaluated, $matches) != 0) { if (strpos($under, ',') === false) { $under = $under . $matches[1]; } $nice_label = trim($matches[3]); } } require_code('form_templates'); $tree = do_template('FORM_SCREEN_INPUT_TREE_LIST', array('_GUID' => '25368e562be3b4b9c6163aa008b47c91', 'TABINDEX' => strval(get_form_field_tabindex()), 'NICE_LABEL' => is_null($nice_label) || $nice_label == '-1' ? '' : $nice_label, 'END_OF_FORM' => true, 'REQUIRED' => '', 'USE_SERVER_ID' => false, 'NAME' => 'search_under', 'DEFAULT' => $under, 'HOOK' => $ajax_hook, 'ROOT_ID' => '', 'OPTIONS' => serialize($ajax_options))); } else { $ajax = false; $tree = form_input_list_entry('!', false, do_lang_tempcode('NA_EM')); if (method_exists($object, 'get_tree')) { $under = get_param('search_under', '!', true); $tree->attach($object->get_tree($under)); } } $options = new ocp_tempcode(); if (array_key_exists('special_on', $info)) { foreach ($info['special_on'] as $name => $display) { $options->attach(do_template('SEARCH_FOR_SEARCH_DOMAIN_OPTION', array('_GUID' => 'c1853f42d0a110026453f8b94c9f623c', 'CHECKED' => is_null($content) || get_param_integer('option_' . $id . '_' . $name, 0) == 1, 'NAME' => 'option_' . $id . '_' . $name, 'DISPLAY' => $display))); } } if (array_key_exists('special_off', $info)) { foreach ($info['special_off'] as $name => $display) { $options->attach(do_template('SEARCH_FOR_SEARCH_DOMAIN_OPTION', array('_GUID' => '2223ada7636c85e6879feb9a6f6885d2', 'CHECKED' => get_param_integer('option_' . $id . '_' . $name, 0) == 1, 'NAME' => 'option_' . $id . '_' . $name, 'DISPLAY' => $display))); } } if (method_exists($object, 'get_fields')) { $fields = $object->get_fields(); foreach ($fields as $field) { $options->attach(do_template('SEARCH_FOR_SEARCH_DOMAIN_OPTION' . $field['TYPE'], array('_GUID' => 'a223ada7636c85e6879feb9a6f6885d2', 'NAME' => 'option_' . $field['NAME'], 'DISPLAY' => $field['DISPLAY'], 'SPECIAL' => $field['SPECIAL'], 'CHECKED' => array_key_exists('checked', $field) ? $field['CHECKED'] : false))); } } $specialisation = do_template('SEARCH_ADVANCED', array('_GUID' => 'fad0c147b8291ba972f105c65715f1ac', 'AJAX' => $ajax, 'OPTIONS' => $options, 'TREE' => $tree, 'UNDERNEATH' => !is_null($under))); } else { $map = array('page' => '_SELF', 'type' => 'results'); $under = get_param('search_under', '-1', true); if ($under != '-1') { $map['search_under'] = $under; } $url = build_url($map, '_SELF', NULL, false, true); $search_domains = new ocp_tempcode(); $_search_domains = array(); $_hooks = find_all_hooks('modules', 'search'); foreach (array_keys($_hooks) as $hook) { require_code('hooks/modules/search/' . filter_naughty_harsh($hook)); $object = object_factory('Hook_search_' . filter_naughty_harsh($hook), true); if (is_null($object)) { continue; } $info = $object->info(); if (is_null($info)) { continue; } $NON_CANONICAL_PARAMS[] = 'search_' . $hook; $is_default_or_advanced = $info['default'] && $id == '' || $hook == $id; $checked = get_param_integer('search_' . $hook, is_null($content) || get_param_integer('all_defaults', 0) == 1 ? $is_default_or_advanced ? 1 : 0 : 0) == 1; $options = array_key_exists('special_on', $info) || array_key_exists('special_off', $info) || array_key_exists('extra_sort_fields', $info) || method_exists($object, 'get_fields') || method_exists($object, 'get_tree') || method_exists($object, 'get_ajax_tree') ? build_url(array('page' => '_SELF', 'id' => $hook), '_SELF', NULL, false, true) : new ocp_tempcode(); $_search_domains[] = array('_GUID' => '3d3099872184923aec0f49388f52c750', 'ADVANCED_ONLY' => array_key_exists('advanced_only', $info) && $info['advanced_only'], 'CHECKED' => $checked, 'OPTIONS' => $options, 'LANG' => $info['lang'], 'NAME' => $hook); } global $M_SORT_KEY; $M_SORT_KEY = 'LANG'; usort($_search_domains, 'multi_sort'); foreach ($_search_domains as $sd) { $search_domains->attach(do_template('SEARCH_FOR_SEARCH_DOMAIN', $sd)); } $specialisation = do_template('SEARCH_DOMAINS', array('_GUID' => '1fd8718b540ec475988070ee7a444dc1', 'SEARCH_DOMAINS' => $search_domains)); } $author = get_param('author', ''); $author_id = $author != '' ? $GLOBALS['FORUM_DRIVER']->get_member_from_username($author) : NULL; $days = get_param_integer('days', 60); $sort = get_param('sort', 'relevance'); $direction = get_param('direction', 'DESC'); if (!in_array(strtoupper($direction), array('ASC', 'DESC'))) { log_hack_attack_and_exit('ORDERBY_HACK'); } $NON_CANONICAL_PARAMS[] = 'sort'; $NON_CANONICAL_PARAMS[] = 'direction'; $only_titles = get_param_integer('only_titles', 0) == 1; $search_under = get_param('search_under', '!', true); if ($search_under == '') { $search_under = '!'; } $boolean_operator = get_param('conjunctive_operator', 'OR'); $NON_CANONICAL_PARAMS[] = 'search_under'; $NON_CANONICAL_PARAMS[] = 'all_defaults'; $NON_CANONICAL_PARAMS[] = 'days'; $NON_CANONICAL_PARAMS[] = 'only_titles'; $NON_CANONICAL_PARAMS[] = 'conjunctive_operator'; $NON_CANONICAL_PARAMS[] = 'boolean_search'; $NON_CANONICAL_PARAMS[] = 'only_search_meta'; $NON_CANONICAL_PARAMS[] = 'content'; $NON_CANONICAL_PARAMS[] = 'author'; $test = db_has_full_text($GLOBALS['SITE_DB']->connection_read); $old_mysql = !$test; $can_order_by_rating = db_has_subqueries($GLOBALS['SITE_DB']->connection_read); // Perform search, if we did one $out = NULL; $results_browser = ''; $num_results = 0; if (!is_null($content)) { list($out, $results_browser, $num_results) = $this->results($id, $author, $author_id, $days, $sort, $direction, $only_titles, $search_under); if (has_zone_access(get_member(), 'adminzone')) { $admin_search_url = build_url(array('page' => 'admin', 'type' => 'search', 'search_content' => $content), 'adminzone'); attach_message(do_lang_tempcode('ALSO_ADMIN_ZONE_SEARCH', escape_html($admin_search_url->evaluate())), 'inform'); } } return do_template('SEARCH_FORM_SCREEN', array('_GUID' => '8bb208185740183323a6fe6e89d55de5', 'SEARCH_TERM' => is_null($content) ? '' : $content, 'NUM_RESULTS' => integer_format($num_results), 'CAN_ORDER_BY_RATING' => $can_order_by_rating, 'EXTRA_SORT_FIELDS' => $extra_sort_fields, 'USER_LABEL' => $user_label, 'DAYS_LABEL' => $days_label, 'BOOLEAN_SEARCH' => $this->_is_boolean_search(), 'AND' => $boolean_operator == 'AND', 'ONLY_TITLES' => $only_titles, 'DAYS' => is_null($days) ? '' : strval($days), 'SORT' => $sort, 'DIRECTION' => $direction, 'CONTENT' => $content, 'RESULTS' => $out, 'RESULTS_BROWSER' => $results_browser, 'OLD_MYSQL' => $old_mysql, 'TITLE' => $title, 'AUTHOR' => $author, 'SPECIALISATION' => $specialisation, 'URL' => $url)); }
/** * Standard modular new-style deep page-link finder function (does not return the main entry-points). * * @param string Callback function to send discovered page-links to. * @param MEMBER The member we are finding stuff for (we only find what the member can view). * @param integer Code for how deep we are tunnelling down, in terms of whether we are getting entries as well as categories. * @param string Stub used to create page-links. This is passed in because we don't want to assume a zone or page name within this function. * @param ?string Where we're looking under (NULL: root of tree). We typically will NOT show a root node as there's often already an entry-point representing it. * @param integer Our recursion depth (used to calculate importance of page-link, used for instance by Google sitemap). Deeper is typically less important. * @param ?array Non-standard for API [extra parameter tacked on] (NULL: yet unknown). Contents of database table for performance. * @param ?array Non-standard for API [extra parameter tacked on] (NULL: yet unknown). Contents of database table for performance. */ function get_sitemap_pagelinks($callback, $member_id, $depth, $pagelink_stub, $parent_pagelink = NULL, $recurse_level = 0, $category_data = NULL, $entry_data = NULL) { $parent_pagelink_orig = $parent_pagelink; // This is where we start if (is_null($parent_pagelink)) { $parent_pagelink = $pagelink_stub . ':misc'; // This is the entry-point we're under $parent_attributes = array('id' => NULL, 'c_name' => ''); } else { list(, $parent_attributes, ) = page_link_decode($parent_pagelink); } // We read in all data for efficiency if (is_null($category_data)) { $query = 'SELECT c_name,d.id,t.text_original AS title,cc_add_date AS edit_date'; $lots = $GLOBALS['SITE_DB']->query_value_null_ok('catalogue_categories', 'COUNT(*)') > 1000 && db_has_subqueries($GLOBALS['SITE_DB']->connection_read); if ($lots) { $query .= ',NULL AS parent_id'; } else { $query .= ',cc_parent_id AS parent_id'; } $query .= ' FROM ' . get_table_prefix() . 'catalogue_categories d LEFT JOIN ' . get_table_prefix() . 'translate t ON ' . db_string_equal_to('language', user_lang()) . ' AND t.id=d.cc_title'; $query .= ' WHERE d.c_name NOT LIKE \'' . db_encode_like('\\_%') . '\''; if ($lots) { $query .= ' AND EXISTS (SELECT * FROM ' . get_table_prefix() . 'catalogue_entries e WHERE e.cc_id=d.id)'; } $category_data = list_to_map('id', $GLOBALS['SITE_DB']->query($query)); } $query = 'SELECT c.* FROM ' . get_table_prefix() . 'catalogues c'; if (can_arbitrary_groupby()) { $query .= ' JOIN ' . get_table_prefix() . 'catalogue_entries e ON c.c_name=e.c_name'; } $query .= ' WHERE c.c_name NOT LIKE \'' . db_encode_like('\\_%') . '\''; if (can_arbitrary_groupby()) { $query .= ' GROUP BY e.cc_id'; } $catalogues = list_to_map('c_name', $GLOBALS['SITE_DB']->query($query)); if (!is_null($parent_pagelink_orig)) { $parent_attributes['c_name'] = $category_data[intval($parent_attributes['id'])]['c_name']; } // Subcategories foreach ($category_data as $row) { if (!is_null($row['parent_id']) && strval($row['parent_id']) == $parent_attributes['id'] || is_null($parent_pagelink_orig) && is_null($row['parent_id'])) { $pagelink = $pagelink_stub . 'category:' . strval($row['id']); if (__CLASS__ != '') { $this->get_sitemap_pagelinks($callback, $member_id, $depth, $pagelink_stub, $pagelink, $recurse_level + 1, $category_data, $entry_data); // Recurse } else { call_user_func_array(__FUNCTION__, array($callback, $member_id, $depth, $pagelink_stub, $pagelink, $recurse_level + 1, $category_data, $entry_data)); // Recurse } if (has_category_access($member_id, 'catalogues_catalogue', $row['c_name']) && (get_value('disable_cat_cat_perms') === '1' || has_category_access($member_id, 'catalogues_category', strval($row['id'])))) { call_user_func_array($callback, array($pagelink, $parent_pagelink, NULL, $row['edit_date'], max(0.7 - $recurse_level * 0.1, 0.3), $row['title'])); // Callback } else { call_user_func_array($callback, array($pagelink, $parent_pagelink, NULL, $row['edit_date'], max(0.7 - $recurse_level * 0.1, 0.3), do_lang('UNKNOWN'), false)); // Callback } } } // Entries if ($depth >= DEPTH__ENTRIES && has_category_access($member_id, 'catalogues_catalogue', $parent_attributes['c_name']) && (get_value('disable_cat_cat_perms') === '1' || has_category_access($member_id, 'catalogues_category', $parent_attributes['id']))) { require_code('catalogues'); $start = 0; do { $entry_data = $GLOBALS['SITE_DB']->query_select('catalogue_entries d', array('c_name', 'id', 'cc_id AS category_id', 'ce_add_date AS add_date', 'ce_edit_date AS edit_date', 'd.*'), array('cc_id' => intval($parent_attributes['id'])), '', 500, $start); foreach ($entry_data as $row) { $map = get_catalogue_entry_map($row, $catalogues[$row['c_name']], 'CATEGORY', 'DEFAULT', NULL, NULL, array(0)); $row['title'] = is_object($map['FIELD_0_PLAIN']) ? $map['FIELD_0_PLAIN']->evaluate() : $map['FIELD_0_PLAIN']; $pagelink = $pagelink_stub . 'entry:' . strval($row['id']); call_user_func_array($callback, array($pagelink, $parent_pagelink, $row['add_date'], $row['edit_date'], 0.2, $row['title'])); // Callback } $start += 500; } while (array_key_exists(0, $entry_data)); } }
/** * Get some rows, queried from the database according to the search parameters. * * @param ?ID_TEXT The META type used by our content (NULL: Cannot support META search) * @param ?ID_TEXT The name of the field that retrieved META IDs will relate to (NULL: Cannot support META search) * @param string Search string * @param boolean Whether to do a boolean search. * @param ID_TEXT Boolean operator * @set OR AND * @param boolean Whether to only do a META (tags) search * @param ID_TEXT Order direction * @param integer Start position in total results * @param integer Maximum results to return in total * @param boolean Whether to only search titles (as opposed to both titles and content) * @param ID_TEXT The table name * @param array The translateable fields to search over (or an ! which is skipped). The first of these must be the title field or an '!'; if it is '!' then the title field will be the first raw-field * @param string The WHERE clause * @param string The WHERE clause that applies specifically for content (this will be duplicated to check against multiple fields). ? refers to the yet-unknown field name * @param ID_TEXT What to order by * @param string What to select * @param ?array The non-translateable fields to search over (NULL: there are none) * @param ?string The permission module to check category access for (NULL: none) * @param ?string The field that specifies the permissions ID to check category access for (NULL: none) * @param boolean Whether the permissions field is a string * @return array The rows found */ function get_search_rows($meta_type, $meta_id_field, $content, $boolean_search, $boolean_operator, $only_search_meta, $direction, $max, $start, $only_titles, $table, $fields, $where_clause, $content_where, $order, $select = '*', $raw_fields = NULL, $permissions_module = NULL, $permissions_field = NULL, $permissions_field_is_string = false) { if (substr($where_clause, 0, 5) == ' AND ') { $where_clause = substr($where_clause, 5); } if (substr($where_clause, -5) == ' AND ') { $where_clause = substr($where_clause, 0, strlen($where_clause) - 5); } $where_alternative_matches = array(); $had_limit_imposed = false; if (!is_null($permissions_module) && !$GLOBALS['FORUM_DRIVER']->is_super_admin(get_member())) { $g_or = _get_where_clause_groups(get_member()); // this destroys mysqls query optimiser by forcing complexed OR's into the join, so we'll do this in PHP code // $table.=' LEFT JOIN '.$GLOBALS['SITE_DB']->get_table_prefix().'group_category_access z ON ('.db_string_equal_to('z.module_the_name',$permissions_module).' AND z.category_name='.$permissions_field.(($g_or!='')?(' AND '.str_replace('group_id','z.group_id',$g_or)):'').')'; // $where_clause.=' AND '; // $where_clause.='z.category_name IS NOT NULL'; $cat_access = list_to_map('category_name', $GLOBALS['SITE_DB']->query('SELECT category_name FROM ' . $GLOBALS['SITE_DB']->get_table_prefix() . 'group_category_access WHERE ' . db_string_equal_to('module_the_name', $permissions_module) . ($g_or != '' ? ' AND (' . $g_or . ')' : ''))); } if ($only_titles && array_key_exists(0, $fields) && $fields[0] == '') { return array(); } if (is_null($raw_fields)) { $raw_fields = array(); } $db = substr($table, 0, 2) != 'f_' ? $GLOBALS['SITE_DB'] : $GLOBALS['FORUM_DB']; // This is so for example catalogue_entries.php can use brackets in it's table specifier whilst avoiding the table prefix after the first bracket. A bit weird, but that's our convention and it does save a small amount of typing $table_clause = $db->get_table_prefix() . ($table[0] == '(' ? substr($table, 1) : $table); if ($table[0] == '(') { $table_clause = '(' . $table_clause; } $t_rows = array(); $t_count = 0; // Rating ordering, via special encoding if (substr($order, 0, 7) == '_rating') { list(, $rating_type, $meta_rating_id_field) = explode(':', $order); $select .= ',(SELECT AVG(rating) FROM ' . get_table_prefix() . 'rating WHERE ' . db_string_equal_to('rating_for_type', $rating_type) . ' AND rating_for_id=' . $meta_rating_id_field . ') AS compound_rating'; $order = 'compound_rating'; } $translate_join_type = get_value('alternate_search_join_type') === '1' ? 'LEFT JOIN' : 'JOIN'; // Defined-keywords/tags search if (get_param_integer('keep_just_show_query', 0) == 0 && !is_null($meta_type) && $content != '') { $keywords_where = preg_replace('#\\?#', 'tm.text_original', build_content_where($content, $boolean_search, $boolean_operator, true)); $keywords_where = str_replace(' AND (tm.text_original IS NOT NULL)', '', $keywords_where); // Not needed for translate joins, as these won't be NULL's. Fixes performance issue. if ($keywords_where != '') { if ($meta_id_field == 'the_zone:the_page') { $meta_join = 'm.meta_for_id=CONCAT(r.the_zone,\':\',r.the_page)'; } else { $meta_join = 'm.meta_for_id=r.' . $meta_id_field; } $extra_join = ''; foreach ($fields as $i => $field) { if ($field == '' || $field == '!' || strpos($select, 't1.text_original') === false) { continue; } $extra_join .= ' ' . $translate_join_type . ' ' . $db->get_table_prefix() . 'translate t' . strval($i) . ' ON t' . strval($i) . '.id=' . $field . ' AND ' . db_string_equal_to('t' . strval($i) . '.language', user_lang()); } if (!db_has_subqueries($db->connection_read) || true) { $_keywords_query = $table_clause . ' LEFT JOIN ' . $db->get_table_prefix() . 'seo_meta m ON (' . db_string_equal_to('m.meta_for_type', $meta_type) . ' AND ' . $meta_join . ') ' . $translate_join_type . ' ' . $db->get_table_prefix() . 'translate tm ON tm.id=m.meta_keywords AND ' . db_string_equal_to('tm.language', user_lang()) . $extra_join; $_keywords_query .= ' WHERE ' . $keywords_where; $_keywords_query .= $where_clause != '' ? ' AND ' . $where_clause : ''; } else { $_keywords_query = $table_clause . ' LEFT JOIN ' . $db->get_table_prefix() . 'seo_meta m ON (' . db_string_equal_to('m.meta_for_type', $meta_type) . ' AND ' . $meta_join . ') ' . $translate_join_type . ' ' . $db->get_table_prefix() . 'translate tm ON tm.id=m.meta_keywords AND ' . db_string_equal_to('tm.language', user_lang()) . $extra_join; $_keywords_query .= ' WHERE ' . $keywords_where; $_keywords_query .= $where_clause != '' ? ' AND tm.id IN (SELECT m.id FROM ' . $table_clause . ' LEFT JOIN ' . $db->get_table_prefix() . 'seo_meta m ON (' . db_string_equal_to('m.meta_for_type', $meta_type) . ' AND ' . $meta_join . ') ' . $translate_join_type . ' ' . $db->get_table_prefix() . 'translate tm ON tm.id=m.meta_keywords AND ' . db_string_equal_to('tm.language', user_lang()) . ' WHERE ' . $where_clause . ' AND ' . $keywords_where . ')' : ''; } $keywords_query = 'SELECT ' . $select . ' FROM ' . $_keywords_query; $_count_query_keywords_search = 'SELECT COUNT(*) FROM ' . $_keywords_query; $group_by_ok = can_arbitrary_groupby() && $meta_id_field === 'id'; if (strpos($table, ' LEFT JOIN') === false) { $group_by_ok = false; } // Don't actually need to do a group by, as no duplication possible $keywords_query .= $group_by_ok ? ' GROUP BY r.id' : ''; if ($order != '' && $order . ' ' . $direction != 'contextual_relevance DESC') { $keywords_query .= ' ORDER BY ' . $order; if ($direction == 'DESC') { $keywords_query .= ' DESC'; } } if ($group_by_ok) { $_count_query_keywords_search = str_replace('COUNT(*)', 'COUNT(DISTINCT r.id)', $_count_query_keywords_search); } $t_keyword_search_rows_count = $db->query_value_null_ok_full($_count_query_keywords_search); if ($t_keyword_search_rows_count > 500) { $t_keyword_search_rows = $db->query($keywords_query, $max + $start); $had_limit_imposed = true; } else { $t_keyword_search_rows = $db->query($keywords_query); } $t_count += $t_keyword_search_rows_count; $t_rows = array_merge($t_rows, $t_keyword_search_rows); } else { $_count_query_keywords_search = NULL; } } else { $_count_query_keywords_search = NULL; } $orig_table_clause = $table_clause; // Main content search if (!$only_search_meta) { if ($content_where != '' || preg_match('#t\\d+\\.text_original#', $where_clause) != 0 || preg_match('#t\\d+\\.text_original#', $select) != 0) { // Each of the fields represents an 'OR' match, so we put it together into a list ($where_alternative_matches) of specifiers for each. Hopefully we will 'UNION' them rather than 'OR' them as it is much more efficient in terms of table index usage $where_alternative_matches = array(); foreach ($fields as $i => $field) { if (strpos($select, 't' . strval($i) . '.text_original') !== false || strpos($where_clause, 't' . strval($i) . '.text_original') !== false) { $tc_add = ' ' . $translate_join_type . ' ' . $db->get_table_prefix() . 'translate t' . strval($i) . ' ON t' . strval($i) . '.id=' . $field . ' AND ' . db_string_equal_to('t' . strval($i) . '.language', user_lang()); $orig_table_clause .= $tc_add; } } foreach ($fields as $i => $field) { if ($field == '' || $field == '!') { continue; } if ($field == $order) { $order = 't' . $i . '.text_original'; } // Ah, remap to the textual equivalent then $tc_add = ' ' . $translate_join_type . ' ' . $db->get_table_prefix() . 'translate t' . strval($i) . ' ON t' . strval($i) . '.id=' . $field . ' AND ' . db_string_equal_to('t' . strval($i) . '.language', user_lang()); if (strpos($orig_table_clause, $tc_add) !== false) { $tc_add = ''; } if (!$only_titles || $i == 0) { $where_clause_2 = preg_replace('#\\?#', 't' . strval($i) . '.text_original', $content_where); $where_clause_2 = str_replace(' AND (t' . strval($i) . '.text_original IS NOT NULL)', '', $where_clause_2); // Not needed for translate joins, as these won't be NULL's. Fixes performance issue. $where_clause_3 = $where_clause; if ($table == 'f_members' && substr($field, 0, 6) == 'field_' && db_has_subqueries($db->connection_read)) { $where_clause_3 .= ($where_clause == '' ? '' : ' AND ') . 'NOT EXISTS (SELECT * FROM ' . $db->get_table_prefix() . 'f_cpf_perms cpfp WHERE cpfp.member_id=r.id AND cpfp.field_id=' . substr($field, 6) . ' AND cpfp.guest_view=0)'; } if ($order == '' && db_has_expression_ordering($db->connection_read) && $content_where != '') { $_select = preg_replace('#\\?#', 't' . strval($i) . '.text_original', $content_where) . ' AS contextual_relevance'; $_select = str_replace(' AND (t' . strval($i) . '.text_original IS NOT NULL)', '', $_select); // Not needed for translate joins, as these won't be NULL's. Fixes performance issue. } else { $_select = '1'; } $_table_clause = $orig_table_clause . $tc_add; $where_alternative_matches[] = array($where_clause_2, $where_clause_3, $_select, $_table_clause, 't' . strval($i)); } else { $_table_clause = $orig_table_clause . $tc_add; $where_alternative_matches[] = array('1=0', '', '1', $_table_clause, 't' . strval($i)); } } if ($content_where != '') { foreach ($raw_fields as $i => $field) { if ($only_titles && $i != 0) { break; } $where_clause_2 = preg_replace('#\\?#', $field, $content_where); $where_clause_3 = $where_clause; if ($table == 'f_members' && substr($field, 0, 6) == 'field_' && db_has_subqueries($db->connection_read)) { $where_clause_3 .= ($where_clause == '' ? '' : ' AND ') . 'NOT EXISTS (SELECT * FROM ' . $db->get_table_prefix() . 'f_cpf_perms cpfp WHERE cpfp.member_id=r.id AND cpfp.field_id=' . substr($field, 6) . ' AND cpfp.guest_view=0)'; } if ($order == '' && db_has_expression_ordering($db->connection_read) && $content_where != '') { $_select = preg_replace('#\\?#', $field, $content_where) . ' AS contextual_relevance'; } else { $_select = '1'; } $_table_clause = $orig_table_clause; $where_alternative_matches[] = array($where_clause_2, $where_clause_3, $_select, $_table_clause, NULL); } } } if (count($where_alternative_matches) == 0) { $where_alternative_matches[] = array($where_clause, '', '', $table_clause, NULL); } else { if ($order == '' && db_has_expression_ordering($db->connection_read) && $content_where != '') { $order = 'contextual_relevance DESC'; } } // Work out main query global $SITE_INFO; if (isset($SITE_INFO['mysql_old']) && $SITE_INFO['mysql_old'] == '1' || !isset($SITE_INFO['mysql_old']) && is_file(get_file_base() . '/mysql_old')) { $_query = ''; foreach ($where_alternative_matches as $parts) { list($where_clause_2, $where_clause_3, $_select, , ) = $parts; $where_clause_3 = $where_clause_2 . ($where_clause_3 == '' ? '' : ($where_clause_2 == '' ? '' : ' AND ') . $where_clause_3); $select .= ($_select == '' ? '' : ',') . $_select; $_query .= $where_clause_3 != '' ? ($_query == '' ? ' WHERE ' : ' OR ') . $where_clause_3 : ''; } $query = 'SELECT ' . $select . ' FROM ' . $table_clause . $_query; } else { $query = ''; foreach ($where_alternative_matches as $parts) { list($where_clause_2, $where_clause_3, $_select, $_table_clause, $tid) = $parts; if ($query != '') { $query .= ' LIMIT ' . strval($max); $query .= ' UNION '; } if (!db_has_subqueries($db->connection_read) || is_null($tid) || $content_where == '' || true) { $where_clause_3 = $where_clause_2 . ($where_clause_3 == '' ? '' : ($where_clause_2 == '' ? '' : ' AND ') . $where_clause_3); $query .= 'SELECT ' . $select . ($_select == '' ? '' : ',') . $_select . ' FROM ' . $_table_clause . ($where_clause_3 == '' ? '' : ' WHERE ' . $where_clause_3); } else { $query .= 'SELECT ' . $select . ($_select == '' ? '' : ',') . $_select . ' FROM ' . $_table_clause; if ($where_clause_2 != '' || $where_clause_3 != '') { $query .= ' WHERE ' . $where_clause_2; $query .= $where_clause_3 != '' ? ($where_clause_2 == '' ? '' : ' AND ') . $tid . '.id IN (SELECT ' . $tid . '.id FROM ' . $_table_clause . ' WHERE ' . $where_clause_2 . ($where_clause_3 == '' ? '' : ($where_clause_2 == '' ? '' : ' AND ') . $where_clause_3) . ')' : ''; } } } } // Work out COUNT(*) query using one of a few possible methods. It's not efficient and stops us doing proper merge-sorting between content types (and possible not accurate - if we use an efficient but non-deduping COUNT strategy) if we have to use this, so we only do it if there are too many rows to fetch in one go. $_query = ''; if (isset($SITE_INFO['mysql_old']) && $SITE_INFO['mysql_old'] == '1' || !isset($SITE_INFO['mysql_old']) && is_file(get_file_base() . '/mysql_old') || strpos(get_db_type(), 'mysql') === false) { foreach ($where_alternative_matches as $parts) { list($where_clause_2, $where_clause_3, , $_table_clause, $tid) = $parts; if (!db_has_subqueries($db->connection_read) || is_null($tid) || $content_where == '' || true) { $where_clause_3 = $where_clause_2 . ($where_clause_3 == '' ? '' : ($where_clause_2 == '' ? '' : ' AND ') . $where_clause_3); $_query .= $where_clause_3 != '' ? ($_query == '' ? ' WHERE ' : ' OR ') . $where_clause_3 : ''; } else { if ($where_clause_2 != '' || $where_clause_3 != '') { $_query .= ($_query == '' ? ' WHERE ' : ' OR ') . $where_clause_2; $_query .= $where_clause_3 != '' ? ($where_clause_2 == '' ? '' : ' AND ') . $tid . '.id IN (SELECT ' . $tid . '.id FROM ' . $_table_clause . ' WHERE ' . $where_clause_2 . ($where_clause_3 == '' ? '' : ($where_clause_2 == '' ? '' : ' AND ') . $where_clause_3) . ')' : ''; } } } $_count_query_main_search = 'SELECT COUNT(*) FROM ' . $table_clause . $_query; } else { foreach ($where_alternative_matches as $parts) { list($where_clause_2, $where_clause_3, $_select, $_table_clause, $tid) = $parts; if ($_query != '') { $_query .= '+'; } if (!db_has_subqueries($db->connection_read) || is_null($tid) || $content_where == '' || true) { $where_clause_3 = $where_clause_2 . ($where_clause_3 == '' ? '' : ($where_clause_2 == '' ? '' : ' AND ') . $where_clause_3); $_query .= '(SELECT COUNT(*) FROM ' . $_table_clause . ($where_clause_3 == '' ? '' : ' WHERE ' . $where_clause_3) . ')'; } else { $_query .= '(SELECT COUNT(*) FROM ' . $_table_clause; if ($where_clause_2 != '' || $where_clause_3 != '') { $_query .= ' WHERE ' . $where_clause_2; $_query .= $where_clause_3 != '' ? ($where_clause_2 == '' ? '' : ' AND ') . $tid . '.id IN (SELECT ' . $tid . '.id FROM ' . $_table_clause . ' WHERE ' . $where_clause_2 . ($where_clause_3 == '' ? '' : ($where_clause_2 == '' ? '' : ' AND ') . $where_clause_3) . ')' : ''; } $_query .= ')'; } } $_count_query_main_search = 'SELECT (' . $_query . ')'; } $group_by_ok = can_arbitrary_groupby() && $meta_id_field === 'id'; if (strpos($table, ' LEFT JOIN') === false) { $group_by_ok = false; } // Don't actually need to do a group by, as no duplication possible. We want to avoid GROUP BY as it forces MySQL to create a temporary table, slowing things down a lot. $query .= $group_by_ok ? ' GROUP BY r.id' : ''; if ($order != '' && $order . ' ' . $direction != 'contextual_relevance DESC') { $query .= ' ORDER BY ' . $order; if ($direction == 'DESC' && substr($order, -4) != ' ASC' && substr($order, -5) != ' DESC') { $query .= ' DESC'; } } if (get_param_integer('keep_show_query', 0) == 1) { attach_message($query, 'inform'); } if (get_param_integer('keep_just_show_query', 0) == 1) { @ini_set('ocproducts.xss_detect', '0'); header('Content-type: text/plain; charset=' . get_charset()); exit($query); } if ($group_by_ok) { $_count_query_main_search = str_replace('COUNT(*)', 'COUNT(DISTINCT r.id)', $_count_query_main_search); } $t_main_search_rows_count = $db->query_value_null_ok_full($_count_query_main_search); if ($t_main_search_rows_count > 500) { $t_main_search_rows = $db->query($query, $max + $start, NULL, false, true); $had_limit_imposed = true; } else { $t_main_search_rows = $db->query($query, NULL, NULL, false, true); } if ($t_main_search_rows === NULL) { $t_main_search_rows = array(); } // In case of a failed search query $t_count += $t_main_search_rows_count; $t_rows = array_merge($t_rows, $t_main_search_rows); } else { $t_main_search_rows = array(); } // Clean results and return // NB: We don't use the count_query's any more (except when using huge data sets, see above), because you can't actually just add them because they overlap. So instead we fetch all results and throw some away. $t_rows = array_merge($t_rows, $t_main_search_rows); if (count($t_rows) > 0) { $t_rows_new = array(); if (array_key_exists('id', $t_rows[0]) || array_key_exists('_primary_id', $t_rows[0])) { $done = array(); foreach ($t_rows as $t_row) { if (array_key_exists('id', $t_row)) { if (array_key_exists($t_row['id'], $done)) { continue; } $done[$t_row['id']] = 1; } elseif (array_key_exists('_primary_id', $t_row)) { if (array_key_exists($t_row['_primary_id'], $done)) { continue; } $done[$t_row['_primary_id']] = 1; } $t_rows_new[] = $t_row; } } else { foreach ($t_rows as $t_row) { unset($t_row['contextual_relevance']); foreach ($t_rows_new as $_t_row) { if ($_t_row == $t_row || array_key_exists('id', $t_row) && array_key_exists('id', $_t_row) && !array_key_exists('_primary_id', $t_row) && !array_key_exists('_primary_id', $_t_row) && $t_row['id'] == $_t_row['id'] || array_key_exists('_primary_id', $t_row) && array_key_exists('_primary_id', $_t_row) && $t_row['_primary_id'] == $_t_row['_primary_id']) { continue 2; } } $t_rows_new[] = $t_row; } } $t_rows = $t_rows_new; } if (get_param_integer('keep_show_query', 0) == 1) { if (array_key_exists(0, $t_rows) && array_key_exists('id', $t_rows[0])) { $results = var_export(array_unique(collapse_1d_complexity('id', $t_rows)), true); } else { $results = var_export($t_rows, true); } attach_message(do_lang('_RESULTS') . ': ' . $results, 'inform'); } if (isset($cat_access)) { $before = count($t_rows); foreach ($t_rows as $i => $row) { if (!array_key_exists(@strval($row[$permissions_field]), $cat_access)) { unset($t_rows[$i]); } } } $final_result_rows = $t_rows; if (!$had_limit_imposed) { // More accurate, as filtered for dupes $t_count = count($t_rows); } $GLOBALS['TOTAL_RESULTS'] += $t_count; array_splice($final_result_rows, $max * 2 + $start); // We return more than max in case our search hook does some extra in-code filtering (Catalogues, Comcode pages). It shouldn't really but sometimes it has to, and it certainly shouldn't filter more than 50%. Also so our overall ordering can be better. return $final_result_rows; }