| [ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * WordPress Plugin Administration API: WP_Plugin_Dependencies class 4 * 5 * @package WordPress 6 * @subpackage Administration 7 * @since 6.5.0 8 */ 9 10 /** 11 * Core class for installing plugin dependencies. 12 * 13 * It is designed to add plugin dependencies as designated in the 14 * `Requires Plugins` header to a new view in the plugins install page. 15 */ 16 class WP_Plugin_Dependencies { 17 18 /** 19 * Holds 'get_plugins()'. 20 * 21 * @since 6.5.0 22 * 23 * @var array 24 */ 25 protected static $plugins; 26 27 /** 28 * Holds plugin directory names to compare with cache. 29 * 30 * @since 6.5.0 31 * 32 * @var array 33 */ 34 protected static $plugin_dirnames; 35 36 /** 37 * Holds sanitized plugin dependency slugs. 38 * 39 * Keyed on the dependent plugin's filepath, 40 * relative to the plugins directory. 41 * 42 * @since 6.5.0 43 * 44 * @var array 45 */ 46 protected static $dependencies; 47 48 /** 49 * Holds an array of sanitized plugin dependency slugs. 50 * 51 * @since 6.5.0 52 * 53 * @var array 54 */ 55 protected static $dependency_slugs; 56 57 /** 58 * Holds an array of dependent plugin slugs. 59 * 60 * Keyed on the dependent plugin's filepath, 61 * relative to the plugins directory. 62 * 63 * @since 6.5.0 64 * 65 * @var array 66 */ 67 protected static $dependent_slugs; 68 69 /** 70 * Holds 'plugins_api()' data for plugin dependencies. 71 * 72 * @since 6.5.0 73 * 74 * @var array 75 */ 76 protected static $dependency_api_data; 77 78 /** 79 * Holds plugin dependency filepaths, relative to the plugins directory. 80 * 81 * Keyed on the dependency's slug. 82 * 83 * @since 6.5.0 84 * 85 * @var string[] 86 */ 87 protected static $dependency_filepaths; 88 89 /** 90 * An array of circular dependency pairings. 91 * 92 * @since 6.5.0 93 * 94 * @var array[] 95 */ 96 protected static $circular_dependencies_pairs; 97 98 /** 99 * An array of circular dependency slugs. 100 * 101 * @since 6.5.0 102 * 103 * @var string[] 104 */ 105 protected static $circular_dependencies_slugs; 106 107 /** 108 * Whether Plugin Dependencies have been initialized. 109 * 110 * @since 6.5.0 111 * 112 * @var bool 113 */ 114 protected static $initialized = false; 115 116 /** 117 * Initializes by fetching plugin header and plugin API data. 118 * 119 * @since 6.5.0 120 */ 121 public static function initialize() { 122 if ( false === self::$initialized ) { 123 self::read_dependencies_from_plugin_headers(); 124 self::get_dependency_api_data(); 125 self::$initialized = true; 126 } 127 } 128 129 /** 130 * Determines whether the plugin has plugins that depend on it. 131 * 132 * @since 6.5.0 133 * 134 * @param string $plugin_file The plugin's filepath, relative to the plugins directory. 135 * @return bool Whether the plugin has plugins that depend on it. 136 */ 137 public static function has_dependents( $plugin_file ) { 138 return in_array( self::convert_to_slug( $plugin_file ), (array) self::$dependency_slugs, true ); 139 } 140 141 /** 142 * Determines whether the plugin has plugin dependencies. 143 * 144 * @since 6.5.0 145 * 146 * @param string $plugin_file The plugin's filepath, relative to the plugins directory. 147 * @return bool Whether a plugin has plugin dependencies. 148 */ 149 public static function has_dependencies( $plugin_file ) { 150 return isset( self::$dependencies[ $plugin_file ] ); 151 } 152 153 /** 154 * Determines whether the plugin has active dependents. 155 * 156 * @since 6.5.0 157 * 158 * @param string $plugin_file The plugin's filepath, relative to the plugins directory. 159 * @return bool Whether the plugin has active dependents. 160 */ 161 public static function has_active_dependents( $plugin_file ) { 162 require_once ABSPATH . 'wp-admin/includes/plugin.php'; 163 164 $dependents = self::get_dependents( self::convert_to_slug( $plugin_file ) ); 165 foreach ( $dependents as $dependent ) { 166 if ( is_plugin_active( $dependent ) ) { 167 return true; 168 } 169 } 170 171 return false; 172 } 173 174 /** 175 * Gets filepaths of plugins that require the dependency. 176 * 177 * @since 6.5.0 178 * 179 * @param string $slug The dependency's slug. 180 * @return array An array of dependent plugin filepaths, relative to the plugins directory. 181 */ 182 public static function get_dependents( $slug ) { 183 $dependents = array(); 184 185 foreach ( (array) self::$dependencies as $dependent => $dependencies ) { 186 if ( in_array( $slug, $dependencies, true ) ) { 187 $dependents[] = $dependent; 188 } 189 } 190 191 return $dependents; 192 } 193 194 /** 195 * Gets the slugs of plugins that the dependent requires. 196 * 197 * @since 6.5.0 198 * 199 * @param string $plugin_file The dependent plugin's filepath, relative to the plugins directory. 200 * @return array An array of dependency plugin slugs. 201 */ 202 public static function get_dependencies( $plugin_file ) { 203 return self::$dependencies[ $plugin_file ] ?? array(); 204 } 205 206 /** 207 * Gets a dependent plugin's filepath. 208 * 209 * @since 6.5.0 210 * 211 * @param string $slug The dependent plugin's slug. 212 * @return string|false The dependent plugin's filepath, relative to the plugins directory, 213 * or false if the plugin has no dependencies. 214 */ 215 public static function get_dependent_filepath( $slug ) { 216 $filepath = array_search( $slug, self::$dependent_slugs, true ); 217 218 return $filepath ? $filepath : false; 219 } 220 221 /** 222 * Determines whether the plugin has unmet dependencies. 223 * 224 * @since 6.5.0 225 * 226 * @param string $plugin_file The plugin's filepath, relative to the plugins directory. 227 * @return bool Whether the plugin has unmet dependencies. 228 */ 229 public static function has_unmet_dependencies( $plugin_file ) { 230 if ( ! isset( self::$dependencies[ $plugin_file ] ) ) { 231 return false; 232 } 233 234 require_once ABSPATH . 'wp-admin/includes/plugin.php'; 235 236 foreach ( self::$dependencies[ $plugin_file ] as $dependency ) { 237 $dependency_filepath = self::get_dependency_filepath( $dependency ); 238 239 if ( false === $dependency_filepath || is_plugin_inactive( $dependency_filepath ) ) { 240 return true; 241 } 242 } 243 244 return false; 245 } 246 247 /** 248 * Determines whether the plugin has a circular dependency. 249 * 250 * @since 6.5.0 251 * 252 * @param string $plugin_file The plugin's filepath, relative to the plugins directory. 253 * @return bool Whether the plugin has a circular dependency. 254 */ 255 public static function has_circular_dependency( $plugin_file ) { 256 if ( ! is_array( self::$circular_dependencies_slugs ) ) { 257 self::get_circular_dependencies(); 258 } 259 260 if ( ! empty( self::$circular_dependencies_slugs ) ) { 261 $slug = self::convert_to_slug( $plugin_file ); 262 263 if ( in_array( $slug, self::$circular_dependencies_slugs, true ) ) { 264 return true; 265 } 266 } 267 268 return false; 269 } 270 271 /** 272 * Gets the names of plugins that require the plugin. 273 * 274 * @since 6.5.0 275 * 276 * @param string $plugin_file The plugin's filepath, relative to the plugins directory. 277 * @return array An array of dependent names. 278 */ 279 public static function get_dependent_names( $plugin_file ) { 280 $dependent_names = array(); 281 $plugins = self::get_plugins(); 282 $slug = self::convert_to_slug( $plugin_file ); 283 284 foreach ( self::get_dependents( $slug ) as $dependent ) { 285 $dependent_names[ $dependent ] = $plugins[ $dependent ]['Name']; 286 } 287 sort( $dependent_names ); 288 289 return $dependent_names; 290 } 291 292 /** 293 * Gets the names of plugins required by the plugin. 294 * 295 * @since 6.5.0 296 * 297 * @param string $plugin_file The dependent plugin's filepath, relative to the plugins directory. 298 * @return array An array of dependency names. 299 */ 300 public static function get_dependency_names( $plugin_file ) { 301 $dependency_api_data = self::get_dependency_api_data(); 302 $dependencies = self::get_dependencies( $plugin_file ); 303 $plugins = self::get_plugins(); 304 305 $dependency_names = array(); 306 foreach ( $dependencies as $dependency ) { 307 // Use the name if it's available, otherwise fall back to the slug. 308 if ( isset( $dependency_api_data[ $dependency ]['name'] ) ) { 309 $name = $dependency_api_data[ $dependency ]['name']; 310 } else { 311 $dependency_filepath = self::get_dependency_filepath( $dependency ); 312 if ( false !== $dependency_filepath ) { 313 $name = $plugins[ $dependency_filepath ]['Name']; 314 } else { 315 $name = $dependency; 316 } 317 } 318 319 $dependency_names[ $dependency ] = $name; 320 } 321 322 return $dependency_names; 323 } 324 325 /** 326 * Gets the filepath for a dependency, relative to the plugin's directory. 327 * 328 * @since 6.5.0 329 * 330 * @param string $slug The dependency's slug. 331 * @return string|false If installed, the dependency's filepath relative to the plugins directory, otherwise false. 332 */ 333 public static function get_dependency_filepath( $slug ) { 334 $dependency_filepaths = self::get_dependency_filepaths(); 335 336 if ( ! isset( $dependency_filepaths[ $slug ] ) ) { 337 return false; 338 } 339 340 return $dependency_filepaths[ $slug ]; 341 } 342 343 /** 344 * Returns API data for the dependency. 345 * 346 * @since 6.5.0 347 * 348 * @param string $slug The dependency's slug. 349 * @return array|false The dependency's API data on success, otherwise false. 350 */ 351 public static function get_dependency_data( $slug ) { 352 $dependency_api_data = self::get_dependency_api_data(); 353 return $dependency_api_data[ $slug ] ?? false; 354 } 355 356 /** 357 * Displays an admin notice if dependencies are not installed. 358 * 359 * @since 6.5.0 360 */ 361 public static function display_admin_notice_for_unmet_dependencies() { 362 if ( in_array( false, self::get_dependency_filepaths(), true ) ) { 363 $error_message = __( 'Some required plugins are missing or inactive.' ); 364 365 if ( is_multisite() ) { 366 if ( current_user_can( 'manage_network_plugins' ) ) { 367 $error_message .= ' ' . sprintf( 368 /* translators: %s: Link to the network plugins page. */ 369 __( '<a href="%s">Manage plugins</a>.' ), 370 esc_url( network_admin_url( 'plugins.php' ) ) 371 ); 372 } else { 373 $error_message .= ' ' . __( 'Please contact your network administrator.' ); 374 } 375 } elseif ( 'plugins' !== get_current_screen()->base ) { 376 $error_message .= ' ' . sprintf( 377 /* translators: %s: Link to the plugins page. */ 378 __( '<a href="%s">Manage plugins</a>.' ), 379 esc_url( admin_url( 'plugins.php' ) ) 380 ); 381 } 382 383 wp_admin_notice( 384 $error_message, 385 array( 386 'type' => 'warning', 387 ) 388 ); 389 } 390 } 391 392 /** 393 * Displays an admin notice if circular dependencies are installed. 394 * 395 * @since 6.5.0 396 */ 397 public static function display_admin_notice_for_circular_dependencies() { 398 $circular_dependencies = self::get_circular_dependencies(); 399 if ( ! empty( $circular_dependencies ) && count( $circular_dependencies ) > 1 ) { 400 $circular_dependencies = array_unique( $circular_dependencies, SORT_REGULAR ); 401 $plugins = self::get_plugins(); 402 $plugin_dirnames = self::get_plugin_dirnames(); 403 404 // Build output lines. 405 $circular_dependency_lines = ''; 406 foreach ( $circular_dependencies as $circular_dependency ) { 407 $first_filepath = $plugin_dirnames[ $circular_dependency[0] ]; 408 $second_filepath = $plugin_dirnames[ $circular_dependency[1] ]; 409 $circular_dependency_lines .= sprintf( 410 /* translators: 1: First plugin name, 2: Second plugin name. */ 411 '<li>' . _x( '%1$s requires %2$s', 'The first plugin requires the second plugin.' ) . '</li>', 412 '<strong>' . esc_html( $plugins[ $first_filepath ]['Name'] ) . '</strong>', 413 '<strong>' . esc_html( $plugins[ $second_filepath ]['Name'] ) . '</strong>' 414 ); 415 } 416 417 wp_admin_notice( 418 sprintf( 419 '<p>%1$s</p><ul>%2$s</ul><p>%3$s</p>', 420 __( 'These plugins cannot be activated because their requirements are invalid.' ), 421 $circular_dependency_lines, 422 __( 'Please contact the plugin authors for more information.' ) 423 ), 424 array( 425 'type' => 'warning', 426 'paragraph_wrap' => false, 427 ) 428 ); 429 } 430 } 431 432 /** 433 * Checks plugin dependencies after a plugin is installed via AJAX. 434 * 435 * @since 6.5.0 436 */ 437 public static function check_plugin_dependencies_during_ajax() { 438 check_ajax_referer( 'updates' ); 439 440 if ( empty( $_POST['slug'] ) ) { 441 wp_send_json_error( 442 array( 443 'slug' => '', 444 'pluginName' => '', 445 'errorCode' => 'no_plugin_specified', 446 'errorMessage' => __( 'No plugin specified.' ), 447 ) 448 ); 449 } 450 451 $slug = sanitize_key( wp_unslash( $_POST['slug'] ) ); 452 $status = array( 'slug' => $slug ); 453 454 self::get_plugins(); 455 self::get_plugin_dirnames(); 456 457 if ( ! isset( self::$plugin_dirnames[ $slug ] ) ) { 458 $status['errorCode'] = 'plugin_not_installed'; 459 $status['errorMessage'] = __( 'The plugin is not installed.' ); 460 wp_send_json_error( $status ); 461 } 462 463 $plugin_file = self::$plugin_dirnames[ $slug ]; 464 $status['pluginName'] = self::$plugins[ $plugin_file ]['Name']; 465 $status['plugin'] = $plugin_file; 466 467 if ( current_user_can( 'activate_plugin', $plugin_file ) && is_plugin_inactive( $plugin_file ) ) { 468 $status['activateUrl'] = add_query_arg( 469 array( 470 '_wpnonce' => wp_create_nonce( 'activate-plugin_' . $plugin_file ), 471 'action' => 'activate', 472 'plugin' => $plugin_file, 473 ), 474 is_multisite() ? network_admin_url( 'plugins.php' ) : admin_url( 'plugins.php' ) 475 ); 476 } 477 478 if ( is_multisite() && current_user_can( 'manage_network_plugins' ) ) { 479 $status['activateUrl'] = add_query_arg( array( 'networkwide' => 1 ), $status['activateUrl'] ); 480 } 481 482 self::initialize(); 483 $dependencies = self::get_dependencies( $plugin_file ); 484 if ( empty( $dependencies ) ) { 485 $status['message'] = __( 'The plugin has no required plugins.' ); 486 wp_send_json_success( $status ); 487 } 488 489 require_once ABSPATH . 'wp-admin/includes/plugin.php'; 490 491 $inactive_dependencies = array(); 492 foreach ( $dependencies as $dependency ) { 493 if ( false === self::$plugin_dirnames[ $dependency ] || is_plugin_inactive( self::$plugin_dirnames[ $dependency ] ) ) { 494 $inactive_dependencies[] = $dependency; 495 } 496 } 497 498 if ( ! empty( $inactive_dependencies ) ) { 499 $inactive_dependency_names = array_map( 500 function ( $dependency ) { 501 if ( isset( self::$dependency_api_data[ $dependency ]['Name'] ) ) { 502 $inactive_dependency_name = self::$dependency_api_data[ $dependency ]['Name']; 503 } else { 504 $inactive_dependency_name = $dependency; 505 } 506 return $inactive_dependency_name; 507 }, 508 $inactive_dependencies 509 ); 510 511 $status['errorCode'] = 'inactive_dependencies'; 512 $status['errorMessage'] = sprintf( 513 /* translators: %s: A list of inactive dependency plugin names. */ 514 __( 'The following plugins must be activated first: %s.' ), 515 implode( ', ', $inactive_dependency_names ) 516 ); 517 $status['errorData'] = array_combine( $inactive_dependencies, $inactive_dependency_names ); 518 519 wp_send_json_error( $status ); 520 } 521 522 $status['message'] = __( 'All required plugins are installed and activated.' ); 523 wp_send_json_success( $status ); 524 } 525 526 /** 527 * Gets data for installed plugins. 528 * 529 * @since 6.5.0 530 * 531 * @return array An array of plugin data. 532 */ 533 protected static function get_plugins() { 534 if ( is_array( self::$plugins ) ) { 535 return self::$plugins; 536 } 537 538 require_once ABSPATH . 'wp-admin/includes/plugin.php'; 539 self::$plugins = get_plugins(); 540 541 return self::$plugins; 542 } 543 544 /** 545 * Reads and stores dependency slugs from a plugin's 'Requires Plugins' header. 546 * 547 * @since 6.5.0 548 */ 549 protected static function read_dependencies_from_plugin_headers() { 550 self::$dependencies = array(); 551 self::$dependency_slugs = array(); 552 self::$dependent_slugs = array(); 553 $plugins = self::get_plugins(); 554 foreach ( $plugins as $plugin => $header ) { 555 if ( '' === $header['RequiresPlugins'] ) { 556 continue; 557 } 558 559 $dependency_slugs = self::sanitize_dependency_slugs( $header['RequiresPlugins'] ); 560 self::$dependencies[ $plugin ] = $dependency_slugs; 561 self::$dependency_slugs = array_merge( self::$dependency_slugs, $dependency_slugs ); 562 563 $dependent_slug = self::convert_to_slug( $plugin ); 564 self::$dependent_slugs[ $plugin ] = $dependent_slug; 565 } 566 self::$dependency_slugs = array_unique( self::$dependency_slugs ); 567 } 568 569 /** 570 * Sanitizes slugs. 571 * 572 * @since 6.5.0 573 * 574 * @param string $slugs A comma-separated string of plugin dependency slugs. 575 * @return array An array of sanitized plugin dependency slugs. 576 */ 577 protected static function sanitize_dependency_slugs( $slugs ) { 578 $sanitized_slugs = array(); 579 $slugs = explode( ',', $slugs ); 580 581 foreach ( $slugs as $slug ) { 582 $slug = trim( $slug ); 583 584 /** 585 * Filters a plugin dependency's slug before matching to 586 * the WordPress.org slug format. 587 * 588 * Can be used to switch between free and premium plugin slugs, for example. 589 * 590 * @since 6.5.0 591 * 592 * @param string $slug The slug. 593 */ 594 $slug = apply_filters( 'wp_plugin_dependencies_slug', $slug ); 595 596 // Match to WordPress.org slug format. 597 if ( preg_match( '/^[a-z0-9]+(-[a-z0-9]+)*$/mu', $slug ) ) { 598 $sanitized_slugs[] = $slug; 599 } 600 } 601 $sanitized_slugs = array_unique( $sanitized_slugs ); 602 sort( $sanitized_slugs ); 603 604 return $sanitized_slugs; 605 } 606 607 /** 608 * Gets the filepath of installed dependencies. 609 * If a dependency is not installed, the filepath defaults to false. 610 * 611 * @since 6.5.0 612 * 613 * @return array An array of install dependencies filepaths, relative to the plugins directory. 614 */ 615 protected static function get_dependency_filepaths() { 616 if ( is_array( self::$dependency_filepaths ) ) { 617 return self::$dependency_filepaths; 618 } 619 620 if ( null === self::$dependency_slugs ) { 621 return array(); 622 } 623 624 self::$dependency_filepaths = array(); 625 626 $plugin_dirnames = self::get_plugin_dirnames(); 627 foreach ( self::$dependency_slugs as $slug ) { 628 if ( isset( $plugin_dirnames[ $slug ] ) ) { 629 self::$dependency_filepaths[ $slug ] = $plugin_dirnames[ $slug ]; 630 continue; 631 } 632 633 self::$dependency_filepaths[ $slug ] = false; 634 } 635 636 return self::$dependency_filepaths; 637 } 638 639 /** 640 * Retrieves and stores dependency plugin data from the WordPress.org Plugin API. 641 * 642 * @since 6.5.0 643 * 644 * @global string $pagenow The filename of the current screen. 645 * 646 * @return array|void An array of dependency API data, or void on early exit. 647 */ 648 protected static function get_dependency_api_data() { 649 global $pagenow; 650 651 if ( ! is_admin() || ( 'plugins.php' !== $pagenow && 'plugin-install.php' !== $pagenow ) ) { 652 return; 653 } 654 655 if ( is_array( self::$dependency_api_data ) ) { 656 return self::$dependency_api_data; 657 } 658 659 $plugins = self::get_plugins(); 660 self::$dependency_api_data = (array) get_site_transient( 'wp_plugin_dependencies_plugin_data' ); 661 foreach ( self::$dependency_slugs as $slug ) { 662 // Set transient for individual data, remove from self::$dependency_api_data if transient expired. 663 if ( ! get_site_transient( "wp_plugin_dependencies_plugin_timeout_{$slug}" ) ) { 664 unset( self::$dependency_api_data[ $slug ] ); 665 set_site_transient( "wp_plugin_dependencies_plugin_timeout_{$slug}", true, 12 * HOUR_IN_SECONDS ); 666 } 667 668 if ( isset( self::$dependency_api_data[ $slug ] ) ) { 669 if ( false === self::$dependency_api_data[ $slug ] ) { 670 $dependency_file = self::get_dependency_filepath( $slug ); 671 672 if ( false === $dependency_file ) { 673 self::$dependency_api_data[ $slug ] = array( 'Name' => $slug ); 674 } else { 675 self::$dependency_api_data[ $slug ] = array( 'Name' => $plugins[ $dependency_file ]['Name'] ); 676 } 677 continue; 678 } 679 680 // Don't hit the Plugin API if data exists. 681 if ( ! empty( self::$dependency_api_data[ $slug ]['last_updated'] ) ) { 682 continue; 683 } 684 } 685 686 if ( ! function_exists( 'plugins_api' ) ) { 687 require_once ABSPATH . 'wp-admin/includes/plugin-install.php'; 688 } 689 690 $information = plugins_api( 691 'plugin_information', 692 array( 693 'slug' => $slug, 694 'fields' => array( 695 'short_description' => true, 696 'icons' => true, 697 ), 698 ) 699 ); 700 701 if ( is_wp_error( $information ) ) { 702 continue; 703 } 704 705 self::$dependency_api_data[ $slug ] = (array) $information; 706 // plugins_api() returns 'name' not 'Name'. 707 self::$dependency_api_data[ $slug ]['Name'] = self::$dependency_api_data[ $slug ]['name']; 708 set_site_transient( 'wp_plugin_dependencies_plugin_data', self::$dependency_api_data, 0 ); 709 } 710 711 // Remove from self::$dependency_api_data if slug no longer a dependency. 712 $differences = array_diff( array_keys( self::$dependency_api_data ), self::$dependency_slugs ); 713 foreach ( $differences as $difference ) { 714 unset( self::$dependency_api_data[ $difference ] ); 715 } 716 717 ksort( self::$dependency_api_data ); 718 // Remove empty elements. 719 self::$dependency_api_data = array_filter( self::$dependency_api_data ); 720 set_site_transient( 'wp_plugin_dependencies_plugin_data', self::$dependency_api_data, 0 ); 721 722 return self::$dependency_api_data; 723 } 724 725 /** 726 * Gets plugin directory names. 727 * 728 * @since 6.5.0 729 * 730 * @return array An array of plugin directory names. 731 */ 732 protected static function get_plugin_dirnames() { 733 if ( is_array( self::$plugin_dirnames ) ) { 734 return self::$plugin_dirnames; 735 } 736 737 self::$plugin_dirnames = array(); 738 739 $plugin_files = array_keys( self::get_plugins() ); 740 foreach ( $plugin_files as $plugin_file ) { 741 $slug = self::convert_to_slug( $plugin_file ); 742 self::$plugin_dirnames[ $slug ] = $plugin_file; 743 } 744 745 return self::$plugin_dirnames; 746 } 747 748 /** 749 * Gets circular dependency data. 750 * 751 * @since 6.5.0 752 * 753 * @return array[] An array of circular dependency pairings. 754 */ 755 protected static function get_circular_dependencies() { 756 if ( is_array( self::$circular_dependencies_pairs ) ) { 757 return self::$circular_dependencies_pairs; 758 } 759 760 if ( null === self::$dependencies ) { 761 return array(); 762 } 763 764 self::$circular_dependencies_slugs = array(); 765 766 self::$circular_dependencies_pairs = array(); 767 foreach ( self::$dependencies as $dependent => $dependencies ) { 768 /* 769 * $dependent is in 'a/a.php' format. Dependencies are stored as slugs, i.e. 'a'. 770 * 771 * Convert $dependent to slug format for checking. 772 */ 773 $dependent_slug = self::convert_to_slug( $dependent ); 774 775 self::$circular_dependencies_pairs = array_merge( 776 self::$circular_dependencies_pairs, 777 self::check_for_circular_dependencies( array( $dependent_slug ), $dependencies ) 778 ); 779 } 780 781 return self::$circular_dependencies_pairs; 782 } 783 784 /** 785 * Checks for circular dependencies. 786 * 787 * @since 6.5.0 788 * 789 * @param array $dependents Array of dependent plugins. 790 * @param array $dependencies Array of plugins dependencies. 791 * @return array A circular dependency pairing, or an empty array if none exists. 792 */ 793 protected static function check_for_circular_dependencies( $dependents, $dependencies ) { 794 $circular_dependencies_pairs = array(); 795 796 // Check for a self-dependency. 797 $dependents_location_in_its_own_dependencies = array_intersect( $dependents, $dependencies ); 798 if ( ! empty( $dependents_location_in_its_own_dependencies ) ) { 799 foreach ( $dependents_location_in_its_own_dependencies as $self_dependency ) { 800 self::$circular_dependencies_slugs[] = $self_dependency; 801 $circular_dependencies_pairs[] = array( $self_dependency, $self_dependency ); 802 803 // No need to check for itself again. 804 unset( $dependencies[ array_search( $self_dependency, $dependencies, true ) ] ); 805 } 806 } 807 808 /* 809 * Check each dependency to see: 810 * 1. If it has dependencies. 811 * 2. If its list of dependencies includes one of its own dependents. 812 */ 813 foreach ( $dependencies as $dependency ) { 814 // Check if the dependency is also a dependent. 815 $dependency_location_in_dependents = array_search( $dependency, self::$dependent_slugs, true ); 816 817 if ( false !== $dependency_location_in_dependents ) { 818 $dependencies_of_the_dependency = self::$dependencies[ $dependency_location_in_dependents ]; 819 820 foreach ( $dependents as $dependent ) { 821 // Check if its dependencies includes one of its own dependents. 822 $dependent_location_in_dependency_dependencies = array_search( 823 $dependent, 824 $dependencies_of_the_dependency, 825 true 826 ); 827 828 if ( false !== $dependent_location_in_dependency_dependencies ) { 829 self::$circular_dependencies_slugs[] = $dependent; 830 self::$circular_dependencies_slugs[] = $dependency; 831 $circular_dependencies_pairs[] = array( $dependent, $dependency ); 832 833 // Remove the dependent from its dependency's dependencies. 834 unset( $dependencies_of_the_dependency[ $dependent_location_in_dependency_dependencies ] ); 835 } 836 } 837 838 $dependents[] = $dependency; 839 840 /* 841 * Now check the dependencies of the dependency's dependencies for the dependent. 842 * 843 * Yes, that does make sense. 844 */ 845 $circular_dependencies_pairs = array_merge( 846 $circular_dependencies_pairs, 847 self::check_for_circular_dependencies( $dependents, array_unique( $dependencies_of_the_dependency ) ) 848 ); 849 } 850 } 851 852 return $circular_dependencies_pairs; 853 } 854 855 /** 856 * Converts a plugin filepath to a slug. 857 * 858 * @since 6.5.0 859 * 860 * @param string $plugin_file The plugin's filepath, relative to the plugins directory. 861 * @return string The plugin's slug. 862 */ 863 protected static function convert_to_slug( $plugin_file ) { 864 if ( 'hello.php' === $plugin_file ) { 865 return 'hello-dolly'; 866 } 867 return str_contains( $plugin_file, '/' ) ? dirname( $plugin_file ) : str_replace( '.php', '', $plugin_file ); 868 } 869 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Sat Jan 17 08:20:04 2026 | Cross-referenced by PHPXref |