BB_Query

The BuddyPress BB Query class.

Defined (1)

The class is defined in the following location(s).

/bp-forums/bbpress/bb-includes/class.bb-query.php  
  1. class BB_Query { 
  2. var $type; 
  3. var $query; 
  4. var $query_id; 
  5.  
  6. var $query_vars = array(); 
  7. var $not_set = array(); 
  8. var $request; 
  9. var $match_query = false; 
  10.  
  11. var $results; 
  12. var $errors; 
  13. var $count = 0; 
  14. var $found_rows = false; 
  15.  
  16. // Can optionally pass unique id string to help out filters 
  17. function __construct( $type = 'topic', $query = '', $id = '' ) { 
  18. $this->init( $type, $query, $id ); 
  19.  
  20. if ( !empty($this->query) ) 
  21. $this->query(); 
  22.  
  23. function BB_Query( $type = 'topic', $query = '', $id = '' ) { 
  24. $this->__construct( $type, $query, $id ); 
  25.  
  26. function init( $type = null, $query = null, $id = null ) { 
  27. if ( !is_null($type) || !isset($this->type) ) 
  28. $this->type = is_null($type) ? 'topic' : $type; 
  29. if ( !is_null($query) || !isset($this->query) ) 
  30. $this->query = $query; 
  31. if ( !is_null($id) || !isset($this->query_id) ) 
  32. $this->query_id = $id; 
  33.  
  34. $this->query_vars = array(); 
  35. $this->not_set = array(); 
  36. unset($this->request); 
  37. $this->match_query = false; 
  38.  
  39. unset($this->results, $this->errors); 
  40. $this->count = 0; 
  41. $this->found_rows = false; 
  42.  
  43. function &query() { 
  44. global $bbdb; 
  45.  
  46. if ( $args = func_get_args() ) 
  47. call_user_func_array( array(&$this, 'init'), $args ); 
  48.  
  49. if ( !$this->generate_query() ) 
  50. return; 
  51.  
  52. do_action_ref_array( 'bb_query', array(&$this) ); 
  53.  
  54. $key = md5( $this->request ); 
  55. if ( false === $cached_ids = wp_cache_get( $key, 'bb_query' ) ) { 
  56. if ( 'post' == $this->type ) { 
  57. $this->results = bb_cache_posts( $this->request, $this->query_vars['post_id_only'] ); // This always appends meta 
  58. $_the_id = 'post_id'; 
  59. $this->query_vars['append_meta'] = false; 
  60. } else { 
  61. $this->results = $bbdb->get_results( $this->request ); 
  62. $_the_id = 'topic_id'; 
  63. $cached_ids = array(); 
  64. if ( is_array($this->results) ) 
  65. foreach ( $this->results as $object ) 
  66. $cached_ids[] = $object->$_the_id; 
  67. wp_cache_set( $key, $cached_ids, 'bb_query' ); 
  68. } else { 
  69. if ( 'post' == $this->type ) { 
  70. $_query_ids = array(); 
  71. $_cached_posts = array(); 
  72. foreach ( $cached_ids as $_cached_id ) { 
  73. if ( false !== $_post = wp_cache_get( $_cached_id, 'bb_post' ) ) { 
  74. $_cached_posts[$_post->post_id] = $_post; 
  75. } else { 
  76. $_query_ids[] = $_cached_id; 
  77. if ( count( $_query_ids ) ) { 
  78. $_query_ids = join( ', ', array_map( 'intval', $_query_ids ) ); 
  79. $results = $bbdb->get_results( "SELECT * FROM $bbdb->posts WHERE post_id IN($_query_ids)" ); 
  80. $results = array_merge( $results, $_cached_posts ); 
  81. } else { 
  82. $results = $_cached_posts; 
  83. $_the_id = 'post_id'; 
  84. } else { 
  85. $_query_ids = array(); 
  86. $_cached_topics = array(); 
  87. foreach ( $cached_ids as $_cached_id ) { 
  88. if ( false !== $_topic = wp_cache_get( $_cached_id, 'bb_topic' ) ) { 
  89. $_cached_topics[$_topic->topic_id] = $_topic; 
  90. } else { 
  91. $_query_ids[] = $_cached_id; 
  92. if ( count( $_query_ids ) ) { 
  93. $_query_ids = join( ', ', array_map( 'intval', $_query_ids ) ); 
  94. $results = $bbdb->get_results( "SELECT * FROM $bbdb->topics WHERE topic_id IN($_query_ids)" ); 
  95. $results = array_merge( $results, $_cached_topics ); 
  96. } else { 
  97. $results = $_cached_topics; 
  98. $_the_id = 'topic_id'; 
  99.  
  100. $this->results = array(); 
  101. $trans = array(); 
  102.  
  103. foreach ( $results as $object ) 
  104. $trans[$object->$_the_id] = $object; 
  105. foreach ( $cached_ids as $cached_id ) 
  106. $this->results[] = $trans[$cached_id]; 
  107.  
  108. $this->count = count( $this->results ); 
  109.  
  110. if ( false === $this->found_rows && $this->query_vars['count'] ) // handles FOUND_ROWS() or COUNT(*) 
  111. $this->found_rows = bb_count_last_query( $this->request ); 
  112. if ( 'post' == $this->type ) { 
  113. if ( $this->query_vars['append_meta'] ) 
  114. $this->results = bb_append_meta( $this->results, 'post' ); 
  115. if ( $this->query_vars['cache_users'] ) 
  116. bb_post_author_cache( $this->results ); 
  117. if ( $this->query_vars['cache_topics'] ) 
  118. bb_cache_post_topics( $this->results ); 
  119. } else { 
  120. if ( $this->query_vars['append_meta'] ) 
  121. $this->results = bb_append_meta( $this->results, 'topic' ); 
  122.  
  123. return $this->results; 
  124.  
  125. function generate_query() { 
  126. if ( $args = func_get_args() ) 
  127. call_user_func_array( array(&$this, 'init'), $args ); 
  128.  
  129. $this->parse_query(); 
  130.  
  131. // Allow filter to abort query 
  132. if ( false === $this->query_vars ) 
  133. return false; 
  134.  
  135. if ( 'post' == $this->type ) 
  136. $this->generate_post_sql(); 
  137. else 
  138. $this->generate_topic_sql(); 
  139.  
  140. return $this->request; 
  141.  
  142. // $defaults = vars to use if not set in GET, POST or allowed 
  143. // $allowed = array( key_name => value, key_name, key_name, key_name => value ); 
  144. // key_name => value pairs override anything from defaults, GET, POST 
  145. // Lone key_names are a whitelist. Only those can be set by defaults, GET, POST 
  146. // If there are no lone key_names, allow everything but still override with key_name => value pairs 
  147. // Ex: $allowed = array( 'topic_status' => 0, 'post_status' => 0, 'topic_author', 'started' ); 
  148. // Will only take topic_author and started values from defaults, GET, POST and will query with topic_status = 0 and post_status = 0 
  149. function &query_from_env( $type = 'topic', $defaults = null, $allowed = null, $id = '' ) { 
  150. $this->init_from_env( $type, $defaults, $allowed, $id ); 
  151. return $this->query(); 
  152.  
  153. function init_from_env( $type = 'topic', $defaults = null, $allowed = null, $id = '' ) { 
  154. $vars = $this->fill_query_vars( array() ); 
  155.  
  156. $defaults = wp_parse_args($defaults); 
  157. $get_vars = stripslashes_deep( $_GET ); 
  158. $post_vars = stripslashes_deep( $_POST ); 
  159. $allowed = wp_parse_args($allowed); 
  160.  
  161. $_allowed = array(); 
  162. foreach ( array_keys($allowed) as $k ) { 
  163. if ( is_numeric($k) ) { 
  164. $_allowed[] = $allowed[$k]; 
  165. unset($allowed[$k]); 
  166. } elseif ( !isset($$k) ) { 
  167. $$k = $allowed[$k]; 
  168.  
  169. extract($post_vars, EXTR_SKIP); 
  170. extract($get_vars, EXTR_SKIP); 
  171. extract($defaults, EXTR_SKIP); 
  172.  
  173. $vars = $_allowed ? compact($_allowed, array_keys($allowed)) : compact(array_keys($vars)); 
  174.  
  175. $this->init( $type, $vars, $id ); 
  176.  
  177. function fill_query_vars( $array ) { 
  178. // Should use 0, '' for empty values 
  179. // Function should return false iff not set 
  180.  
  181. // parameters commented out are handled farther down 
  182.  
  183. $ints = array( 
  184. // 'page', // Defaults to global or number in URI 
  185. // 'per_page', // Defaults to page_topics 
  186. 'tag_id', // one tag ID 
  187. 'favorites', // one user ID,  
  188. 'offset', // first item to query 
  189. 'number' // number of items to retrieve 
  190. ); 
  191.  
  192. $parse_ints = array( 
  193. // Both 
  194. 'post_id',  
  195. 'topic_id',  
  196. 'forum_id',  
  197.  
  198. // Topics 
  199. 'topic_author_id',  
  200. 'post_count',  
  201. 'tag_count',  
  202.  
  203. // Posts 
  204. 'post_author_id',  
  205. 'position' 
  206. ); 
  207.  
  208. $dates = array( 
  209. 'started', // topic 
  210. 'updated', // topic 
  211. 'posted' // post 
  212. ); 
  213.  
  214. $others = array( 
  215. // Both 
  216. 'topic', // one topic name 
  217. 'forum', // one forum name 
  218. 'tag', // one tag name 
  219.  
  220. // Topics 
  221. 'topic_author', // one username 
  222. 'topic_status', // *normal, deleted, all, parse_int ( and - ) 
  223. 'open', // *all, yes = open, no = closed, parse_int ( and - ) 
  224. 'sticky', // *all, no = normal, forum, super = front, parse_int ( and - ) 
  225. 'meta_key', // one meta_key ( and - ) 
  226. 'meta_value', // range 
  227. 'topic_title', // LIKE search. Understands "doublequoted strings" 
  228. 'search', // generic search: topic_title OR post_text 
  229. // Can ONLY be used in a topic query 
  230. // Returns additional search_score and (concatenated) post_text columns 
  231.  
  232. // Posts 
  233. 'post_author', // one username 
  234. 'post_status', // *noraml, deleted, all, parse_int ( and - ) 
  235. 'post_text', // FULLTEXT search 
  236. // Returns additional search_score column (and (concatenated) post_text column if topic query) 
  237. 'poster_ip', // one IPv4 address 
  238.  
  239. // SQL 
  240. 'index_hint', // A full index hint using valid index hint syntax, can be multiple hints an array 
  241. 'order_by', // fieldname 
  242. 'order', // *DESC, ASC 
  243. 'count', // *false = none, true = COUNT(*), found_rows = FOUND_ROWS() 
  244. '_join_type', // not implemented: For benchmarking only. Will disappear. join (1 query), in (2 queries) 
  245.  
  246. // Utility 
  247. // 'append_meta', // *true, false: topics only 
  248. // 'cache_users', // *true, false 
  249. // 'cache_topics, // *true, false: posts only 
  250. // 'post_id_only', // true, *false: this query is only returning post IDs 
  251. 'cache_posts' // not implemented: none, first, last 
  252. ); 
  253.  
  254. foreach ( $ints as $key ) 
  255. if ( ( false === $array[$key] = isset($array[$key]) ? (int) $array[$key] : false ) && isset($this) ) 
  256. $this->not_set[] = $key; 
  257.  
  258. foreach ( $parse_ints as $key ) 
  259. if ( ( false === $array[$key] = isset($array[$key]) ? preg_replace( '/[^<=>0-9, -]/', '', $array[$key] ) : false ) && isset($this) ) 
  260. $this->not_set[] = $key; 
  261.  
  262. foreach ( $dates as $key ) 
  263. if ( ( false === $array[$key] = isset($array[$key]) ? preg_replace( '/[^<>0-9-]/', '', $array[$key] ) : false ) && isset($this) ) 
  264. $this->not_set[] = $key; 
  265.  
  266. foreach ( $others as $key ) { 
  267. if ( !isset($array[$key]) ) 
  268. $array[$key] = false; 
  269. if ( isset($this) && false === $array[$key] ) 
  270. $this->not_set[] = $key; 
  271.  
  272. // Both 
  273. if ( isset($array['page']) ) 
  274. $array['page'] = (int) $array['page']; 
  275. elseif ( isset($GLOBALS['page']) ) 
  276. $array['page'] = (int) $GLOBALS['page']; 
  277. else 
  278. $array['page'] = bb_get_uri_page(); 
  279.  
  280. if ( $array['page'] < 1 ) 
  281. $array['page'] = 1; 
  282.  
  283. $array['per_page'] = isset($array['per_page']) ? (int) $array['per_page'] : 0; 
  284. if ( $array['per_page'] < -1 ) 
  285. $array['per_page'] = 1; 
  286.  
  287. // Posts 
  288. if ( ( !$array['poster_ip'] = isset($array['poster_ip']) ? preg_replace("@[^0-9a-f:.]@i", '', $array['poster_ip']) : false ) && isset($this) ) { 
  289. $this->not_set[] = 'poster_ip'; 
  290. $array['poster_ip'] = false; 
  291.  
  292. // Utility 
  293. $array['append_meta'] = isset($array['append_meta']) ? (int) (bool) $array['append_meta'] : 1; 
  294. $array['cache_users'] = isset($array['cache_users']) ? (int) (bool) $array['cache_users'] : 1; 
  295. $array['cache_topics'] = isset($array['cache_topics']) ? (int) (bool) $array['cache_topics'] : 1; 
  296. $array['post_id_only'] = isset($array['post_id_only']) ? (int) (bool) $array['post_id_only'] : 1; 
  297.  
  298. // Only one FULLTEXT search per query please 
  299. if ( $array['search'] ) 
  300. $array['post_text'] = false; 
  301.  
  302. return $array; 
  303.  
  304. // Parse a query string and set query flag booleans. 
  305. function parse_query() { 
  306. if ( $args = func_get_args() ) 
  307. call_user_func_array( array(&$this, 'init'), $args ); 
  308.  
  309. if ( is_array($this->query) ) 
  310. $this->query_vars = $this->query; 
  311. else 
  312. wp_parse_str($this->query, $this->query_vars); 
  313.  
  314. do_action_ref_array('bb_parse_query', array(&$this)); 
  315.  
  316. if ( false === $this->query_vars ) 
  317. return; 
  318.  
  319. $this->query_vars = $this->fill_query_vars($this->query_vars); 
  320.  
  321. function get($query_var) { 
  322. return isset($this->query_vars[$query_var]) ? $this->query_vars[$query_var] : null; 
  323.  
  324. function set($query_var, $value) { 
  325. $this->query_vars[$query_var] = $value; 
  326.  
  327. function generate_topic_sql( $_part_of_post_query = false ) { 
  328. global $bbdb; 
  329.  
  330. $q =& $this->query_vars; 
  331. $distinct = ''; 
  332. $sql_calc_found_rows = 'found_rows' === $q['count'] ? 'SQL_CALC_FOUND_ROWS' : ''; // unfiltered 
  333. $fields = 't.*'; 
  334. $index_hint = ''; 
  335. $join = ''; 
  336. $where = ''; 
  337. $group_by = ''; 
  338. $having = ''; 
  339. $order_by = ''; 
  340.  
  341. $post_where = ''; 
  342. $post_queries = array('post_author_id', 'post_author', 'posted', 'post_status', 'position', 'post_text', 'poster_ip'); 
  343.  
  344. if ( !$_part_of_post_query && ( $q['search'] || array_diff($post_queries, $this->not_set) ) ) : 
  345. $join .= " JOIN $bbdb->posts as p ON ( t.topic_id = p.topic_id )"; 
  346. $post_where = $this->generate_post_sql( true ); 
  347. if ( $q['search'] ) { 
  348. $post_where .= ' AND ( '; 
  349. $post_where .= $this->generate_topic_title_sql( $q['search'] ); 
  350. $post_where .= ' OR '; 
  351. $post_where .= $this->generate_post_text_sql( $q['search'] ); 
  352. $post_where .= ' )'; 
  353.  
  354. $group_by = 't.topic_id'; 
  355.  
  356. $fields .= ", MIN(p.post_id) as post_id"; 
  357.  
  358. if ( $bbdb->has_cap( 'GROUP_CONCAT', $bbdb->posts ) ) 
  359. $fields .= ", GROUP_CONCAT(p.post_text SEPARATOR ' ') AS post_text"; 
  360. else 
  361. $fields .= ", p.post_text"; 
  362.  
  363. if ( $this->match_query ) { 
  364. $fields .= ", AVG($this->match_query) AS search_score"; 
  365. if ( !$q['order_by'] ) 
  366. $q['order_by'] = 'search_score'; 
  367. } elseif ( $q['search'] || $q['post_text'] ) { 
  368. $fields .= ", 0 AS search_score"; 
  369. endif; 
  370.  
  371. if ( !$_part_of_post_query ) : 
  372. if ( $q['post_id'] ) : 
  373. $post_topics = $post_topics_no = array(); 
  374. $op = substr($q['post_id'], 0, 1); 
  375. if ( in_array($op, array('>', '<')) ) : 
  376. $post_topics = $bbdb->get_col( "SELECT DISTINCT topic_id FROM $bbdb->posts WHERE post_id $op '" . (int) substr($q['post_id'], 1) . "'" ); 
  377. else : 
  378. $posts = explode(', ', $q['post_id']); 
  379. $get_posts = array(); 
  380. foreach ( $posts as $post_id ) { 
  381. $post_id = (int) $post_id; 
  382. $_post_id = abs($post_id); 
  383. $get_posts[] = $_post_id; 
  384. bb_cache_posts( $get_posts ); 
  385.  
  386. foreach ( $posts as $post_id ) : 
  387. $post = bb_get_post( abs($post_id) ); 
  388. if ( $post_id < 0 ) 
  389. $post_topics_no[] = $post->topic_id; 
  390. else 
  391. $post_topics[] = $post->topic_id; 
  392. endforeach; 
  393. endif; 
  394. if ( $post_topics ) 
  395. $where .= " AND t.topic_id IN (" . join(', ', $post_topics) . ")"; 
  396. if ( $post_topics_no ) 
  397. $where .= " AND t.topic_id NOT IN (" . join(', ', $post_topics_no) . ")"; 
  398. endif; 
  399.  
  400. if ( $q['topic_id'] ) : 
  401. $where .= $this->parse_value( 't.topic_id', $q['topic_id'] ); 
  402. elseif ( $q['topic'] ) : 
  403. $q['topic'] = bb_slug_sanitize( $q['topic'] ); 
  404. $where .= " AND t.topic_slug = '$q[topic]'"; 
  405. endif; 
  406.  
  407. if ( $q['forum_id'] ) : 
  408. $where .= $this->parse_value( 't.forum_id', $q['forum_id'] ); 
  409. elseif ( $q['forum'] ) : 
  410. if ( !$q['forum_id'] = bb_get_id_from_slug( 'forum', $q['forum'] ) ) 
  411. $this->error( 'query_var:forum', 'No forum by that name' ); 
  412. $where .= " AND t.forum_id = $q[forum_id]"; 
  413. endif; 
  414.  
  415. if ( $q['tag'] && !is_int($q['tag_id']) ) 
  416. $q['tag_id'] = (int) bb_get_tag_id( $q['tag'] ); 
  417.  
  418. if ( is_numeric($q['tag_id']) ) : 
  419. $join .= " JOIN `$bbdb->term_relationships` AS tr ON ( t.`topic_id` = tr.`object_id` AND tr.`term_taxonomy_id` = $q[tag_id] )"; 
  420. endif; 
  421.  
  422. if ( is_numeric($q['favorites']) && $f_user = bb_get_user( $q['favorites'] ) ) 
  423. $where .= $this->parse_value( 't.topic_id', $f_user->favorites ); 
  424. endif; // !_part_of_post_query 
  425.  
  426. if ( $q['topic_title'] ) 
  427. $where .= ' AND ' . $this->generate_topic_title_sql( $q['topic_title'] ); 
  428.  
  429. if ( $q['started'] ) 
  430. $where .= $this->date( 't.topic_start_time', $q['started'] ); 
  431.  
  432. if ( $q['updated'] ) 
  433. $where .= $this->date( 't.topic_time', $q['updated'] ); 
  434.  
  435. if ( $q['topic_author_id'] ) : 
  436. $where .= $this->parse_value( 't.topic_poster', $q['topic_author_id'] ); 
  437. elseif ( $q['topic_author'] ) : 
  438. $user = bb_get_user( $q['topic_author'], array( 'by' => 'login' ) ); 
  439. if ( !$q['topic_author_id'] = (int) $user->ID ) 
  440. $this->error( 'query_var:user', 'No user by that name' ); 
  441. $where .= " AND t.topic_poster = $q[topic_author_id]"; 
  442. endif; 
  443.  
  444. if ( !$q['topic_status'] ) : 
  445. $where .= " AND t.topic_status = '0'"; 
  446. elseif ( false === strpos($q['topic_status'], 'all') ) : 
  447. $stati = array( 'normal' => 0, 'deleted' => 1 ); 
  448. $q['topic_status'] = str_replace(array_keys($stati), array_values($stati), $q['topic_status']); 
  449. $where .= $this->parse_value( 't.topic_status', $q['topic_status'] ); 
  450. endif; 
  451.  
  452. if ( false !== $q['open'] && false === strpos($q['open'], 'all') ) : 
  453. $stati = array( 'no' => 0, 'closed' => 0, 'yes' => 1, 'open' => 1 ); 
  454. $q['open'] = str_replace(array_keys($stati), array_values($stati), $q['open']); 
  455. $where .= $this->parse_value( 't.topic_open', $q['open'] ); 
  456. endif; 
  457.  
  458. if ( false !== $q['sticky'] && false === strpos($q['sticky'], 'all') ) : 
  459. $stickies = array( 'no' => 0, 'normal' => 0, 'forum' => 1, 'super' => 2, 'front' => 2, 'sticky' => '-0' ); 
  460. $q['sticky'] = str_replace(array_keys($stickies), array_values($stickies), $q['sticky']); 
  461. $where .= $this->parse_value( 't.topic_sticky', $q['sticky'] ); 
  462. endif; 
  463.  
  464. if ( false !== $q['post_count'] ) 
  465. $where .= $this->parse_value( 't.topic_posts', $q['post_count'] ); 
  466.  
  467. if ( false !== $q['tag_count'] ) 
  468. $where .= $this->parse_value( 't.tag_count', $q['tag_count'] ); 
  469.  
  470. if ( $q['meta_key'] && $q['meta_key'] = preg_replace('|[^a-z0-9_-]|i', '', $q['meta_key']) ) : 
  471. if ( '-' == substr($q['meta_key'], 0, 1) ) : 
  472. $join .= " LEFT JOIN $bbdb->meta AS tm ON ( tm.object_type = 'bb_topic' AND t.topic_id = tm.object_id AND tm.meta_key = '" . substr( $q['meta_key'], 1 ) . "' )"; 
  473. $where .= " AND tm.meta_key IS NULL"; 
  474. else : 
  475. $join .= " JOIN $bbdb->meta AS tm ON ( tm.object_type = 'bb_topic' AND t.topic_id = tm.object_id AND tm.meta_key = '$q[meta_key]' )"; 
  476.  
  477. if ( $q['meta_value'] ) : 
  478. $q['meta_value'] = maybe_serialize( $q['meta_value'] ); 
  479. if ( strpos( $q['meta_value'], 'NULL' ) !== false ) 
  480. $join = ' LEFT' . $join; 
  481. $where .= $this->parse_value( 'tm.meta_value', $q['meta_value'] ); 
  482. endif; 
  483. endif; 
  484. endif; 
  485.  
  486. // Just getting topic part for inclusion in post query 
  487. if ( $_part_of_post_query ) 
  488. return $where; 
  489.  
  490. $where .= $post_where; 
  491.  
  492. if ( $where ) // Get rid of initial " AND " (this is pre-filters) 
  493. $where = substr($where, 5); 
  494.  
  495. if ( $q['index_hint'] ) 
  496. $index_hint = $q['index_hint']; 
  497.  
  498. if ( $q['order_by'] ) 
  499. $order_by = $q['order_by']; 
  500. else 
  501. $order_by = 't.topic_time'; 
  502.  
  503. $bits = compact( array('distinct', 'sql_calc_found_rows', 'fields', 'index_hint', 'join', 'where', 'group_by', 'having', 'order_by') ); 
  504. $this->request = $this->_filter_sql( $bits, "$bbdb->topics AS t" ); 
  505. return $this->request; 
  506.  
  507. function generate_post_sql( $_part_of_topic_query = false ) { 
  508. global $bbdb; 
  509.  
  510. $q =& $this->query_vars; 
  511. $distinct = ''; 
  512. $sql_calc_found_rows = 'found_rows' === $q['count'] ? 'SQL_CALC_FOUND_ROWS' : ''; // unfiltered 
  513. $fields = 'p.*'; 
  514. $index_hint = ''; 
  515. $join = ''; 
  516. $where = ''; 
  517. $group_by = ''; 
  518. $having = ''; 
  519. $order_by = ''; 
  520.  
  521. $topic_where = ''; 
  522. $topic_queries = array( 'topic_author_id', 'topic_author', 'topic_status', 'post_count', 'tag_count', 'started', 'updated', 'open', 'sticky', 'meta_key', 'meta_value', 'topic_title' ); 
  523. if ( !$_part_of_topic_query && array_diff($topic_queries, $this->not_set) ) : 
  524. $join .= " JOIN $bbdb->topics as t ON ( t.topic_id = p.topic_id )"; 
  525. $topic_where = $this->generate_topic_sql( true ); 
  526. endif; 
  527.  
  528. if ( !$_part_of_topic_query ) : 
  529. if ( $q['post_id'] ) 
  530. $where .= $this->parse_value( 'p.post_id', $q['post_id'] ); 
  531.  
  532. if ( $q['topic_id'] ) : 
  533. $where .= $this->parse_value( 'p.topic_id', $q['topic_id'] ); 
  534. elseif ( $q['topic'] ) : 
  535. if ( !$q['topic_id'] = bb_get_id_from_slug( 'topic', $q['topic'] ) ) 
  536. $this->error( 'query_var:topic', 'No topic by that name' ); 
  537. $where .= " AND p.topic_id = " . $q['topic_id']; 
  538. endif; 
  539.  
  540. if ( $q['forum_id'] ) : 
  541. $where .= $this->parse_value( 'p.forum_id', $q['forum_id'] ); 
  542. elseif ( $q['forum'] ) : 
  543. if ( !$q['forum_id'] = bb_get_id_from_slug( 'forum', $q['forum'] ) ) 
  544. $this->error( 'query_var:forum', 'No forum by that name' ); 
  545. $where .= " AND p.forum_id = " . $q['forum_id']; 
  546. endif; 
  547.  
  548. if ( $q['tag'] && !is_int($q['tag_id']) ) 
  549. $q['tag_id'] = (int) bb_get_tag_id( $q['tag'] ); 
  550.  
  551. if ( is_numeric($q['tag_id']) ) : 
  552. $join .= " JOIN `$bbdb->term_relationships` AS tr ON ( p.`topic_id` = tr.`object_id` AND tr.`term_taxonomy_id` = $q[tag_id] )"; 
  553. endif; 
  554.  
  555. if ( is_numeric($q['favorites']) && $f_user = bb_get_user( $q['favorites'] ) ) 
  556. $where .= $this->parse_value( 'p.topic_id', $f_user->favorites ); 
  557. endif; // !_part_of_topic_query 
  558.  
  559. if ( $q['post_text'] ) : 
  560. $where .= ' AND ' . $this->generate_post_text_sql( $q['post_text'] ); 
  561. if ( $this->match_query ) { 
  562. $fields .= ", $this->match_query AS search_score"; 
  563. if ( !$q['order_by'] ) 
  564. $q['order_by'] = 'search_score'; 
  565. } else { 
  566. $fields .= ', 0 AS search_score'; 
  567. endif; 
  568.  
  569. if ( $q['posted'] ) 
  570. $where .= $this->date( 'p.post_time', $q['posted'] ); 
  571.  
  572. if ( $q['post_author_id'] ) : 
  573. $where .= $this->parse_value( 'p.poster_id', $q['post_author_id'] ); 
  574. elseif ( $q['post_author'] ) : 
  575. $user = bb_get_user( $q['post_author'], array( 'by' => 'login' ) ); 
  576. if ( !$q['post_author_id'] = (int) $user->ID ) 
  577. $this->error( 'query_var:user', 'No user by that name' ); 
  578. $where .= " AND p.poster_id = $q[post_author_id]"; 
  579. endif; 
  580.  
  581. if ( !$q['post_status'] ) : 
  582. $where .= " AND p.post_status = '0'"; 
  583. elseif ( false === strpos($q['post_status'], 'all') ) : 
  584. $stati = array( 'normal' => 0, 'deleted' => 1 ); 
  585. $q['post_status'] = str_replace(array_keys($stati), array_values($stati), $q['post_status']); 
  586. $where .= $this->parse_value( 'p.post_status', $q['post_status'] ); 
  587. endif; 
  588.  
  589. if ( false !== $q['position'] ) 
  590. $where .= $this->parse_value( 'p.post_position', $q['position'] ); 
  591.  
  592. if ( false !== $q['poster_ip'] ) 
  593. $where .= " AND poster_ip = '" . $q['poster_ip'] . "'"; 
  594.  
  595. // Just getting post part for inclusion in topic query 
  596. if ( $_part_of_topic_query ) 
  597. return $where; 
  598.  
  599. $where .= $topic_where; 
  600.  
  601. if ( $where ) // Get rid of initial " AND " (this is pre-filters) 
  602. $where = substr($where, 5); 
  603.  
  604. if ( $q['index_hint'] ) 
  605. $index_hint = $q['index_hint']; 
  606.  
  607. if ( $q['order_by'] ) 
  608. $order_by = $q['order_by']; 
  609. else 
  610. $order_by = 'p.post_time'; 
  611.  
  612. $bits = compact( array('distinct', 'sql_calc_found_rows', 'fields', 'index_hint', 'join', 'where', 'group_by', 'having', 'order_by') ); 
  613. $this->request = $this->_filter_sql( $bits, "$bbdb->posts AS p" ); 
  614.  
  615. return $this->request; 
  616.  
  617. function generate_topic_title_sql( $string ) { 
  618. global $bbdb; 
  619. $string = trim($string); 
  620.  
  621. if ( !preg_match_all('/".*?("|$)|((?<=[\s", +])|^)[^\s", +]+/', $string, $matches) ) { 
  622. $string = $bbdb->escape($string); 
  623. return "(t.topic_title LIKE '%$string%')"; 
  624.  
  625. $where = ''; 
  626.  
  627. foreach ( $matches[0] as $match ) { 
  628. $term = trim($match, "\"\n\r "); 
  629. $term = $bbdb->escape($term); 
  630. $where .= " AND t.topic_title LIKE '%$term%'"; 
  631.  
  632. if ( count($matches[0]) > 1 && $string != $matches[0][0] ) { 
  633. $string = $bbdb->escape($string); 
  634. $where .= " OR t.topic_title LIKE '%$string%'"; 
  635.  
  636. return '(' . substr($where, 5) . ')'; 
  637.  
  638. function generate_post_text_sql( $string ) { 
  639. global $bbdb; 
  640. $string = trim($string); 
  641. $_string = $bbdb->escape( $string ); 
  642. if ( strlen($string) < 5 ) 
  643. return "p.post_text LIKE '%$_string%'"; 
  644.  
  645. return $this->match_query = "MATCH(p.post_text) AGAINST('$_string')"; 
  646.  
  647. function _filter_sql( $bits, $from ) { 
  648. global $bbdb; 
  649.  
  650. $q =& $this->query_vars; 
  651.  
  652. // MySQL 5.1 allows multiple index hints per query - earlier versions only get the first hint 
  653. if ( $bits['index_hint'] ) { 
  654. if ( !is_array( $bits['index_hint'] ) ) { 
  655. $bits['index_hint'] = array( (string) $bits['index_hint'] ); 
  656. if ( $bbdb->has_cap( 'index_hint_for_any' ) ) { 
  657. // 5.1 <= MySQL 
  658. $_regex = '/\s*(USE|IGNORE|FORCE)\s+(INDEX|KEY)\s+(FOR\s+(JOIN|ORDER\s+BY|GROUP\s+BY)\s+)?\(\s*`?[a-z0-9_]+`?(\s*, \s*`?[a-z0-9_]+`?)*\s*\)\s*/i'; 
  659. } elseif ( $bbdb->has_cap( 'index_hint_for_join' ) ) { 
  660. // 5.0 <= MySQL < 5.1 
  661. $_regex = '/\s*(USE|IGNORE|FORCE)\s+(INDEX|KEY)\s+(FOR\s+JOIN\s+)?\(\s*`?[a-z0-9_]+`?(\s*, \s*`?[a-z0-9_]+`?)*\s*\)\s*/i'; 
  662. } else { 
  663. // MySQL < 5.0 
  664. $_regex = '/\s*(USE|IGNORE|FORCE)\s+(INDEX|KEY)\s+\(\s*`?[a-z0-9_]+`?(\s*, \s*`?[a-z0-9_]+`?)*\s*\)\s*/i'; 
  665. $_index_hint = array(); 
  666. foreach ( $bits['index_hint'] as $_hint ) { 
  667. if ( preg_match( $_regex, $_hint ) ) { 
  668. $_index_hint[] = trim( $_hint ); 
  669. unset( $_regex, $_hint ); 
  670. if ( $bbdb->has_cap( 'index_hint_lists' ) ) { 
  671. // 5.1 <= MySQL 
  672. $bits['index_hint'] = join( ' ', $_index_hint ); 
  673. } else { 
  674. // MySQL < 5.1 
  675. $bits['index_hint'] = isset( $_index_hint[0] ) ? $_index_hint[0] : ''; 
  676. unset( $_index_hint ); 
  677.  
  678. $q['order'] = strtoupper($q['order']); 
  679. if ( $q['order'] && in_array($q['order'], array('ASC', 'DESC')) ) 
  680. $bits['order_by'] .= " $q[order]"; 
  681. else 
  682. $bits['order_by'] .= " DESC"; 
  683.  
  684. $bits['limit'] = ''; 
  685.  
  686. // When offset and number are provided, skip per_page and limit checks 
  687. if ( !empty( $q['offset'] ) && !empty( $q['number'] ) ) { 
  688. $bits['limit'] .= $q['offset'] . ", " . $q['number']; 
  689.  
  690. // Else proceed as normal 
  691. } else { 
  692. if ( !$q['per_page'] ) { 
  693. $q['per_page'] = (int) bb_get_option( 'page_topics' ); 
  694.  
  695. if ( $q['per_page'] > 0 ) { 
  696. if ( $q['page'] > 1 ) { 
  697. $bits['limit'] .= $q['per_page'] * ( $q['page'] - 1 ) . ", "; 
  698. $bits['limit'] .= $q['per_page']; 
  699.  
  700. $name = "get_{$this->type}s_"; 
  701.  
  702. // Unfiltered 
  703. $sql_calc_found_rows = $bits['sql_calc_found_rows']; 
  704. unset($bits['sql_calc_found_rows']); 
  705.  
  706. foreach ( $bits as $bit => $value ) { 
  707. if ( $this->query_id ) 
  708. $value = apply_filters( "{$this->query_id}_$bit", $value ); 
  709. $$bit = apply_filters( "$name$bit", $value ); 
  710.  
  711. if ( $where ) 
  712. $where = "WHERE $where"; 
  713. if ( $group_by ) 
  714. $group_by = "GROUP BY $group_by"; 
  715. if ( $having ) 
  716. $having = "HAVING $having"; 
  717. if ( $order_by ) 
  718. $order_by = "ORDER BY $order_by"; 
  719. if ( $limit ) 
  720. $limit = "LIMIT $limit"; 
  721.  
  722. return "SELECT $distinct $sql_calc_found_rows $fields FROM $from $index_hint $join $where $group_by $having $order_by $limit"; 
  723.  
  724. function parse_value( $field, $value = '' ) { 
  725. if ( !$value && !is_numeric($value) ) 
  726. return ''; 
  727.  
  728. global $bbdb; 
  729.  
  730. $op = substr($value, 0, 1); 
  731.  
  732. // #, =whatever, <#, >#. Cannot do < and > at same time 
  733. if ( in_array($op, array('<', '=', '>')) ) : 
  734. $value = substr($value, 1); 
  735. $value = is_numeric($value) ? (float) $value : $bbdb->escape( $value ); 
  736. return " AND $field $op '$value'"; 
  737. elseif ( false === strpos($value, ', ') && 'NULL' !== $value && '-NULL' !== $value ) : 
  738. $value = is_numeric($value) ? (float) $value : $bbdb->escape( $value ); 
  739. return '-' == $op ? " AND $field != '" . substr($value, 1) . "'" : " AND $field = '$value'"; 
  740. endif; 
  741.  
  742. $y = $n = array(); 
  743. foreach ( explode(', ', $value) as $v ) { 
  744. $v = is_numeric($v) ? (int) $v : $bbdb->escape( $v ); 
  745. if ( '-' == substr($v, 0, 1) ) 
  746. if ( $v === '-NULL' ) 
  747. $not_null_flag = true; 
  748. else 
  749. $n[] = substr($v, 1); 
  750. else 
  751. if ( $v === 'NULL' ) 
  752. $null_flag = true; 
  753. else 
  754. $y[] = $v; 
  755.  
  756. $r = ''; 
  757. if ( $y ) { 
  758. $r .= " AND "; 
  759. if ( $null_flag ) 
  760. $r .= "("; 
  761. $r .= "$field IN ('" . join("', '", $y) . "')"; 
  762. if ( $null_flag ) 
  763. $r .= " OR $field IS NULL)"; 
  764. } elseif ( $null_flag ) { 
  765. $r .= " AND $field IS NULL"; 
  766.  
  767. if ( $n ) { 
  768. $r .= " AND "; 
  769. if ( $not_null_flag ) 
  770. $r .= "("; 
  771. $r .= "$field NOT IN ('" . join("', '", $n) . "')"; 
  772. if ( $not_null_flag ) 
  773. $r .= " AND $field IS NOT NULL)"; 
  774. } elseif ( $not_null_flag ) { 
  775. $r .= " AND $field IS NOT NULL"; 
  776.  
  777. return $r; 
  778.  
  779. function date( $field, $date ) { 
  780. if ( !$date && !is_int($date) ) 
  781. return ''; 
  782.  
  783. if ( $is_range = false !== strpos( $date, '--' ) ) 
  784. $dates = explode( '--', $date, 2 ); 
  785. else 
  786. $dates = array( $date ); 
  787.  
  788. $op = false; 
  789. $r = ''; 
  790. foreach ( $dates as $date ) { 
  791. if ( $is_range ) { 
  792. $op = $op ? '<' : '>'; 
  793. $date = (int) substr($date, 0, 14); 
  794. } else { 
  795. $op = substr($date, 0, 1); 
  796. if ( !in_array($op, array('>', '<')) ) 
  797. break; 
  798. $date = (int) substr($date, 1, 14); 
  799. if ( strlen($date) < 14 ) 
  800. $date .= str_repeat('0', 14 - strlen($date)); 
  801. $r .= " AND $field $op $date"; 
  802. if ( $r ) 
  803. return $r; 
  804.  
  805. $date = (int) $date; 
  806. $r = " AND YEAR($field) = " . substr($date, 0, 4); 
  807. if ( strlen($date) > 5 ) 
  808. $r .= " AND MONTH($field) = " . substr($date, 4, 2); 
  809. if ( strlen($date) > 7 ) 
  810. $r .= " AND DAYOFMONTH($field) = " . substr($date, 6, 2); 
  811. if ( strlen($date) > 9 ) 
  812. $r .= " AND HOUR($field) = " . substr($date, 8, 2); 
  813. if ( strlen($date) > 11 ) 
  814. $r .= " AND MINUTE($field) = " . substr($date, 10, 2); 
  815. if ( strlen($date) > 13 ) 
  816. $r .= " AND SECOND($field) = " . substr($date, 12, 2); 
  817. return $r; 
  818.  
  819. function error( $code, $message ) { 
  820. if ( is_wp_error($this->errors) ) 
  821. $this->errors->add( $code, $message ); 
  822. else 
  823. $this->errors = new WP_Error( $code, $message );