Changeset 57658
- Timestamp:
- 02/20/2024 07:25:38 AM (10 months ago)
- Location:
- trunk
- Files:
-
- 11 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-admin/includes/class-plugin-upgrader.php
r57545 r57658 155 155 // Force refresh of plugin update information. 156 156 wp_clean_plugins_cache( $parsed_args['clear_update_cache'] ); 157 158 $all_plugin_data = get_option( 'plugin_data', array() );159 $plugin_file = $this->new_plugin_data['file'];160 unset( $this->new_plugin_data['file'] );161 $all_plugin_data[ $plugin_file ] = $this->new_plugin_data;162 update_option( 'plugin_data', $all_plugin_data );163 157 164 158 if ( $parsed_args['overwrite_package'] ) { … … 489 483 $info = get_plugin_data( $file, false, false ); 490 484 if ( ! empty( $info['Name'] ) ) { 491 $basename = basename( $file ); 492 $dirname = basename( dirname( $file ) ); 493 494 if ( '.' === $dirname ) { 495 $plugin_file = $basename; 496 } else { 497 $plugin_file = "$dirname/$basename"; 498 } 499 $this->new_plugin_data = ( $info ); 500 $this->new_plugin_data['file'] = $plugin_file; 485 $this->new_plugin_data = $info; 501 486 break; 502 487 } -
trunk/src/wp-admin/includes/plugin-install.php
r57545 r57658 928 928 // Determine the status of plugin dependencies. 929 929 $installed_plugins = get_plugins(); 930 $active_plugins = get_option( 'active_plugins' );930 $active_plugins = get_option( 'active_plugins', array() ); 931 931 $plugin_dependencies_count = count( $requires_plugins ); 932 932 $installed_plugin_dependencies_count = 0; -
trunk/src/wp-admin/includes/plugin.php
r57644 r57658 334 334 } 335 335 336 $new_plugin_data = array();337 336 foreach ( $plugin_files as $plugin_file ) { 338 337 if ( ! is_readable( "$plugin_root/$plugin_file" ) ) { … … 347 346 } 348 347 349 $new_plugin_file = str_replace(350 trailingslashit( WP_PLUGIN_DIR ),351 '',352 "$plugin_root/$plugin_file"353 );354 355 $new_plugin_data[ $new_plugin_file ] = $plugin_data;356 348 $wp_plugins[ plugin_basename( $plugin_file ) ] = $plugin_data; 357 349 } … … 361 353 $cache_plugins[ $plugin_folder ] = $wp_plugins; 362 354 wp_cache_set( 'plugins', $cache_plugins, 'plugins' ); 363 364 if ( ! wp_installing() ) {365 update_option( 'plugin_data', $new_plugin_data );366 }367 355 368 356 return $wp_plugins; … … 976 964 977 965 $plugin_translations = wp_get_installed_translations( 'plugins' ); 978 $all_plugin_data = get_option( 'plugin_data', array() );979 966 980 967 $errors = array(); … … 1021 1008 continue; 1022 1009 } 1023 unset( $all_plugin_data[ $plugin_file ] );1024 1010 1025 1011 $plugin_slug = dirname( $plugin_file ); … … 1070 1056 return new WP_Error( 'could_not_remove_plugin', sprintf( $message, implode( ', ', $errors ) ) ); 1071 1057 } 1072 update_option( 'plugin_data', $all_plugin_data );1073 1058 1074 1059 return true; … … 1215 1200 } 1216 1201 1202 WP_Plugin_Dependencies::initialize(); 1203 1217 1204 if ( WP_Plugin_Dependencies::has_unmet_dependencies( $plugin ) ) { 1218 1205 $dependencies = WP_Plugin_Dependencies::get_dependencies( $plugin ); -
trunk/src/wp-admin/plugin-install.php
r57545 r57658 136 136 require_once ABSPATH . 'wp-admin/admin-header.php'; 137 137 138 WP_Plugin_Dependencies::initialize(); 138 139 WP_Plugin_Dependencies::display_admin_notice_for_unmet_dependencies(); 139 WP_Plugin_Dependencies::display_admin_notice_for_deactivated_dependents();140 140 WP_Plugin_Dependencies::display_admin_notice_for_circular_dependencies(); 141 141 ?> -
trunk/src/wp-admin/plugins.php
r57586 r57658 40 40 41 41 wp_enqueue_script( 'updates' ); 42 43 WP_Plugin_Dependencies::initialize(); 42 44 43 45 if ( $action ) { … … 742 744 743 745 <?php WP_Plugin_Dependencies::display_admin_notice_for_unmet_dependencies(); ?> 744 <?php WP_Plugin_Dependencies::display_admin_notice_for_deactivated_dependents(); ?>745 746 <?php WP_Plugin_Dependencies::display_admin_notice_for_circular_dependencies(); ?> 746 747 <div class="wrap"> -
trunk/src/wp-includes/class-wp-plugin-dependencies.php
r57617 r57658 106 106 107 107 /** 108 * Initializes by fetching plugin header and plugin API data, 109 * and deactivating dependents with unmet dependencies. 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. 110 118 * 111 119 * @since 6.5.0 112 120 */ 113 121 public static function initialize() { 114 self::read_dependencies_from_plugin_headers(); 115 self::get_dependency_api_data(); 116 self::deactivate_dependents_with_unmet_dependencies(); 122 if ( false === self::$initialized ) { 123 self::read_dependencies_from_plugin_headers(); 124 self::get_dependency_api_data(); 125 self::$initialized = true; 126 } 117 127 } 118 128 … … 126 136 */ 127 137 public static function has_dependents( $plugin_file ) { 128 return in_array( self::convert_to_slug( $plugin_file ), self::$dependency_slugs, true );138 return in_array( self::convert_to_slug( $plugin_file ), (array) self::$dependency_slugs, true ); 129 139 } 130 140 … … 173 183 $dependents = array(); 174 184 175 foreach ( self::$dependencies as $dependent => $dependencies ) {185 foreach ( (array) self::$dependencies as $dependent => $dependencies ) { 176 186 if ( in_array( $slug, $dependencies, true ) ) { 177 187 $dependents[] = $dependent; … … 370 380 371 381 /** 372 * Displays an admin notice if dependencies have been deactivated.373 *374 * @since 6.5.0375 */376 public static function display_admin_notice_for_deactivated_dependents() {377 /*378 * Plugin deactivated if dependencies not met.379 * Transient on a 10 second timeout.380 */381 $deactivate_requires = get_site_transient( 'wp_plugin_dependencies_deactivated_plugins' );382 if ( ! empty( $deactivate_requires ) ) {383 $deactivated_plugins = '';384 foreach ( $deactivate_requires as $deactivated ) {385 $deactivated_plugins .= '<li>' . esc_html( self::$plugins[ $deactivated ]['Name'] ) . '</li>';386 }387 wp_admin_notice(388 sprintf(389 /* translators: 1: plugin names */390 __( 'The following plugin(s) have been deactivated due to uninstalled or inactive dependencies: %s' ),391 "<ul>$deactivated_plugins</ul>"392 ),393 array(394 'type' => 'error',395 'dismissible' => true,396 )397 );398 }399 }400 401 /**402 382 * Displays an admin notice if circular dependencies are installed. 403 383 * … … 544 524 } 545 525 546 $all_plugin_data = get_option( 'plugin_data', array() ); 547 548 if ( empty( $all_plugin_data ) ) { 549 require_once ABSPATH . '/wp-admin/includes/plugin.php'; 550 $all_plugin_data = get_plugins(); 551 } 552 553 self::$plugins = $all_plugin_data; 526 require_once ABSPATH . '/wp-admin/includes/plugin.php'; 527 self::$plugins = get_plugins(); 554 528 555 529 return self::$plugins; … … 622 596 623 597 /** 624 * Gets plugin filepaths for active plugins that depend on the dependency.625 *626 * Recurses for each dependent that is also a dependency.627 *628 * @param string $plugin_file The dependency's filepath, relative to the plugin directory.629 * @return string[] An array of active dependent plugin filepaths, relative to the plugin directory.630 */631 protected static function get_active_dependents_in_dependency_tree( $plugin_file ) {632 $all_dependents = array();633 $dependents = self::get_dependents( self::convert_to_slug( $plugin_file ) );634 635 if ( empty( $dependents ) ) {636 return $all_dependents;637 }638 639 require_once ABSPATH . '/wp-admin/includes/plugin.php';640 foreach ( $dependents as $dependent ) {641 if ( is_plugin_active( $dependent ) ) {642 $all_dependents[] = $dependent;643 $all_dependents = array_merge(644 $all_dependents,645 self::get_active_dependents_in_dependency_tree( $dependent )646 );647 }648 }649 650 return $all_dependents;651 }652 653 /**654 * Deactivates dependent plugins with unmet dependencies.655 *656 * @since 6.5.0657 */658 protected static function deactivate_dependents_with_unmet_dependencies() {659 $dependents_to_deactivate = array();660 $circular_dependencies = array_reduce(661 self::get_circular_dependencies(),662 function ( $all_circular, $circular_pair ) {663 return array_merge( $all_circular, $circular_pair );664 },665 array()666 );667 668 require_once ABSPATH . '/wp-admin/includes/plugin.php';669 foreach ( self::$dependencies as $dependent => $dependencies ) {670 // Skip dependents that are no longer installed or aren't active.671 if ( ! array_key_exists( $dependent, self::$plugins ) || is_plugin_inactive( $dependent ) ) {672 continue;673 }674 675 // Skip plugins within a circular dependency tree or plugins that have no unmet dependencies.676 if ( in_array( $dependent, $circular_dependencies, true ) || ! self::has_unmet_dependencies( $dependent ) ) {677 continue;678 }679 680 $dependents_to_deactivate[] = $dependent;681 682 // Also add any plugins that rely on any of this plugin's dependents.683 $dependents_to_deactivate = array_merge(684 $dependents_to_deactivate,685 self::get_active_dependents_in_dependency_tree( $dependent )686 );687 }688 689 // Bail early if there are no dependents to deactivate.690 if ( empty( $dependents_to_deactivate ) ) {691 return;692 }693 694 $dependents_to_deactivate = array_unique( $dependents_to_deactivate );695 696 deactivate_plugins( $dependents_to_deactivate );697 set_site_transient( 'wp_plugin_dependencies_deactivated_plugins', $dependents_to_deactivate, 10 );698 }699 700 /**701 598 * Gets the filepath of installed dependencies. 702 599 * If a dependency is not installed, the filepath defaults to false. … … 709 606 if ( is_array( self::$dependency_filepaths ) ) { 710 607 return self::$dependency_filepaths; 608 } 609 610 if ( null === self::$dependency_slugs ) { 611 return array(); 711 612 } 712 613 … … 847 748 } 848 749 750 if ( null === self::$dependencies ) { 751 return array(); 752 } 753 849 754 self::$circular_dependencies_slugs = array(); 850 755 -
trunk/src/wp-settings.php
r57628 r57658 389 389 require ABSPATH . WPINC . '/interactivity-api/class-wp-interactivity-api-directives-processor.php'; 390 390 require ABSPATH . WPINC . '/interactivity-api/interactivity-api.php'; 391 require ABSPATH . WPINC . '/class-wp-plugin-dependencies.php'; 391 392 392 393 wp_script_modules()->add_hooks(); … … 419 420 420 421 $GLOBALS['wp_plugin_paths'] = array(); 421 422 // Load and initialize WP_Plugin_Dependencies.423 require_once ABSPATH . WPINC . '/class-wp-plugin-dependencies.php';424 if ( ! defined( 'WP_RUN_CORE_TESTS' ) ) {425 WP_Plugin_Dependencies::initialize();426 }427 422 428 423 // Load must-use plugins. … … 500 495 501 496 // Load active plugins. 502 $all_plugin_data = get_option( 'plugin_data', array() );503 $failed_plugins = array();504 $plugins_dir_strlen = strlen( trailingslashit( WP_PLUGIN_DIR ) );505 497 foreach ( wp_get_active_and_valid_plugins() as $plugin ) { 506 $plugin_file = substr( $plugin, $plugins_dir_strlen );507 508 /*509 * Skip any plugins that have not been added to the 'plugin_data' option yet.510 *511 * Some plugin files may be added locally and activated, but will not yet be512 * added to the 'plugin_data' option. This causes the 'active_plugins' option513 * and the 'plugin_data' option to be temporarily out of sync until the next514 * call to `get_plugins()`.515 */516 if ( isset( $all_plugin_data[ $plugin_file ] ) ) {517 $plugin_headers = $all_plugin_data[ $plugin_file ];518 $errors = array();519 $requirements = array(520 'requires' => ! empty( $plugin_headers['RequiresWP'] ) ? $plugin_headers['RequiresWP'] : '',521 'requires_php' => ! empty( $plugin_headers['RequiresPHP'] ) ? $plugin_headers['RequiresPHP'] : '',522 );523 $compatible_wp = is_wp_version_compatible( $requirements['requires'] );524 $compatible_php = is_php_version_compatible( $requirements['requires_php'] );525 526 $php_update_message = '</p><p>' . sprintf(527 /* translators: %s: URL to Update PHP page. */528 __( '<a href="%s">Learn more about updating PHP</a>.' ),529 esc_url( wp_get_update_php_url() )530 );531 532 $annotation = wp_get_update_php_annotation();533 534 if ( $annotation ) {535 $php_update_message .= '</p><p><em>' . $annotation . '</em>';536 }537 538 if ( ! $compatible_wp && ! $compatible_php ) {539 $errors[] = sprintf(540 /* translators: 1: Current WordPress version, 2: Current PHP version, 3: Plugin name, 4: Required WordPress version, 5: Required PHP version. */541 _x( '<strong>Error:</strong> Current versions of WordPress (%1$s) and PHP (%2$s) do not meet minimum requirements for %3$s. The plugin requires WordPress %4$s and PHP %5$s.', 'plugin' ),542 get_bloginfo( 'version' ),543 PHP_VERSION,544 $plugin_headers['Name'],545 $requirements['requires'],546 $requirements['requires_php']547 ) . $php_update_message;548 } elseif ( ! $compatible_php ) {549 $errors[] = sprintf(550 /* translators: 1: Current PHP version, 2: Plugin name, 3: Required PHP version. */551 _x( '<strong>Error:</strong> Current PHP version (%1$s) does not meet minimum requirements for %2$s. The plugin requires PHP %3$s.', 'plugin' ),552 PHP_VERSION,553 $plugin_headers['Name'],554 $requirements['requires_php']555 ) . $php_update_message;556 } elseif ( ! $compatible_wp ) {557 $errors[] = sprintf(558 /* translators: 1: Current WordPress version, 2: Plugin name, 3: Required WordPress version. */559 _x( '<strong>Error:</strong> Current WordPress version (%1$s) does not meet minimum requirements for %2$s. The plugin requires WordPress %3$s.', 'plugin' ),560 get_bloginfo( 'version' ),561 $plugin_headers['Name'],562 $requirements['requires']563 );564 }565 566 if ( ! empty( $errors ) ) {567 $failed_plugins[ $plugin_file ] = '';568 foreach ( $errors as $error ) {569 $failed_plugins[ $plugin_file ] .= wp_get_admin_notice(570 $error,571 array(572 'type' => 'error',573 'dismissible' => true,574 )575 );576 }577 continue;578 }579 }580 581 498 wp_register_plugin_realpath( $plugin ); 582 499 … … 596 513 unset( $plugin, $_wp_plugin_file ); 597 514 598 if ( ! empty( $failed_plugins ) ) {599 add_action(600 'admin_notices',601 function () use ( $failed_plugins ) {602 global $pagenow;603 604 if ( 'index.php' === $pagenow || 'plugins.php' === $pagenow ) {605 echo implode( '', $failed_plugins );606 }607 }608 );609 }610 unset( $failed_plugins );611 612 515 // Load pluggable functions. 613 516 require ABSPATH . WPINC . '/pluggable.php'; -
trunk/tests/phpunit/tests/admin/plugin-dependencies/base.php
r57545 r57658 33 33 'circular_dependencies_pairs' => null, 34 34 'circular_dependencies_slugs' => null, 35 'initialized' => false, 35 36 ); 36 37 … … 63 64 * Resets all static properties to a default value after each test. 64 65 */ 65 public function set_up() { 66 parent::set_up(); 67 66 public function tear_down() { 68 67 foreach ( self::$static_properties as $name => $default_value ) { 69 68 $this->set_property_value( $name, $default_value ); 70 69 } 70 71 parent::tear_down(); 71 72 } 72 73 -
trunk/tests/phpunit/tests/admin/plugin-dependencies/getDependencyFilepath.php
r57545 r57658 17 17 */ 18 18 class Tests_Admin_WPPluginDependencies_GetDependencyFilepath extends WP_PluginDependencies_UnitTestCase { 19 20 /** 21 * Tests that false is returned if Plugin Dependencies has not been initialized. 22 * 23 * @ticket 60457 24 */ 25 public function test_should_return_false_before_initialization() { 26 // Ensure Plugin Dependencies has not been initialized. 27 $this->assertFalse( 28 $this->get_property_value( 'initialized' ), 29 'Plugin Dependencies has been initialized.' 30 ); 31 32 $this->assertSame( 33 self::$static_properties['dependency_slugs'], 34 $this->get_property_value( 'dependency_slugs' ), 35 '"dependency_slugs" was not set to its default value.' 36 ); 37 38 $this->assertFalse( 39 self::$instance->get_dependency_filepath( 'dependency' ), 40 'false was not returned before initialization.' 41 ); 42 } 19 43 20 44 /** -
trunk/tests/phpunit/tests/admin/plugin-dependencies/hasCircularDependency.php
r57545 r57658 17 17 */ 18 18 class Tests_Admin_WPPluginDependencies_HasCircularDependency extends WP_PluginDependencies_UnitTestCase { 19 20 /** 21 * Tests that false is returned if Plugin Dependencies has not been initialized. 22 * 23 * @ticket 60457 24 */ 25 public function test_should_return_false_before_initialization() { 26 $this->set_property_value( 27 'plugins', 28 array( 29 'dependent/dependent.php' => array( 30 'Name' => 'Dependent', 31 'RequiresPlugins' => 'dependency', 32 ), 33 'dependency/dependency.php' => array( 34 'Name' => 'Dependency', 35 'RequiresPlugins' => 'dependent', 36 ), 37 ) 38 ); 39 40 // Ensure Plugin Dependencies has not been initialized. 41 $this->assertFalse( 42 $this->get_property_value( 'initialized' ), 43 'Plugin Dependencies has been initialized.' 44 ); 45 46 $this->assertSame( 47 self::$static_properties['circular_dependencies_slugs'], 48 $this->get_property_value( 'circular_dependencies_slugs' ), 49 '"circular_dependencies_slugs" was not set to its default value.' 50 ); 51 52 $this->assertFalse( 53 self::$instance->has_circular_dependency( 'dependency' ), 54 'false was not returned before initialization.' 55 ); 56 } 19 57 20 58 /** -
trunk/tests/phpunit/tests/admin/plugin-dependencies/initialize.php
r57545 r57658 15 15 */ 16 16 class Tests_Admin_WPPluginDependencies_Initialize extends WP_PluginDependencies_UnitTestCase { 17 18 /** 19 * Tests that initialization runs only once. 20 * 21 * @ticket 60457 22 * 23 * @dataProvider data_static_properties_set_during_initialization 24 * 25 * @param string $property_name The name of the property to check. 26 */ 27 public function test_should_only_initialize_once( $property_name ) { 28 $this->assertFalse( 29 $this->get_property_value( 'initialized' ), 30 'Plugin Dependencies has already been initialized.' 31 ); 32 33 self::$instance->initialize(); 34 35 $this->assertTrue( 36 $this->get_property_value( 'initialized' ), 37 '"initialized" was not set to true during initialization.' 38 ); 39 40 $default_value = self::$static_properties[ $property_name ]; 41 42 $this->assertNotSame( 43 $default_value, 44 $this->get_property_value( $property_name ), 45 "\"{$property_name}\" was not set during initialization." 46 ); 47 48 // Reset it to its default. 49 $this->set_property_value( $property_name, self::$static_properties[ $property_name ] ); 50 51 self::$instance->initialize(); 52 53 $this->assertSame( 54 $default_value, 55 $this->get_property_value( $property_name ), 56 "\"{$property_name}\" was set during the second initialization attempt." 57 ); 58 } 59 60 /** 61 * Data provider. 62 * 63 * @return array[] 64 */ 65 public function data_static_properties_set_during_initialization() { 66 /* 67 * This does not include 'dependency_api_data' as it is only set 68 * on certain pages. This is tested later. 69 */ 70 return self::text_array_to_dataprovider( 71 array( 72 'plugins', 73 'dependencies', 74 'dependency_slugs', 75 'dependent_slugs', 76 ) 77 ); 78 } 17 79 18 80 /** … … 239 301 $this->assertSame( $expected_slugs, $this->get_property_value( 'dependent_slugs' ) ); 240 302 } 241 242 /**243 * Tests that dependents with unmet dependencies are deactivated.244 *245 * @ticket 22316246 *247 * @covers WP_Plugin_Dependencies::deactivate_dependents_with_unmet_dependencies248 * @covers WP_Plugin_Dependencies::has_unmet_dependencies249 * @covers WP_Plugin_Dependencies::get_active_dependents_in_dependency_tree250 *251 * @dataProvider data_should_only_deactivate_dependents_with_unmet_dependencies252 *253 * @param array $active_plugins An array of active plugin paths.254 * @param array $plugins An array of installed plugins.255 * @param array $expected The expected value of 'active_plugins' after initialization.256 */257 public function test_should_deactivate_dependents_with_uninstalled_dependencies( $active_plugins, $plugins, $expected ) {258 update_option( 'active_plugins', $active_plugins );259 260 $this->set_property_value( 'plugins', $plugins );261 self::$instance::initialize();262 263 $this->assertSame( $expected, array_values( get_option( 'active_plugins', array() ) ) );264 }265 266 /**267 * Data provider.268 *269 * @return array[]270 */271 public function data_should_only_deactivate_dependents_with_unmet_dependencies() {272 return array(273 'a dependent with an uninstalled dependency' => array(274 'active_plugins' => array( 'dependent/dependent.php' ),275 'plugins' => array(276 'dependent/dependent.php' => array( 'RequiresPlugins' => 'dependency' ),277 ),278 'expected' => array(),279 ),280 'a dependent with an inactive dependency' => array(281 'active_plugins' => array( 'dependent/dependent.php' ),282 'plugins' => array(283 'dependent/dependent.php' => array( 'RequiresPlugins' => 'dependency' ),284 'dependency/dependency.php' => array( 'RequiresPlugins' => '' ),285 ),286 'expected' => array(),287 ),288 'a dependent with two dependencies, one uninstalled, one inactive' => array(289 'active_plugins' => array( 'dependent/dependent.php' ),290 'plugins' => array(291 'dependent/dependent.php' => array( 'RequiresPlugins' => 'dependency, dependency2' ),292 'dependency2/dependency2.php' => array( 'RequiresPlugins' => '' ),293 ),294 'expected' => array(),295 ),296 'a dependent with a dependency that is installed and active' => array(297 'active_plugins' => array( 'dependent/dependent.php', 'dependency/dependency.php' ),298 'plugins' => array(299 'dependent/dependent.php' => array( 'RequiresPlugins' => 'dependency' ),300 'dependency/dependency.php' => array( 'RequiresPlugins' => '' ),301 ),302 'expected' => array( 'dependent/dependent.php', 'dependency/dependency.php' ),303 ),304 'one dependent with two dependencies that are installed and active' => array(305 'active_plugins' => array(306 'dependent/dependent.php',307 'dependency/dependency.php',308 'dependency2/dependency2.php',309 ),310 'plugins' => array(311 'dependent/dependent.php' => array( 'RequiresPlugins' => 'dependency, dependency2' ),312 'dependency/dependency.php' => array( 'RequiresPlugins' => '' ),313 'dependency2/dependency2.php' => array( 'RequiresPlugins' => '' ),314 ),315 'expected' => array(316 'dependent/dependent.php',317 'dependency/dependency.php',318 'dependency2/dependency2.php',319 ),320 ),321 'two dependents, one with an uninstalled dependency, and one with an active dependency' => array(322 'active_plugins' => array(323 'dependent/dependent.php',324 'dependent2/dependent2.php',325 'dependency2/dependency2.php',326 ),327 'plugins' => array(328 'dependent/dependent.php' => array( 'RequiresPlugins' => 'dependency' ),329 'dependent2/dependent2.php' => array( 'RequiresPlugins' => 'dependency2' ),330 'dependency2/dependency2.php' => array( 'RequiresPlugins' => '' ),331 ),332 'expected' => array( 'dependent2/dependent2.php', 'dependency2/dependency2.php' ),333 ),334 );335 }336 303 }
Note: See TracChangeset
for help on using the changeset viewer.