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