admin
14 years ago
screen-options
15 years ago
activation.php
14 years ago
any-post.php
14 years ago
checkers.php
14 years ago
config-manager.php
14 years ago
containers.php
14 years ago
extra-strings.php
14 years ago
instances.php
14 years ago
link-query.php
14 years ago
links.php
14 years ago
logger.php
15 years ago
module-base.php
15 years ago
module-manager.php
14 years ago
modules.php
14 years ago
parsers.php
14 years ago
screen-meta-links.php
14 years ago
survey.php
14 years ago
utility-class.php
14 years ago
wp-mutex.php
14 years ago
link-query.php
796 lines
| 1 | <?php |
| 2 | |
| 3 | /** |
| 4 | * Class for querying, sorting and filtering links. |
| 5 | * Used as a singleton. |
| 6 | * |
| 7 | * @package Broken Link Checker |
| 8 | * @access public |
| 9 | */ |
| 10 | class blcLinkQuery { |
| 11 | |
| 12 | var $native_filters; |
| 13 | var $search_filter; |
| 14 | var $custom_filters = array(); |
| 15 | |
| 16 | var $valid_url_params = array(); |
| 17 | |
| 18 | function __construct(){ |
| 19 | //Init. the available native filters. |
| 20 | $this->native_filters = array( |
| 21 | 'broken' => array( |
| 22 | 'params' => array( |
| 23 | 'where_expr' => '( broken = 1 )', |
| 24 | ), |
| 25 | 'name' => __('Broken', 'broken-link-checker'), |
| 26 | 'heading' => __('Broken Links', 'broken-link-checker'), |
| 27 | 'heading_zero' => __('No broken links found', 'broken-link-checker'), |
| 28 | 'native' => true, |
| 29 | ), |
| 30 | 'redirects' => array( |
| 31 | 'params' => array( |
| 32 | 'where_expr' => '( redirect_count > 0 )', |
| 33 | ), |
| 34 | 'name' => __('Redirects', 'broken-link-checker'), |
| 35 | 'heading' => __('Redirected Links', 'broken-link-checker'), |
| 36 | 'heading_zero' => __('No redirects found', 'broken-link-checker'), |
| 37 | 'native' => true, |
| 38 | ), |
| 39 | |
| 40 | 'all' => array( |
| 41 | 'params' => array( |
| 42 | 'where_expr' => '1', |
| 43 | ), |
| 44 | 'name' => __('All', 'broken-link-checker'), |
| 45 | 'heading' => __('Detected Links', 'broken-link-checker'), |
| 46 | 'heading_zero' => __('No links found (yet)', 'broken-link-checker'), |
| 47 | 'native' => true, |
| 48 | ), |
| 49 | ); |
| 50 | |
| 51 | //Create the special "search" filter |
| 52 | $this->search_filter = array( |
| 53 | 'name' => __('Search', 'broken-link-checker'), |
| 54 | 'heading' => __('Search Results', 'broken-link-checker'), |
| 55 | 'heading_zero' => __('No links found for your query', 'broken-link-checker'), |
| 56 | 'params' => array(), |
| 57 | 'use_url_params' => true, |
| 58 | 'hidden' => true, |
| 59 | ); |
| 60 | |
| 61 | //These search arguments may be passed via the URL if the filter's 'use_url_params' field is set to True. |
| 62 | //They map to the fields of the search form on the Tools -> Broken Links page. Only these arguments |
| 63 | //can be used in user-defined filters. |
| 64 | $this->valid_url_params = array( |
| 65 | 's_link_text', |
| 66 | 's_link_url', |
| 67 | 's_parser_type', |
| 68 | 's_container_type', |
| 69 | 's_link_type', |
| 70 | 's_http_code', |
| 71 | 's_filter', |
| 72 | ); |
| 73 | } |
| 74 | |
| 75 | static function getInstance(){ |
| 76 | static $instance = null; |
| 77 | if ( is_null($instance) ){ |
| 78 | $instance = new blcLinkQuery; |
| 79 | } |
| 80 | return $instance; |
| 81 | } |
| 82 | |
| 83 | /** |
| 84 | * Load and return the list of user-defined link filters. |
| 85 | * |
| 86 | * @return array An array of custom filter definitions. If there are no custom filters defined returns an empty array. |
| 87 | */ |
| 88 | function load_custom_filters(){ |
| 89 | global $wpdb; /** @var wpdb $wpdb */ |
| 90 | |
| 91 | $filter_data = $wpdb->get_results("SELECT * FROM {$wpdb->prefix}blc_filters ORDER BY name ASC", ARRAY_A); |
| 92 | $filters = array(); |
| 93 | |
| 94 | if ( !empty($filter_data) ) { |
| 95 | foreach($filter_data as $data){ |
| 96 | wp_parse_str($data['params'], $params); |
| 97 | |
| 98 | $filters[ 'f'.$data['id'] ] = array( |
| 99 | 'name' => $data['name'], |
| 100 | 'params' => $params, |
| 101 | 'heading' => ucwords($data['name']), |
| 102 | 'heading_zero' => __('No links found for your query', 'broken-link-checker'), |
| 103 | 'custom' => true, |
| 104 | ); |
| 105 | } |
| 106 | } |
| 107 | |
| 108 | $this->custom_filters = $filters; |
| 109 | |
| 110 | return $filters; |
| 111 | } |
| 112 | |
| 113 | /** |
| 114 | * Add a custom link filter. |
| 115 | * |
| 116 | * @param string $name Filter name. |
| 117 | * @param string|array $params Filter params. Either as a query string, or an array. |
| 118 | * @return string|bool The ID of the newly added filter, or False. |
| 119 | */ |
| 120 | function create_custom_filter($name, $params){ |
| 121 | global $wpdb; /** @var wpdb $wpdb */ |
| 122 | |
| 123 | if ( is_array($params) ){ |
| 124 | $params = http_build_query($params, null, '&'); |
| 125 | } |
| 126 | |
| 127 | //Save the new filter |
| 128 | $q = $wpdb->prepare( |
| 129 | "INSERT INTO {$wpdb->prefix}blc_filters(name, params) VALUES (%s, %s)", |
| 130 | $name, $params |
| 131 | ); |
| 132 | |
| 133 | if ( $wpdb->query($q) !== false ){ |
| 134 | $filter_id = 'f'.$wpdb->insert_id; |
| 135 | return $filter_id; |
| 136 | } else { |
| 137 | return false; |
| 138 | } |
| 139 | } |
| 140 | |
| 141 | /** |
| 142 | * Delete a custom filter |
| 143 | * |
| 144 | * @param string $filter_id |
| 145 | * @return bool True on success, False if a database error occured. |
| 146 | */ |
| 147 | function delete_custom_filter($filter_id){ |
| 148 | global $wpdb; /** @var wpdb $wpdb */ |
| 149 | |
| 150 | //Remove the "f" character from the filter ID to get its database key |
| 151 | $filter_id = intval(ltrim($_POST['filter_id'], 'f')); |
| 152 | |
| 153 | //Try to delete the filter |
| 154 | $q = $wpdb->prepare("DELETE FROM {$wpdb->prefix}blc_filters WHERE id = %d", $filter_id); |
| 155 | if ( $wpdb->query($q) !== false ){ |
| 156 | return true; |
| 157 | } else { |
| 158 | return false; |
| 159 | } |
| 160 | } |
| 161 | |
| 162 | function get_filters(){ |
| 163 | $filters = array_merge($this->native_filters, $this->custom_filters); |
| 164 | $filters['search'] = $this->search_filter; |
| 165 | return $filters; |
| 166 | } |
| 167 | |
| 168 | /** |
| 169 | * Get a link search filter by filter ID. |
| 170 | * |
| 171 | * @param string $filter_id |
| 172 | * @return array|null |
| 173 | */ |
| 174 | function get_filter($filter_id){ |
| 175 | $filters = $this->get_filters(); |
| 176 | if ( isset($filters[$filter_id]) ){ |
| 177 | return $filters[$filter_id]; |
| 178 | } else { |
| 179 | return null; |
| 180 | } |
| 181 | } |
| 182 | |
| 183 | /** |
| 184 | * Get link search parameters from the specified filter. |
| 185 | * |
| 186 | * @param array $filter |
| 187 | * @return array An array of parameters suitable for use with blcLinkQuery::get_links() |
| 188 | */ |
| 189 | function get_search_params( $filter = null ){ |
| 190 | //If present, the filter's parameters may be saved either as an array or a string. |
| 191 | $params = array(); |
| 192 | if ( !empty($filter) && !empty($filter['params']) ){ |
| 193 | $params = $filter['params']; |
| 194 | if ( is_string( $params ) ){ |
| 195 | wp_parse_str($params, $params); |
| 196 | } |
| 197 | } |
| 198 | |
| 199 | //Merge in the parameters from the current request, if required |
| 200 | if ( isset($filter['use_url_params']) && $filter['use_url_params'] ){ |
| 201 | $params = array_merge($params, $this->get_url_search_params()); |
| 202 | } |
| 203 | |
| 204 | return $params; |
| 205 | } |
| 206 | |
| 207 | /** |
| 208 | * Extract search query parameters from the current URL |
| 209 | * |
| 210 | * @return array |
| 211 | */ |
| 212 | function get_url_search_params(){ |
| 213 | $url_params = array(); |
| 214 | foreach ($_GET as $param => $value){ |
| 215 | if ( in_array($param, $this->valid_url_params) ){ |
| 216 | $url_params[$param] = $value; |
| 217 | } |
| 218 | } |
| 219 | return $url_params; |
| 220 | } |
| 221 | |
| 222 | |
| 223 | |
| 224 | /** |
| 225 | * A helper method for parsing a list of search criteria and generating the parts of the SQL query. |
| 226 | * |
| 227 | * @see blcLinkQuery::get_links() |
| 228 | * |
| 229 | * @param array $params An array of search criteria. |
| 230 | * @return array 'where_exprs' - an array of search expressions, 'join_instances' - whether joining the instance table is required. |
| 231 | */ |
| 232 | function compile_search_params($params){ |
| 233 | global $wpdb; /** @var wpdb $wpdb */ |
| 234 | |
| 235 | //Track whether we'll need to left-join the instance table to run the query. |
| 236 | $join_instances = false; |
| 237 | |
| 238 | //Generate the individual clauses of the WHERE expression and store them in an array. |
| 239 | $pieces = array(); |
| 240 | |
| 241 | //Convert parser and container type lists to arrays of valid values |
| 242 | $s_parser_type = array(); |
| 243 | if ( !empty($params['s_parser_type']) ){ |
| 244 | $s_parser_type = $params['s_parser_type']; |
| 245 | if ( is_string($s_parser_type) ){ |
| 246 | $s_parser_type = preg_split('/[,\s]+/', $s_parser_type); |
| 247 | } |
| 248 | } |
| 249 | |
| 250 | $s_container_type = array(); |
| 251 | if ( !empty($params['s_container_type']) ){ |
| 252 | $s_container_type = $params['s_container_type']; |
| 253 | if ( is_string($s_container_type) ){ |
| 254 | $s_container_type = preg_split('/[,\s]+/', $s_container_type); |
| 255 | } |
| 256 | } |
| 257 | |
| 258 | //Don't include links with instances that reference invalid (not currently loaded) |
| 259 | //containers and parsers (unless specifically told to also include invalid links). |
| 260 | if ( empty($params['include_invalid']) ){ |
| 261 | $join_instances = true; |
| 262 | |
| 263 | $module_manager = blcModuleManager::getInstance(); |
| 264 | $loaded_containers = array_keys($module_manager->get_active_by_category('container')); |
| 265 | $loaded_parsers = array_keys($module_manager->get_active_by_category('parser')); |
| 266 | |
| 267 | if ( empty($s_parser_type) ){ |
| 268 | $s_parser_type = $loaded_parsers; |
| 269 | } else { |
| 270 | $s_parser_type = array_intersect($s_parser_type, $loaded_parsers); |
| 271 | } |
| 272 | |
| 273 | if ( empty($s_container_type) ){ |
| 274 | $s_container_type = $loaded_containers; |
| 275 | } else { |
| 276 | $s_container_type = array_intersect($s_container_type, $loaded_containers); |
| 277 | } |
| 278 | } |
| 279 | |
| 280 | //Parser type should match the parser_type column in the instance table. |
| 281 | if ( !empty($s_parser_type) ){ |
| 282 | $s_parser_type = array_map('trim', array_unique($s_parser_type)); |
| 283 | $s_parser_type = array_map(array(&$wpdb, 'escape'), $s_parser_type); |
| 284 | |
| 285 | if ( count($s_parser_type) == 1 ){ |
| 286 | $pieces[] = sprintf("instances.parser_type = '%s'", reset($s_parser_type)); |
| 287 | } else { |
| 288 | $pieces[] = "instances.parser_type IN ('" . implode("', '", $s_parser_type) . "')"; |
| 289 | } |
| 290 | |
| 291 | $join_instances = true; |
| 292 | } |
| 293 | |
| 294 | //Container type should match the container_type column in the instance table. |
| 295 | if ( !empty($s_container_type) ){ |
| 296 | //Sanitize for use in SQL |
| 297 | $s_container_type = array_map('trim', array_unique($s_container_type)); |
| 298 | $s_container_type = array_map(array(&$wpdb, 'escape'), $s_container_type); |
| 299 | |
| 300 | if ( count($s_container_type) == 1 ){ |
| 301 | $pieces[] = sprintf("instances.container_type = '%s'", reset($s_container_type)); |
| 302 | } else { |
| 303 | $pieces[] = "instances.container_type IN ('" . implode("', '", $s_container_type) . "')"; |
| 304 | } |
| 305 | |
| 306 | $join_instances = true; |
| 307 | } |
| 308 | |
| 309 | //A part of the WHERE expression can be specified explicitly |
| 310 | if ( !empty($params['where_expr']) ){ |
| 311 | $pieces[] = $params['where_expr']; |
| 312 | $join_instances = $join_instances || ( stripos($params['where_expr'], 'instances') !== false ); |
| 313 | } |
| 314 | |
| 315 | //List of allowed link ids (either an array or comma-separated) |
| 316 | if ( !empty($params['link_ids']) ){ |
| 317 | $link_ids = $params['link_ids']; |
| 318 | |
| 319 | if ( is_string($link_ids) ){ |
| 320 | $link_ids = preg_split('/[,\s]+/', $link_ids); |
| 321 | } |
| 322 | |
| 323 | //Only accept non-zero integers |
| 324 | $sanitized_link_ids = array(); |
| 325 | foreach($link_ids as $id){ |
| 326 | $id = intval($id); |
| 327 | if ( $id != 0 ){ |
| 328 | $sanitized_link_ids[] = $id; |
| 329 | } |
| 330 | } |
| 331 | |
| 332 | $pieces[] = 'links.link_id IN (' . implode(', ', $sanitized_link_ids) . ')'; |
| 333 | } |
| 334 | |
| 335 | //Anchor text - use LIKE search |
| 336 | if ( !empty($params['s_link_text']) ){ |
| 337 | $s_link_text = like_escape($wpdb->escape($params['s_link_text'])); |
| 338 | $s_link_text = str_replace('*', '%', $s_link_text); |
| 339 | |
| 340 | $pieces[] = '(instances.link_text LIKE "%' . $s_link_text . '%")'; |
| 341 | $join_instances = true; |
| 342 | } |
| 343 | |
| 344 | //URL - try to match both the initial URL and the final URL. |
| 345 | //There is limited wildcard support, e.g. "google.*/search" will match both |
| 346 | //"google.com/search" and "google.lv/search" |
| 347 | if ( !empty($params['s_link_url']) ){ |
| 348 | $s_link_url = like_escape($wpdb->escape($params['s_link_url'])); |
| 349 | $s_link_url = str_replace('*', '%', $s_link_url); |
| 350 | |
| 351 | $pieces[] = '(links.url LIKE "%'. $s_link_url .'%") OR '. |
| 352 | '(links.final_url LIKE "%'. $s_link_url .'%")'; |
| 353 | } |
| 354 | |
| 355 | //Container ID should match... you guessed it - container_id |
| 356 | if ( !empty($params['s_container_id']) ){ |
| 357 | $s_container_id = intval($params['s_container_id']); |
| 358 | if ( $s_container_id != 0 ){ |
| 359 | $pieces[] = "instances.container_id = $s_container_id"; |
| 360 | $join_instances = true; |
| 361 | } |
| 362 | } |
| 363 | |
| 364 | //Link type can match either the the parser_type or the container_type. |
| 365 | if ( !empty($params['s_link_type']) ){ |
| 366 | $s_link_type = $wpdb->escape($params['s_link_type']); |
| 367 | $pieces[] = "instances.parser_type = '$s_link_type' OR instances.container_type='$s_link_type'"; |
| 368 | $join_instances = true; |
| 369 | } |
| 370 | |
| 371 | //HTTP code - the user can provide a list of HTTP response codes and code ranges. |
| 372 | //Example : 201,400-410,500 |
| 373 | if ( !empty($params['s_http_code']) ){ |
| 374 | //Strip spaces. |
| 375 | $params['s_http_code'] = str_replace(' ', '', $params['s_http_code']); |
| 376 | //Split by comma |
| 377 | $codes = explode(',', $params['s_http_code']); |
| 378 | |
| 379 | $individual_codes = array(); |
| 380 | $ranges = array(); |
| 381 | |
| 382 | //Try to parse each response code or range. Invalid ones are simply ignored. |
| 383 | foreach($codes as $code){ |
| 384 | if ( is_numeric($code) ){ |
| 385 | //It's a single number |
| 386 | $individual_codes[] = abs(intval($code)); |
| 387 | } elseif ( strpos($code, '-') !== false ) { |
| 388 | //Try to parse it as a range |
| 389 | $range = explode( '-', $code, 2 ); |
| 390 | if ( (count($range) == 2) && is_numeric($range[0]) && is_numeric($range[0]) ){ |
| 391 | //Make sure the smaller code comes first |
| 392 | $range = array( intval($range[0]), intval($range[1]) ); |
| 393 | $ranges[] = array( min($range), max($range) ); |
| 394 | } |
| 395 | } |
| 396 | } |
| 397 | |
| 398 | $piece = array(); |
| 399 | |
| 400 | //All individual response codes get one "http_code IN (...)" clause |
| 401 | if ( !empty($individual_codes) ){ |
| 402 | $piece[] = '(links.http_code IN ('. implode(', ', $individual_codes) .'))'; |
| 403 | } |
| 404 | |
| 405 | //Ranges get a "http_code BETWEEN min AND max" clause each |
| 406 | if ( !empty($ranges) ){ |
| 407 | $range_strings = array(); |
| 408 | foreach($ranges as $range){ |
| 409 | $range_strings[] = "(links.http_code BETWEEN $range[0] AND $range[1])"; |
| 410 | } |
| 411 | $piece[] = '( ' . implode(' OR ', $range_strings) . ' )'; |
| 412 | } |
| 413 | |
| 414 | //Finally, generate a composite WHERE clause for both types of response code queries |
| 415 | if ( !empty($piece) ){ |
| 416 | $pieces[] = implode(' OR ', $piece); |
| 417 | } |
| 418 | |
| 419 | } |
| 420 | |
| 421 | //Optionally sorting is also possible |
| 422 | $order_exprs = array(); |
| 423 | if ( !empty($params['orderby']) ) { |
| 424 | $allowed_columns = array( |
| 425 | 'url' => 'links.url', |
| 426 | ); |
| 427 | $column = $params['orderby']; |
| 428 | |
| 429 | $direction = !empty($params['order']) ? strtolower($params['order']) : 'asc'; |
| 430 | if ( !in_array($direction, array('asc', 'desc')) ) { |
| 431 | $direction = 'asc'; |
| 432 | } |
| 433 | |
| 434 | if ( array_key_exists($column, $allowed_columns) ) { |
| 435 | $order_exprs[] = $allowed_columns[$column] . ' ' . $direction; |
| 436 | } |
| 437 | } |
| 438 | |
| 439 | //Custom filters can optionally call one of the native filters |
| 440 | //to narrow down the result set. |
| 441 | if ( !empty($params['s_filter']) && isset($this->native_filters[$params['s_filter']]) ){ |
| 442 | $the_filter = $this->native_filters[$params['s_filter']]; |
| 443 | $extra_criteria = $this->compile_search_params($the_filter['params']); |
| 444 | |
| 445 | $pieces = array_merge($pieces, $extra_criteria['where_exprs']); |
| 446 | $join_instances = $join_instances || $extra_criteria['join_instances']; |
| 447 | } |
| 448 | |
| 449 | return array( |
| 450 | 'where_exprs' => $pieces, |
| 451 | 'join_instances' => $join_instances, |
| 452 | 'order_exprs' => $order_exprs, |
| 453 | ); |
| 454 | } |
| 455 | |
| 456 | /** |
| 457 | * blcLinkQuery::get_links() |
| 458 | * |
| 459 | * @see blc_get_links() |
| 460 | * |
| 461 | * @param array $params |
| 462 | * @return array|int |
| 463 | */ |
| 464 | function get_links($params = null){ |
| 465 | global $wpdb; /** @var wpdb $wpdb */ |
| 466 | |
| 467 | if( !is_array($params) ){ |
| 468 | $params = array(); |
| 469 | } |
| 470 | |
| 471 | $defaults = array( |
| 472 | 'offset' => 0, |
| 473 | 'max_results' => 0, |
| 474 | 'load_instances' => false, |
| 475 | 'load_containers' => false, |
| 476 | 'load_wrapped_objects' => false, |
| 477 | 'count_only' => false, |
| 478 | 'purpose' => '', |
| 479 | 'include_invalid' => false, |
| 480 | 'orderby' => '', |
| 481 | 'order' => '', |
| 482 | ); |
| 483 | |
| 484 | $params = array_merge($defaults, $params); |
| 485 | |
| 486 | //Compile the search-related params into search expressions usable in a WHERE clause |
| 487 | $criteria = $this->compile_search_params($params); |
| 488 | |
| 489 | //Build the WHERE clause |
| 490 | if ( !empty($criteria['where_exprs']) ){ |
| 491 | $where_expr = "\t( " . implode(" ) AND\n\t( ", $criteria['where_exprs']) . ' ) '; |
| 492 | } else { |
| 493 | $where_expr = '1'; |
| 494 | } |
| 495 | |
| 496 | //Join the blc_instances table if it's required to perform the search. |
| 497 | $joins = ""; |
| 498 | if ( $criteria['join_instances'] ){ |
| 499 | $joins = "JOIN {$wpdb->prefix}blc_instances AS instances ON links.link_id = instances.link_id"; |
| 500 | } |
| 501 | |
| 502 | //Optional sorting |
| 503 | if ( !empty($criteria['order_exprs']) ) { |
| 504 | $order_clause = 'ORDER BY ' . implode(', ', $criteria['order_exprs']); |
| 505 | } else { |
| 506 | $order_clause = ''; |
| 507 | } |
| 508 | |
| 509 | if ( $params['count_only'] ){ |
| 510 | //Only get the number of matching links. |
| 511 | $q = " |
| 512 | SELECT COUNT(*) |
| 513 | FROM ( |
| 514 | SELECT 0 |
| 515 | |
| 516 | FROM |
| 517 | {$wpdb->prefix}blc_links AS links |
| 518 | $joins |
| 519 | |
| 520 | WHERE |
| 521 | $where_expr |
| 522 | |
| 523 | GROUP BY links.link_id) AS foo"; |
| 524 | |
| 525 | return $wpdb->get_var($q); |
| 526 | } |
| 527 | |
| 528 | //Select the required links. |
| 529 | $q = "SELECT |
| 530 | links.* |
| 531 | |
| 532 | FROM |
| 533 | {$wpdb->prefix}blc_links AS links |
| 534 | $joins |
| 535 | |
| 536 | WHERE |
| 537 | $where_expr |
| 538 | |
| 539 | GROUP BY links.link_id |
| 540 | |
| 541 | {$order_clause}"; //Note: would be a lot faster without GROUP BY |
| 542 | |
| 543 | //Add the LIMIT clause |
| 544 | if ( $params['max_results'] || $params['offset'] ){ |
| 545 | $q .= sprintf("\nLIMIT %d, %d", $params['offset'], $params['max_results']); |
| 546 | } |
| 547 | |
| 548 | $results = $wpdb->get_results($q, ARRAY_A); |
| 549 | if ( empty($results) ){ |
| 550 | return array(); |
| 551 | } |
| 552 | |
| 553 | //Create the link objects |
| 554 | $links = array(); |
| 555 | |
| 556 | foreach($results as $result){ |
| 557 | $link = new blcLink($result); |
| 558 | $links[$link->link_id] = $link; |
| 559 | } |
| 560 | |
| 561 | $purpose = $params['purpose']; |
| 562 | /* |
| 563 | Preload instances if : |
| 564 | * It has been requested via the 'load_instances' argument. |
| 565 | * The links are going to be displayed or edited, which involves instances. |
| 566 | */ |
| 567 | $load_instances = $params['load_instances'] || in_array($purpose, array(BLC_FOR_DISPLAY, BLC_FOR_EDITING)); |
| 568 | |
| 569 | if ( $load_instances ){ |
| 570 | $link_ids = array_keys($links); |
| 571 | $all_instances = blc_get_instances($link_ids, $purpose, $params['load_containers'], $params['load_wrapped_objects']); |
| 572 | //Assign each batch of instances to the right link |
| 573 | foreach($all_instances as $link_id => $instances){ |
| 574 | foreach($instances as $instance) { /** @var blcLinkInstance $instance */ |
| 575 | $instance->_link = $links[$link_id]; |
| 576 | } |
| 577 | $links[$link_id]->_instances = $instances; |
| 578 | } |
| 579 | } |
| 580 | |
| 581 | return $links; |
| 582 | } |
| 583 | |
| 584 | /** |
| 585 | * Calculate the number of results for all known filters |
| 586 | * |
| 587 | * @return void |
| 588 | */ |
| 589 | function count_filter_results(){ |
| 590 | foreach($this->native_filters as $filter_id => $filter){ |
| 591 | $this->native_filters[$filter_id]['count'] = $this->get_filter_links( |
| 592 | $filter, array('count_only' => true) |
| 593 | ); |
| 594 | } |
| 595 | |
| 596 | foreach($this->custom_filters as $filter_id => $filter){ |
| 597 | $this->custom_filters[$filter_id]['count'] = $this->get_filter_links( |
| 598 | $filter, array('count_only' => true) |
| 599 | ); |
| 600 | } |
| 601 | |
| 602 | $this->search_filter['count'] = $this->get_filter_links($this->search_filter, array('count_only' => true)); |
| 603 | } |
| 604 | |
| 605 | /** |
| 606 | * Retrieve a list of links matching a filter. |
| 607 | * |
| 608 | * @uses blcLinkQuery::get_links() |
| 609 | * |
| 610 | * @param string|array $filter Either a filter ID or an array containing filter data. |
| 611 | * @param array $extra_params Optional extra criteria that will override those set by the filter. See blc_get_links() for details. |
| 612 | * @return array|int Either an array of blcLink objects, or an integer indicating the number of links that match the filter. |
| 613 | */ |
| 614 | function get_filter_links($filter, $extra_params = null){ |
| 615 | if ( is_string($filter) ){ |
| 616 | $filter = $this->get_filter($filter); |
| 617 | } |
| 618 | |
| 619 | $params = $this->get_search_params($filter); |
| 620 | |
| 621 | |
| 622 | if ( !empty($extra_params) ){ |
| 623 | $params = array_merge($params, $extra_params); |
| 624 | } |
| 625 | |
| 626 | return $this->get_links($params); |
| 627 | } |
| 628 | |
| 629 | /** |
| 630 | * Print a menu of available filters, both native and user-created. |
| 631 | * |
| 632 | * @param string $current Current filter ID. |
| 633 | * @return void |
| 634 | */ |
| 635 | function print_filter_menu($current = ''){ |
| 636 | $filters = $this->get_filters(); |
| 637 | |
| 638 | echo '<ul class="subsubsub">'; |
| 639 | |
| 640 | //Construct a submenu of filter types |
| 641 | $items = array(); |
| 642 | foreach ($filters as $filter => $data){ |
| 643 | if ( !empty($data['hidden']) ) continue; //skip hidden filters |
| 644 | |
| 645 | $class = $number_class = ''; |
| 646 | |
| 647 | if ( $current == $filter ) { |
| 648 | $class = 'class="current"'; |
| 649 | $number_class = 'current-link-count'; |
| 650 | } |
| 651 | |
| 652 | $items[] = "<li><a href='tools.php?page=view-broken-links&filter_id=$filter' $class> |
| 653 | {$data['name']}</a> <span class='count'>(<span class='$number_class'>{$data['count']}</span>)</span>"; |
| 654 | } |
| 655 | echo implode(' |</li>', $items); |
| 656 | |
| 657 | echo '</ul>'; |
| 658 | } |
| 659 | |
| 660 | /** |
| 661 | * Print the appropriate heading for the given filter. |
| 662 | * |
| 663 | * @param array $current_filter |
| 664 | * @return void |
| 665 | */ |
| 666 | function print_filter_heading($current_filter){ |
| 667 | echo '<h2>'; |
| 668 | //Output a header matching the current filter |
| 669 | if ( $current_filter['count'] > 0 ){ |
| 670 | echo $current_filter['heading'] . " (<span class='current-link-count'>{$current_filter['count']}</span>)"; |
| 671 | } else { |
| 672 | echo $current_filter['heading_zero'] . "<span class='current-link-count'></span>"; |
| 673 | } |
| 674 | echo '</h2>'; |
| 675 | } |
| 676 | |
| 677 | /** |
| 678 | * Execute a filter. |
| 679 | * |
| 680 | * Gathers paging and search parameters from $_GET and executes the specified filter. |
| 681 | * The returned array contains standard filter data plus several additional fields : |
| 682 | * 'filter_id' - Which filter was used. May differ from the specified $filter_id due to fallback settings. |
| 683 | * 'per_page' - How many results per page the method tried to retrieve. |
| 684 | * 'page' - Which page of results was retrieved. |
| 685 | * 'max_pages' - The total number of results pages, calculated using the above 'per_page' value. |
| 686 | * 'links' - An array of retrieved links (blcLink objects). |
| 687 | * 'search_params' - An associative array of the current search parameters as extracted either from the current URL or the filter itself. |
| 688 | * 'is_broken_filter' - TRUE if the filter was set to retrieve only broken links, FALSE otherwise. |
| 689 | * |
| 690 | * @param string $filter_id Filter ID. |
| 691 | * @param int $page Optional. Which page of results to retrieve. Defaults to returning the first page of results. |
| 692 | * @param int $per_page Optional. The number of results per page. Defaults to 30. |
| 693 | * @param string $fallback Optional. Which filter to use if none match the specified $filter_id. Defaults to the native broken link filter. |
| 694 | * @param string $orderby Optional. Sort results by this column. |
| 695 | * @param string $order Optional. Sort direction ('asc' or 'desc'). |
| 696 | * @return array Associative array of filter data and the results of its execution. |
| 697 | */ |
| 698 | function exec_filter($filter_id, $page = 1, $per_page = 30, $fallback = 'broken', $orderby = '', $order = 'asc'){ |
| 699 | |
| 700 | //Get the selected filter (defaults to displaying broken links) |
| 701 | $current_filter = $this->get_filter($filter_id); |
| 702 | if ( empty($current_filter) ){ |
| 703 | $current_filter = $this->get_filter($fallback); |
| 704 | $filter_id = $fallback; |
| 705 | } |
| 706 | |
| 707 | //Page number must be > 0 |
| 708 | if ($page < 1) $page = 1; |
| 709 | |
| 710 | //Links per page [1 - 500] |
| 711 | if ($per_page < 1){ |
| 712 | $per_page = 30; |
| 713 | } else if ($per_page > 500){ |
| 714 | $per_page = 500; |
| 715 | } |
| 716 | |
| 717 | //Calculate the maximum number of pages. |
| 718 | $max_pages = ceil($current_filter['count'] / $per_page); |
| 719 | |
| 720 | //Select the required links |
| 721 | $extra_params = array( |
| 722 | 'offset' => ( ($page-1) * $per_page ), |
| 723 | 'max_results' => $per_page, |
| 724 | 'purpose' => BLC_FOR_DISPLAY, |
| 725 | 'orderby' => $orderby, |
| 726 | 'order' => $order, |
| 727 | ); |
| 728 | $links = $this->get_filter_links($current_filter, $extra_params); |
| 729 | |
| 730 | //If the current request is a user-initiated search query (either directly or |
| 731 | //via a custom filter), save the search params. They can later be used to pre-fill |
| 732 | //the search form or build a new/modified custom filter. |
| 733 | $search_params = array(); |
| 734 | if ( !empty($current_filter['custom']) || ($filter_id == 'search') ){ |
| 735 | $search_params = $this->get_search_params($current_filter); |
| 736 | } |
| 737 | |
| 738 | //TODO: Simplify this. Maybe overhaul the filter system to let us query the effective filter. |
| 739 | $is_broken_filter = |
| 740 | ($filter_id == 'broken') |
| 741 | || ( isset($current_filter['params']['s_filter']) && ($current_filter['params']['s_filter'] == 'broken') ) |
| 742 | || ( isset($_GET['s_filter']) && ($_GET['s_filter'] == 'broken') ); |
| 743 | |
| 744 | //Save the effective filter data in the filter array. |
| 745 | //It can be used later to print the link table. |
| 746 | $current_filter = array_merge(array( |
| 747 | 'filter_id' => $filter_id, |
| 748 | 'page' => $page, |
| 749 | 'per_page' => $per_page, |
| 750 | 'max_pages' => $max_pages, |
| 751 | 'links' => $links, |
| 752 | 'search_params' => $search_params, |
| 753 | 'is_broken_filter' => $is_broken_filter, |
| 754 | ), $current_filter); |
| 755 | |
| 756 | return $current_filter; |
| 757 | } |
| 758 | } |
| 759 | |
| 760 | /** |
| 761 | * Retrieve a list of links matching some criteria. |
| 762 | * |
| 763 | * The function argument should be an associative array describing the criteria. |
| 764 | * The supported keys are : |
| 765 | * 'offset' - Skip the first X results. Default is 0. |
| 766 | * 'max_results' - The maximum number of links to return. Defaults to returning all results. |
| 767 | * 'link_ids' - Retrieve only links with these IDs. This should either be a comma-separated list or an array. |
| 768 | * 's_link_text' - Link text must match this keyphrase (performs a fulltext search). |
| 769 | * 's_link_url' - Link URL must contain this string. You can use "*" as a wildcard. |
| 770 | * 's_parser_type' - Filter links by the type of link parser that was used to find them. |
| 771 | * 's_container_type' - Filter links by where they were found, e.g. 'post'. |
| 772 | * 's_container_id' - Find links that belong to a container with this ID (should be used together with s_container_type). |
| 773 | * 's_link_type' - Either parser type or container type must match this. |
| 774 | * 's_http_code' - Filter by HTTP code. Example : 201,400-410,500 |
| 775 | * 's_filter' - Use a built-in filter. Available filters : 'broken', 'redirects', 'all' |
| 776 | * 'where_expr' - Advanced. Lets you directly specify a part of the WHERE clause. |
| 777 | * 'load_instances' - Pre-load all link instance data for each link. Default is false. |
| 778 | * 'load_containers' - Pre-load container data for each instance. Default is false. |
| 779 | * 'load_wrapped_objects' - Pre-load wrapped object data (e.g. posts, comments, etc) for each container. Default is false. |
| 780 | * 'count_only' - Only return the number of results (int), not the whole result set. 'offset' and 'max_results' will be ignored if this is set. Default is false. |
| 781 | * 'purpose' - An optional code indicating how the links will be used. |
| 782 | * 'include_invalid' - Include links that have no instances and links that only have instances that reference not-loaded containers or parsers. Defaults to false. |
| 783 | * |
| 784 | * All keys are optional. |
| 785 | * |
| 786 | * @uses blcLinkQuery::get_links(); |
| 787 | * |
| 788 | * @param array $params |
| 789 | * @return int|array Either an array of blcLink objects, or the number of results for the query. |
| 790 | */ |
| 791 | function blc_get_links($params = null){ |
| 792 | $instance = blcLinkQuery::getInstance(); |
| 793 | return $instance->get_links($params); |
| 794 | } |
| 795 | |
| 796 | ?> |