[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/ -> class-wp-network-query.php (source)

   1  <?php
   2  /**
   3   * Network API: WP_Network_Query class
   4   *
   5   * @package WordPress
   6   * @subpackage Multisite
   7   * @since 4.6.0
   8   */
   9  
  10  /**
  11   * Core class used for querying networks.
  12   *
  13   * @since 4.6.0
  14   *
  15   * @see WP_Network_Query::__construct() for accepted arguments.
  16   */
  17  #[AllowDynamicProperties]
  18  class WP_Network_Query {
  19  
  20      /**
  21       * SQL for database query.
  22       *
  23       * @since 4.6.0
  24       * @var string
  25       */
  26      public $request;
  27  
  28      /**
  29       * SQL query clauses.
  30       *
  31       * @since 4.6.0
  32       * @var array{
  33       *     select: string,
  34       *     from: string,
  35       *     where: array<string, string>,
  36       *     groupby: string,
  37       *     orderby: string,
  38       *     limits: string
  39       * }
  40       */
  41      protected $sql_clauses = array(
  42          'select'  => '',
  43          'from'    => '',
  44          'where'   => array(),
  45          'groupby' => '',
  46          'orderby' => '',
  47          'limits'  => '',
  48      );
  49  
  50      /**
  51       * Query vars set by the user.
  52       *
  53       * @since 4.6.0
  54       * @var array<string, mixed>
  55       */
  56      public $query_vars;
  57  
  58      /**
  59       * Default values for query vars.
  60       *
  61       * @since 4.6.0
  62       * @var array<string, mixed>
  63       */
  64      public $query_var_defaults;
  65  
  66      /**
  67       * List of networks located by the query.
  68       *
  69       * @since 4.6.0
  70       * @var WP_Network[]|int[]
  71       */
  72      public $networks;
  73  
  74      /**
  75       * The amount of found networks for the current query.
  76       *
  77       * @since 4.6.0
  78       * @var int
  79       * @phpstan-var non-negative-int
  80       */
  81      public $found_networks = 0;
  82  
  83      /**
  84       * The number of pages.
  85       *
  86       * @since 4.6.0
  87       * @var int
  88       * @phpstan-var non-negative-int
  89       */
  90      public $max_num_pages = 0;
  91  
  92      /**
  93       * Constructor.
  94       *
  95       * Sets up the network query, based on the query vars passed.
  96       *
  97       * @since 4.6.0
  98       *
  99       * @param string|array $query {
 100       *     Optional. Array or query string of network query parameters. Default empty.
 101       *
 102       *     @type int[]        $network__in          Array of network IDs to include. Default empty.
 103       *     @type int[]        $network__not_in      Array of network IDs to exclude. Default empty.
 104       *     @type bool         $count                Whether to return a network count (true) or array of network objects.
 105       *                                              Default false.
 106       *     @type string       $fields               Network fields to return. Accepts 'ids' (returns an array of network IDs)
 107       *                                              or empty (returns an array of complete network objects). Default empty.
 108       *     @type int          $number               Maximum number of networks to retrieve. Default empty (no limit).
 109       *     @type int          $offset               Number of networks to offset the query. Used to build LIMIT clause.
 110       *                                              Default 0.
 111       *     @type bool         $no_found_rows        Whether to disable the `SQL_CALC_FOUND_ROWS` query. Default true.
 112       *     @type string|array $orderby              Network status or array of statuses. Accepts 'id', 'domain', 'path',
 113       *                                              'domain_length', 'path_length' and 'network__in'. Also accepts false,
 114       *                                              an empty array, or 'none' to disable `ORDER BY` clause. Default 'id'.
 115       *     @type string       $order                How to order retrieved networks. Accepts 'ASC', 'DESC'. Default 'ASC'.
 116       *     @type string       $domain               Limit results to those affiliated with a given domain. Default empty.
 117       *     @type string[]     $domain__in           Array of domains to include affiliated networks for. Default empty.
 118       *     @type string[]     $domain__not_in       Array of domains to exclude affiliated networks for. Default empty.
 119       *     @type string       $path                 Limit results to those affiliated with a given path. Default empty.
 120       *     @type string[]     $path__in             Array of paths to include affiliated networks for. Default empty.
 121       *     @type string[]     $path__not_in         Array of paths to exclude affiliated networks for. Default empty.
 122       *     @type string       $search               Search term(s) to retrieve matching networks for. Default empty.
 123       *     @type bool         $update_network_cache Whether to prime the cache for found networks. Default true.
 124       * }
 125       */
 126  	public function __construct( $query = '' ) {
 127          $this->query_var_defaults = array(
 128              'network__in'          => '',
 129              'network__not_in'      => '',
 130              'count'                => false,
 131              'fields'               => '',
 132              'number'               => '',
 133              'offset'               => '',
 134              'no_found_rows'        => true,
 135              'orderby'              => 'id',
 136              'order'                => 'ASC',
 137              'domain'               => '',
 138              'domain__in'           => '',
 139              'domain__not_in'       => '',
 140              'path'                 => '',
 141              'path__in'             => '',
 142              'path__not_in'         => '',
 143              'search'               => '',
 144              'update_network_cache' => true,
 145          );
 146  
 147          if ( ! empty( $query ) ) {
 148              $this->query( $query );
 149          }
 150      }
 151  
 152      /**
 153       * Parses arguments passed to the network query with default query parameters.
 154       *
 155       * @since 4.6.0
 156       *
 157       * @param string|array $query WP_Network_Query arguments. See WP_Network_Query::__construct() for accepted arguments.
 158       */
 159  	public function parse_query( $query = '' ) {
 160          if ( empty( $query ) ) {
 161              $query = $this->query_vars;
 162          }
 163  
 164          $this->query_vars = wp_parse_args( $query, $this->query_var_defaults );
 165  
 166          /**
 167           * Fires after the network query vars have been parsed.
 168           *
 169           * @since 4.6.0
 170           *
 171           * @param WP_Network_Query $query The WP_Network_Query instance (passed by reference).
 172           */
 173          do_action_ref_array( 'parse_network_query', array( &$this ) );
 174      }
 175  
 176      /**
 177       * Sets up the WordPress query for retrieving networks.
 178       *
 179       * @since 4.6.0
 180       *
 181       * @param string|array $query Array or URL query string of parameters.
 182       * @return array|int List of WP_Network objects, a list of network IDs when 'fields' is set to 'ids',
 183       *                   or the number of networks when 'count' is passed as a query var.
 184       */
 185  	public function query( $query ) {
 186          $this->query_vars = wp_parse_args( $query );
 187          return $this->get_networks();
 188      }
 189  
 190      /**
 191       * Gets a list of networks matching the query vars.
 192       *
 193       * @since 4.6.0
 194       *
 195       * @return array|int List of WP_Network objects, a list of network IDs when 'fields' is set to 'ids',
 196       *                   or the number of networks when 'count' is passed as a query var.
 197       */
 198  	public function get_networks() {
 199          $this->parse_query();
 200  
 201          /**
 202           * Fires before networks are retrieved.
 203           *
 204           * @since 4.6.0
 205           *
 206           * @param WP_Network_Query $query Current instance of WP_Network_Query (passed by reference).
 207           */
 208          do_action_ref_array( 'pre_get_networks', array( &$this ) );
 209  
 210          $network_data = null;
 211  
 212          /**
 213           * Filters the network data before the query takes place.
 214           *
 215           * Return a non-null value to bypass WordPress' default network queries.
 216           *
 217           * The expected return type from this filter depends on the value passed
 218           * in the request query vars:
 219           * - When `$this->query_vars['count']` is set, the filter should return
 220           *   the network count as an integer.
 221           * - When `'ids' === $this->query_vars['fields']`, the filter should return
 222           *   an array of network IDs.
 223           * - Otherwise the filter should return an array of WP_Network objects.
 224           *
 225           * Note that if the filter returns an array of network data, it will be assigned
 226           * to the `networks` property of the current WP_Network_Query instance.
 227           *
 228           * Filtering functions that require pagination information are encouraged to set
 229           * the `found_networks` and `max_num_pages` properties of the WP_Network_Query object,
 230           * passed to the filter by reference. If WP_Network_Query does not perform a database
 231           * query, it will not have enough information to generate these values itself.
 232           *
 233           * @since 5.2.0
 234           * @since 5.6.0 The returned array of network data is assigned to the `networks` property
 235           *              of the current WP_Network_Query instance.
 236           *
 237           * @param array|int|null   $network_data Return an array of network data to short-circuit WP's network query,
 238           *                                       the network count as an integer if `$this->query_vars['count']` is set,
 239           *                                       or null to allow WP to run its normal queries.
 240           * @param WP_Network_Query $query        The WP_Network_Query instance, passed by reference.
 241           */
 242          $network_data = apply_filters_ref_array( 'networks_pre_query', array( $network_data, &$this ) );
 243  
 244          if ( null !== $network_data ) {
 245              if ( is_array( $network_data ) && ! $this->query_vars['count'] ) {
 246                  $this->networks = $network_data;
 247              }
 248  
 249              return $network_data;
 250          }
 251  
 252          // $args can include anything. Only use the args defined in the query_var_defaults to compute the key.
 253          $_args = wp_array_slice_assoc( $this->query_vars, array_keys( $this->query_var_defaults ) );
 254  
 255          // Ignore the $fields, $update_network_cache arguments as the queried result will be the same regardless.
 256          unset( $_args['fields'], $_args['update_network_cache'] );
 257  
 258          $key          = md5( serialize( $_args ) );
 259          $last_changed = wp_cache_get_last_changed( 'networks' );
 260  
 261          $cache_key   = "get_network_ids:$key";
 262          $cache_value = wp_cache_get_salted( $cache_key, 'network-queries', $last_changed );
 263  
 264          if ( false === $cache_value ) {
 265              $network_ids = $this->get_network_ids();
 266              if ( $network_ids ) {
 267                  $this->set_found_networks();
 268              }
 269  
 270              $cache_value = array(
 271                  'network_ids'    => $network_ids,
 272                  'found_networks' => $this->found_networks,
 273              );
 274              wp_cache_set_salted( $cache_key, $cache_value, 'network-queries', $last_changed );
 275          } else {
 276              $network_ids          = $cache_value['network_ids'];
 277              $this->found_networks = $cache_value['found_networks'];
 278          }
 279  
 280          if ( $this->found_networks && $this->query_vars['number'] ) {
 281              $this->max_num_pages = (int) ceil( $this->found_networks / $this->query_vars['number'] );
 282          }
 283  
 284          // If querying for a count only, there's nothing more to do.
 285          if ( $this->query_vars['count'] ) {
 286              // $network_ids is actually a count in this case.
 287              return (int) $network_ids;
 288          }
 289  
 290          $network_ids = array_map( 'intval', $network_ids );
 291  
 292          if ( 'ids' === $this->query_vars['fields'] ) {
 293              $this->networks = $network_ids;
 294              return $this->networks;
 295          }
 296  
 297          if ( $this->query_vars['update_network_cache'] ) {
 298              _prime_network_caches( $network_ids );
 299          }
 300  
 301          // Fetch full network objects from the primed cache.
 302          $_networks = array();
 303          foreach ( $network_ids as $network_id ) {
 304              $_network = get_network( $network_id );
 305              if ( $_network ) {
 306                  $_networks[] = $_network;
 307              }
 308          }
 309  
 310          /**
 311           * Filters the network query results.
 312           *
 313           * @since 4.6.0
 314           *
 315           * @param WP_Network[]     $_networks An array of WP_Network objects.
 316           * @param WP_Network_Query $query     Current instance of WP_Network_Query (passed by reference).
 317           */
 318          $_networks = apply_filters_ref_array( 'the_networks', array( $_networks, &$this ) );
 319  
 320          // Convert to WP_Network instances.
 321          $this->networks = array_map( 'get_network', $_networks );
 322  
 323          return $this->networks;
 324      }
 325  
 326      /**
 327       * Used internally to get a list of network IDs matching the query vars.
 328       *
 329       * @since 4.6.0
 330       *
 331       * @global wpdb $wpdb WordPress database abstraction object.
 332       *
 333       * @return int|array A single count of network IDs if a count query. An array of network IDs if a full query.
 334       */
 335  	protected function get_network_ids() {
 336          global $wpdb;
 337  
 338          $order = $this->parse_order( $this->query_vars['order'] );
 339  
 340          // Disable ORDER BY with 'none', an empty array, or boolean false.
 341          if ( in_array( $this->query_vars['orderby'], array( 'none', array(), false ), true ) ) {
 342              $orderby = '';
 343          } elseif ( ! empty( $this->query_vars['orderby'] ) ) {
 344              $ordersby = is_array( $this->query_vars['orderby'] ) ?
 345                  $this->query_vars['orderby'] :
 346                  preg_split( '/[,\s]/', $this->query_vars['orderby'] );
 347  
 348              $orderby_array = array();
 349              foreach ( $ordersby as $_key => $_value ) {
 350                  if ( ! $_value ) {
 351                      continue;
 352                  }
 353  
 354                  if ( is_int( $_key ) ) {
 355                      $_orderby = $_value;
 356                      $_order   = $order;
 357                  } else {
 358                      $_orderby = $_key;
 359                      $_order   = $_value;
 360                  }
 361  
 362                  $parsed = $this->parse_orderby( $_orderby );
 363  
 364                  if ( ! $parsed ) {
 365                      continue;
 366                  }
 367  
 368                  if ( 'network__in' === $_orderby ) {
 369                      $orderby_array[] = $parsed;
 370                      continue;
 371                  }
 372  
 373                  $orderby_array[] = $parsed . ' ' . $this->parse_order( $_order );
 374              }
 375  
 376              $orderby = implode( ', ', $orderby_array );
 377          } else {
 378              $orderby = "$wpdb->site.id $order";
 379          }
 380  
 381          $number = absint( $this->query_vars['number'] );
 382          $offset = absint( $this->query_vars['offset'] );
 383          $limits = '';
 384  
 385          if ( ! empty( $number ) ) {
 386              if ( $offset ) {
 387                  $limits = 'LIMIT ' . $offset . ',' . $number;
 388              } else {
 389                  $limits = 'LIMIT ' . $number;
 390              }
 391          }
 392  
 393          if ( $this->query_vars['count'] ) {
 394              $fields = 'COUNT(*)';
 395          } else {
 396              $fields = "$wpdb->site.id";
 397          }
 398  
 399          // Parse network IDs for an IN clause.
 400          if ( ! empty( $this->query_vars['network__in'] ) ) {
 401              $this->sql_clauses['where']['network__in'] = "$wpdb->site.id IN ( " . implode( ',', wp_parse_id_list( $this->query_vars['network__in'] ) ) . ' )';
 402          }
 403  
 404          // Parse network IDs for a NOT IN clause.
 405          if ( ! empty( $this->query_vars['network__not_in'] ) ) {
 406              $this->sql_clauses['where']['network__not_in'] = "$wpdb->site.id NOT IN ( " . implode( ',', wp_parse_id_list( $this->query_vars['network__not_in'] ) ) . ' )';
 407          }
 408  
 409          if ( ! empty( $this->query_vars['domain'] ) ) {
 410              $this->sql_clauses['where']['domain'] = $wpdb->prepare( "$wpdb->site.domain = %s", $this->query_vars['domain'] );
 411          }
 412  
 413          // Parse network domain for an IN clause.
 414          if ( is_array( $this->query_vars['domain__in'] ) ) {
 415              $this->sql_clauses['where']['domain__in'] = "$wpdb->site.domain IN ( '" . implode( "', '", $wpdb->_escape( $this->query_vars['domain__in'] ) ) . "' )";
 416          }
 417  
 418          // Parse network domain for a NOT IN clause.
 419          if ( is_array( $this->query_vars['domain__not_in'] ) ) {
 420              $this->sql_clauses['where']['domain__not_in'] = "$wpdb->site.domain NOT IN ( '" . implode( "', '", $wpdb->_escape( $this->query_vars['domain__not_in'] ) ) . "' )";
 421          }
 422  
 423          if ( ! empty( $this->query_vars['path'] ) ) {
 424              $this->sql_clauses['where']['path'] = $wpdb->prepare( "$wpdb->site.path = %s", $this->query_vars['path'] );
 425          }
 426  
 427          // Parse network path for an IN clause.
 428          if ( is_array( $this->query_vars['path__in'] ) ) {
 429              $this->sql_clauses['where']['path__in'] = "$wpdb->site.path IN ( '" . implode( "', '", $wpdb->_escape( $this->query_vars['path__in'] ) ) . "' )";
 430          }
 431  
 432          // Parse network path for a NOT IN clause.
 433          if ( is_array( $this->query_vars['path__not_in'] ) ) {
 434              $this->sql_clauses['where']['path__not_in'] = "$wpdb->site.path NOT IN ( '" . implode( "', '", $wpdb->_escape( $this->query_vars['path__not_in'] ) ) . "' )";
 435          }
 436  
 437          // Falsey search strings are ignored.
 438          if ( strlen( $this->query_vars['search'] ) ) {
 439              $this->sql_clauses['where']['search'] = $this->get_search_sql(
 440                  $this->query_vars['search'],
 441                  array( "$wpdb->site.domain", "$wpdb->site.path" )
 442              );
 443          }
 444  
 445          $join = '';
 446  
 447          $where = implode( ' AND ', $this->sql_clauses['where'] );
 448  
 449          $groupby = '';
 450  
 451          $pieces = array( 'fields', 'join', 'where', 'orderby', 'limits', 'groupby' );
 452  
 453          /**
 454           * Filters the network query clauses.
 455           *
 456           * @since 4.6.0
 457           *
 458           * @param string[]         $clauses {
 459           *     Associative array of the clauses for the query.
 460           *
 461           *     @type string $fields   The SELECT clause of the query.
 462           *     @type string $join     The JOIN clause of the query.
 463           *     @type string $where    The WHERE clause of the query.
 464           *     @type string $orderby  The ORDER BY clause of the query.
 465           *     @type string $limits   The LIMIT clause of the query.
 466           *     @type string $groupby  The GROUP BY clause of the query.
 467           * }
 468           * @param WP_Network_Query $query   Current instance of WP_Network_Query (passed by reference).
 469           */
 470          $clauses = apply_filters_ref_array( 'networks_clauses', array( compact( $pieces ), &$this ) );
 471  
 472          $fields  = $clauses['fields'] ?? '';
 473          $join    = $clauses['join'] ?? '';
 474          $where   = $clauses['where'] ?? '';
 475          $orderby = $clauses['orderby'] ?? '';
 476          $limits  = $clauses['limits'] ?? '';
 477          $groupby = $clauses['groupby'] ?? '';
 478  
 479          if ( $where ) {
 480              $where = 'WHERE ' . $where;
 481          }
 482  
 483          if ( $groupby ) {
 484              $groupby = 'GROUP BY ' . $groupby;
 485          }
 486  
 487          if ( $orderby ) {
 488              $orderby = "ORDER BY $orderby";
 489          }
 490  
 491          $found_rows = '';
 492          if ( ! $this->query_vars['no_found_rows'] ) {
 493              $found_rows = 'SQL_CALC_FOUND_ROWS';
 494          }
 495  
 496          $this->sql_clauses['select']  = "SELECT $found_rows $fields";
 497          $this->sql_clauses['from']    = "FROM $wpdb->site $join";
 498          $this->sql_clauses['groupby'] = $groupby;
 499          $this->sql_clauses['orderby'] = $orderby;
 500          $this->sql_clauses['limits']  = $limits;
 501  
 502          // Beginning of the string is on a new line to prevent leading whitespace. See https://core.trac.wordpress.org/ticket/56841.
 503          $this->request =
 504              "{$this->sql_clauses['select']}
 505               {$this->sql_clauses['from']}
 506               {$where}
 507               {$this->sql_clauses['groupby']}
 508               {$this->sql_clauses['orderby']}
 509               {$this->sql_clauses['limits']}";
 510  
 511          if ( $this->query_vars['count'] ) {
 512              return (int) $wpdb->get_var( $this->request );
 513          }
 514  
 515          $network_ids = $wpdb->get_col( $this->request );
 516  
 517          return array_map( 'intval', $network_ids );
 518      }
 519  
 520      /**
 521       * Populates found_networks and max_num_pages properties for the current query
 522       * if the limit clause was used.
 523       *
 524       * @since 4.6.0
 525       *
 526       * @global wpdb $wpdb WordPress database abstraction object.
 527       */
 528  	private function set_found_networks() {
 529          global $wpdb;
 530  
 531          if ( $this->query_vars['number'] && ! $this->query_vars['no_found_rows'] ) {
 532              /**
 533               * Filters the query used to retrieve found network count.
 534               *
 535               * @since 4.6.0
 536               *
 537               * @param string           $found_networks_query SQL query. Default 'SELECT FOUND_ROWS()'.
 538               * @param WP_Network_Query $network_query        The `WP_Network_Query` instance.
 539               */
 540              $found_networks_query = apply_filters( 'found_networks_query', 'SELECT FOUND_ROWS()', $this );
 541  
 542              $this->found_networks = (int) $wpdb->get_var( $found_networks_query );
 543          }
 544      }
 545  
 546      /**
 547       * Used internally to generate an SQL string for searching across multiple columns.
 548       *
 549       * @since 4.6.0
 550       *
 551       * @global wpdb $wpdb WordPress database abstraction object.
 552       *
 553       * @param string   $search  Search string.
 554       * @param string[] $columns Array of columns to search.
 555       * @return string Search SQL.
 556       */
 557  	protected function get_search_sql( $search, $columns ) {
 558          global $wpdb;
 559  
 560          $like = '%' . $wpdb->esc_like( $search ) . '%';
 561  
 562          $searches = array();
 563          foreach ( $columns as $column ) {
 564              $searches[] = $wpdb->prepare( "$column LIKE %s", $like );
 565          }
 566  
 567          return '(' . implode( ' OR ', $searches ) . ')';
 568      }
 569  
 570      /**
 571       * Parses and sanitizes 'orderby' keys passed to the network query.
 572       *
 573       * @since 4.6.0
 574       *
 575       * @global wpdb $wpdb WordPress database abstraction object.
 576       *
 577       * @param string $orderby Alias for the field to order by.
 578       * @return string|false Value to used in the ORDER clause. False otherwise.
 579       */
 580  	protected function parse_orderby( $orderby ) {
 581          global $wpdb;
 582  
 583          $allowed_keys = array(
 584              'id',
 585              'domain',
 586              'path',
 587          );
 588  
 589          $parsed = false;
 590          if ( 'network__in' === $orderby ) {
 591              $network__in = implode( ',', array_map( 'absint', $this->query_vars['network__in'] ) );
 592              $parsed      = "FIELD( {$wpdb->site}.id, $network__in )";
 593          } elseif ( 'domain_length' === $orderby || 'path_length' === $orderby ) {
 594              $field  = substr( $orderby, 0, -7 );
 595              $parsed = "CHAR_LENGTH($wpdb->site.$field)";
 596          } elseif ( in_array( $orderby, $allowed_keys, true ) ) {
 597              $parsed = "$wpdb->site.$orderby";
 598          }
 599  
 600          return $parsed;
 601      }
 602  
 603      /**
 604       * Parses an 'order' query variable and cast it to 'ASC' or 'DESC' as necessary.
 605       *
 606       * @since 4.6.0
 607       *
 608       * @param string $order The 'order' query variable.
 609       * @return string The sanitized 'order' query variable.
 610       */
 611  	protected function parse_order( $order ) {
 612          if ( ! is_string( $order ) || empty( $order ) ) {
 613              return 'ASC';
 614          }
 615  
 616          if ( 'ASC' === strtoupper( $order ) ) {
 617              return 'ASC';
 618          } else {
 619              return 'DESC';
 620          }
 621      }
 622  }


Generated : Sun Aug 2 08:20:19 2026 Cross-referenced by PHPXref