Skip to content

Commit

Permalink
Merge branch 'develop' into trunk
Browse files Browse the repository at this point in the history
  • Loading branch information
afragen committed May 21, 2023
2 parents e28cdf6 + 3700756 commit 210e5c1
Show file tree
Hide file tree
Showing 7 changed files with 444 additions and 57 deletions.
6 changes: 6 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
[unreleased]

#### 1.11.0 / 2023-05-21
* add **Requires:** data to plugin cards of uninstalled plugins where repo plugins have `Requires Plugins` header set
* add temporary style kludge to above
* add caching to uninstalled plugin data
* abstract code to create plugin install action buttons

#### 1.10.0 / 2023-04-29
* show `Cannot Install` button in Dependencies tab for dependencies with no package
* return of generic plugins_api() response to it's own hook, avoids having to hide items in plugin card
Expand Down
3 changes: 2 additions & 1 deletion plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* Plugin URI: https://wordpress.org/plugins/wp-plugin-dependencies
* Description: Parses 'Requires Plugins' header, add plugin install dependencies tab, and information about dependencies.
* Author: Andy Fragen, Colin Stewart, Paul Biron
* Version: 1.10.0
* Version: 1.11.0
* License: MIT
* Network: true
* Requires at least: 6.0
Expand Down Expand Up @@ -63,6 +63,7 @@ class Init {
* @return void
*/
public function __construct() {
require_once __DIR__ . '/wp-admin/includes/plugin-install.php';
require_once __DIR__ . '/wp-admin/includes/class-wp-plugin-dependencies-2.php';

if ( ! WP_PLUGIN_DEPENDENCIES1_COMMITTED ) {
Expand Down
8 changes: 7 additions & 1 deletion readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Network: true
Requires at least: 6.0
Requires PHP: 5.6
Tested up to: 6.3
Stable tag: 1.10.0
Stable tag: 1.11.0

## Description

Expand Down Expand Up @@ -47,6 +47,12 @@ PRs should be made against the `develop` branch.

## Changelog

#### 1.11.0 / 2023-05-21
* add **Requires:** data to plugin cards of uninstalled plugins where repo plugins have `Requires Plugins` header set
* add temporary style kludge to above
* add caching to uninstalled plugin data
* abstract code to create plugin install action buttons

#### 1.10.0 / 2023-04-29
* show `Cannot Install` button in Dependencies tab for dependencies with no package
* return of generic plugins_api() response to it's own hook, avoids having to hide items in plugin card
Expand Down
113 changes: 61 additions & 52 deletions tests/phpunit/tests/admin/wpPluginDependencies.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,32 @@
* @group admin
* @group plugins
*/

class Tests_Admin_WpPluginDependencies extends WP_UnitTestCase {
protected static $plugin_dir;
/**
* Stored the plugins directory.
*
* @var string
*/
protected static $plugins_dir;

public static function wpSetUpBeforeClass() {
self::$plugin_dir = WP_PLUGIN_DIR . '/wp_plugin_dependencies_plugin';
@mkdir( self::$plugin_dir );
/**
* Sets up the plugins directory before any tests run.
*/
public static function set_up_before_class() {
self::$plugins_dir = WP_PLUGIN_DIR . '/wp_plugin_dependencies_plugin';
@mkdir( self::$plugins_dir );
}

public static function wpTearDownAfterClass() {
array_map( 'unlink', array_filter( (array) glob( self::$plugin_dir . '/*' ) ) );
rmdir( self::$plugin_dir );
/**
* Removes the plugins directory after all tests run.
*/
public static function tear_down_after_class() {
array_map( 'unlink', array_filter( (array) glob( self::$plugins_dir . '/*' ) ) );
rmdir( self::$plugins_dir );
}

/**
* Helper method.
*
* This creates a single-file plugin.
*
* @access private
* Creates a single-file plugin.
*
* @param string $data Optional. Data for the plugin file. Default is a dummy plugin header.
* @param string $filename Optional. Filename for the plugin file. Default is a random string.
Expand All @@ -35,7 +41,7 @@ public static function wpTearDownAfterClass() {
*/
private function create_plugin( $filename, $data = "<?php\n/*\nPlugin Name: Test\n*/", $dir_path = false ) {
if ( false === $filename ) {
$filename = __FUNCTION__ . '.php';
$filename = 'create_plugin.php';
}

if ( false === $dir_path ) {
Expand All @@ -53,9 +59,7 @@ private function create_plugin( $filename, $data = "<?php\n/*\nPlugin Name: Test
}

/**
* Helper method.
*
* This makes a class property accessible.
* Makes a class property accessible.
*
* @param object|string $obj_or_class The object or class.
* @param string $prop The property.
Expand All @@ -68,77 +72,80 @@ private function make_prop_accessible( $obj_or_class, $prop ) {
}

/**
* Helper method.
*
* Makes a class function accessible.
* Makes a class method accessible.
*
* @param object|string $obj_or_class The object or class.
* @param string $function The class method.
* @param string $method The class method.
* @return ReflectionMethod The accessible method.
*/
private function make_method_accessible( $obj_or_class, $function ) {
$method = new ReflectionMethod( $obj_or_class, $function );
private function make_method_accessible( $obj_or_class, $method ) {
$method = new ReflectionMethod( $obj_or_class, $method );
$method->setAccessible( true );
return $method;
}

/**
* @covers WP_Plugin_Dependencies::__construct()
* Tests that the `$requires_plugins` and `$plugin_data` properties are set to
* empty arrays on instantiation.
*
* @covers WP_Plugin_Dependencies::__construct
*/
public function test__construct() {
public function test_construct_should_set_requires_plugins_and_plugin_data_to_empty_arrays() {
$dependencies = new WP_Plugin_Dependencies();
$requires_plugins = $this->make_prop_accessible( $dependencies, 'requires_plugins' );
$plugin_data = $this->make_prop_accessible( $dependencies, 'plugin_data' );

$actual = $requires_plugins->getValue( $dependencies );

$this->assertIsArray( $actual, '$requires_plugins is not an array' );
$this->assertEmpty( $actual, '$requires_plugins is not empty' );
$actual_requires_plugins = $requires_plugins->getValue( $dependencies );
$actual_plugin_data = $plugin_data->getValue( $dependencies );

$actual = $plugin_data->getValue( $dependencies );

$this->assertIsArray( $actual, '$plugin_data is not an array' );
$this->assertEmpty( $actual, '$plugin_data is not empty' );
$this->assertIsArray( $actual_requires_plugins, '$requires_plugins is not an array.' );
$this->assertEmpty( $actual_requires_plugins, '$requires_plugins is not empty.' );
$this->assertIsArray( $actual_plugin_data, '$plugin_data is not an array.' );
$this->assertEmpty( $actual_plugin_data, '$plugin_data is not empty.' );
}

/**
* Tests that `::get_plugins()` returns an array of plugin data.
*
* @covers WP_Plugin_Dependencies::get_plugins
*/
public function test_get_plugins() {
public function test_get_plugins_should_return_an_array_of_plugin_data() {
$dependencies = new WP_Plugin_Dependencies();
$get_plugins = $this->make_method_accessible( $dependencies, 'get_plugins' );
$actual = $get_plugins->invoke( $dependencies );

$this->assertIsArray( $actual, 'Did not return an array' );
$this->assertNotEmpty( $actual, 'The plugins array is empty' );
$this->assertIsArray( $actual, 'Did not return an array.' );
$this->assertNotEmpty( $actual, 'The plugin data array is empty.' );
}

/**
* Tests that plugin headers are correctly parsed.
*
* @dataProvider data_parse_plugin_headers
*
* @covers WP_Plugin_Dependencies::parse_plugin_headers
*
* @param array $headers .
* @param array $plugins_data Raw plugins data.
* @param stdClass $expected The expected parsed headers.
*/
public function test_parse_plugin_headers( $headers, $expected ) {
public function test_parse_plugin_headers( $plugins_data, $expected ) {
$plugin_names = array();

foreach ( $headers as $plugin_name => $plugin ) {
foreach ( $plugins_data as $name => $data ) {
$plugin_data = array_map(
static function( $value, $header ) {
return $header . ': ' . $value;
},
$plugin,
array_keys( $plugin )
$data,
array_keys( $data )
);

$plugin_data = "<?php\n/*\n" . implode( "\n", $plugin_data ) . "\n*/\n";

$plugin_file = $this->create_plugin(
$plugin_name . '.php',
$name . '.php',
$plugin_data,
self::$plugin_dir
self::$plugins_dir
);

$plugin_names[] = $plugin_file[1];
Expand All @@ -148,13 +155,13 @@ static function( $value, $header ) {

$dependencies = new WP_Plugin_Dependencies();
$plugins = $this->make_prop_accessible( $dependencies, 'plugins' );
$plugins->setValue( $dependencies, $headers );
$plugins->setValue( $dependencies, $plugins_data );

$parse_plugin_headers = $this->make_method_accessible( $dependencies, 'parse_plugin_headers' );
$actual = $parse_plugin_headers->invoke( $dependencies );

// Remove any non testing data, may be single file plugins in test environment.
$test_plugin = basename( self::$plugin_dir ) . '/' . $plugin_file[0];
$test_plugin = basename( self::$plugins_dir ) . '/' . $plugin_file[0];
$actual = array_filter(
$actual,
function( $key ) use ( $test_plugin ) {
Expand All @@ -174,9 +181,9 @@ function( $key ) use ( $test_plugin ) {
}

/**
* Data Provider.
* Data provider.
*
* @return array
* @return array[]
*/
public function data_parse_plugin_headers() {
return array(
Expand Down Expand Up @@ -319,25 +326,27 @@ public function data_parse_plugin_headers() {
}

/**
* Tests that slugs are correctly sanitized from the 'RequiresPlugins' header.
*
* @dataProvider data_slug_sanitization
*
* @covers WP_Plugin_Dependencies::sanitize_required_headers
*
* @param string $requires_plugins The unsanitized dependency slug(s).
* @param array $expected The sanitized dependency slug(s).
*/
public function test_slug_sanitization( $requires_plugins, $expected ) {
public function test_slugs_are_correctly_sanitized_from_the_requiresplugins_header( $requires_plugins, $expected ) {
$dependencies = new WP_Plugin_Dependencies();
$sanitize = $this->make_method_accessible( $dependencies, 'sanitize_required_headers' );
$headers = array( 'test-plugin' => array( 'RequiresPlugins' => $requires_plugins ) );
$actual = $sanitize->invoke( $dependencies, $headers );
$this->assertSameSetsWithIndex( $expected, $actual );
$this->assertSame( $expected, $actual );
}

/**
* Data provider.
*
* @return array
* @return array[]
*/
public function data_slug_sanitization() {
return array(
Expand Down Expand Up @@ -440,9 +449,9 @@ public function test_get_dependency_filepaths( $slugs, $plugins, $expected ) {
}

/**
* Data provider for test_get_dependency_filepaths().
* Data provider.
*
* @return array
* @return array[]
*/
public function data_get_dependency_filepaths() {
return array(
Expand Down
89 changes: 89 additions & 0 deletions wp-admin/css/wp-plugin-dependencies.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Some of this file contains a temporary kludge that is not a11y-friendly.
*
* It won't be necessary when merging to Core, as Core's markup will be
* modified to achieve what the kludge does here.
*/
.plugin-card .column-description {
display: flex;
flex-direction: column;
justify-content: flex-start;
}

.plugin-card .column-description > p {
margin-top: 0;
}

.plugin-card .column-description .authors {
order: 1;
}

.plugin-card .column-description .plugin-dependencies {
order: 2;
}

.plugin-card .column-description p:empty {
display: none;
}

/*.plugin-card .plugin-dependency-name:before {
font: normal 20px/.5 dashicons;
position: relative;
top: 4px;
left: -2px;
}
.plugin-card .plugin-dependencies .plugin-dependency-compatible:before {
content: "\f147";
color: #007017;
}
.plugin-card .plugin-dependencies .plugin-dependency-incompatible:before {
content: "\f158";
color: #d63638;
}*/

.plugin-card .desc {
margin-inline: 0;
}

.plugin-card .desc > p {
margin-left: 148px;
margin-right: 128px;
}

.plugin-card .plugin-dependencies {
background-color: #e5f5fa;
border-left: 3px solid #72aee6;
margin-bottom: .5em;
padding: 15px;
}

.plugin-card .plugin-dependencies-explainer-text {
margin-block: 0;
}

.plugin-card .plugin-dependency {
align-items: center;
display: flex;
flex-wrap: wrap;
margin-top: .5em;
}

.plugin-card .plugin-dependency:nth-child(2),
.plugin-card .plugin-dependency:last-child {
margin-top: 1em;
}

.plugin-card .plugin-dependency-name {
margin-right: 1em;
flex-basis: 50%;
}

.plugin-card .plugin-dependency .notice {
flex-basis: 100%;
margin-bottom: .5em;
margin-inline: 0;
}

.plugin-card .plugin-dependency .button {
margin-inline: auto 1em;
}
Loading

0 comments on commit 210e5c1

Please sign in to comment.