Skip to content

Commit 8e58bc2

Browse files
authored
Merge branch 'main' into copilot/fix-activation-hooks-issue
2 parents 5b4b9e8 + 7ddbd20 commit 8e58bc2

11 files changed

+245
-5
lines changed

.github/workflows/code-quality.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ on:
66
branches:
77
- main
88
- master
9+
schedule:
10+
- cron: '17 2 * * *' # Run every day on a seemly random time.
911

1012
jobs:
1113
code-quality:

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,10 @@ wp plugin is-active <plugin> [--network]
398398

399399
Returns exit code 0 when active, 1 when not active.
400400

401+
If the plugin does not exist but is still in WordPress's active plugins storage
402+
(such as the active plugins option or the sitewide plugins option for network-activated plugins),
403+
a warning will be emitted.
404+
401405
**OPTIONS**
402406

403407
<plugin>

features/plugin-is-active.feature

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
Feature: Check if a WordPress plugin is active
2+
3+
Background:
4+
Given a WP install
5+
6+
Scenario: Check if an active plugin is active
7+
When I run `wp plugin activate akismet`
8+
Then STDOUT should contain:
9+
"""
10+
Success:
11+
"""
12+
13+
When I run `wp plugin is-active akismet`
14+
Then the return code should be 0
15+
16+
Scenario: Check if an inactive plugin is not active
17+
When I run `wp plugin activate akismet`
18+
Then STDOUT should contain:
19+
"""
20+
Success:
21+
"""
22+
23+
When I run `wp plugin deactivate akismet`
24+
Then STDOUT should contain:
25+
"""
26+
Success:
27+
"""
28+
29+
When I try `wp plugin is-active akismet`
30+
Then the return code should be 1
31+
32+
Scenario: Check if a non-existent plugin is not active
33+
When I try `wp plugin is-active non-existent-plugin`
34+
Then the return code should be 1
35+
36+
Scenario: Warn when plugin is in active_plugins but file does not exist
37+
When I run `wp plugin activate akismet`
38+
Then STDOUT should contain:
39+
"""
40+
Success:
41+
"""
42+
43+
When I run `wp plugin is-active akismet`
44+
Then the return code should be 0
45+
46+
# Remove the plugin directory
47+
When I run `wp plugin path akismet --dir`
48+
Then save STDOUT as {PLUGIN_PATH}
49+
50+
When I run `rm -rf {PLUGIN_PATH}`
51+
Then the return code should be 0
52+
53+
# Now the plugin file is gone but still in active_plugins
54+
When I try `wp plugin is-active akismet`
55+
Then STDERR should contain:
56+
"""
57+
Warning: Plugin 'akismet' is marked as active but the plugin file does not exist.
58+
"""
59+
And the return code should be 1
60+
61+
Scenario: Warn when network-activated plugin is in active_sitewide_plugins but file does not exist
62+
Given a WP multisite install
63+
64+
When I run `wp plugin activate akismet --network`
65+
Then STDOUT should contain:
66+
"""
67+
Success:
68+
"""
69+
70+
When I run `wp plugin is-active akismet --network`
71+
Then the return code should be 0
72+
73+
# Remove the plugin directory
74+
When I run `wp plugin path akismet --dir`
75+
Then save STDOUT as {PLUGIN_PATH}
76+
77+
When I run `rm -rf {PLUGIN_PATH}`
78+
Then the return code should be 0
79+
80+
# Now the plugin file is gone but still in active_sitewide_plugins
81+
When I try `wp plugin is-active akismet --network`
82+
Then STDERR should contain:
83+
"""
84+
Warning: Plugin 'akismet' is marked as active but the plugin file does not exist.
85+
"""
86+
And the return code should be 1

features/plugin-list-wporg-status.feature

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ Feature: Check the status of plugins on WordPress.org
7878
"error": "not_found"
7979
}
8080
"""
81-
And that HTTP requests to https://plugins.trac.wordpress.org/log/never-wporg?limit=1&mode=stop_on_copy&format=rss will respond with:
81+
And that HTTP requests to https://plugins.trac.wordpress.org/log/never-wporg/?limit=1&mode=stop_on_copy&format=rss will respond with:
8282
"""
8383
HTTP/1.1 404
8484
Content-Type: application/rss+xml;charset=utf-8

features/plugin-update.feature

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,3 +267,25 @@ Feature: Update WordPress plugins
267267
"""
268268
Success: Updated 1 of 1 plugins (1 skipped).
269269
"""
270+
271+
@require-wp-5.2
272+
Scenario: Updating all plugins should show the name of each plugin as it is updated
273+
Given a WP install
274+
And I run `wp plugin delete akismet`
275+
276+
When I run `wp plugin install health-check --version=1.5.0`
277+
Then STDOUT should not be empty
278+
279+
When I run `wp plugin install wordpress-importer --version=0.5`
280+
Then STDOUT should not be empty
281+
282+
When I try `wp plugin update --all`
283+
Then STDOUT should contain:
284+
"""
285+
Updating Health Check & Troubleshooting...
286+
"""
287+
288+
And STDOUT should contain:
289+
"""
290+
Success: Updated 2 of 2 plugins.
291+
"""

features/theme-update.feature

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,3 +154,25 @@ Feature: Update WordPress themes
154154
"""
155155
1.1.1
156156
"""
157+
158+
@require-wp-4.5
159+
Scenario: Updating all themes should show the name of each theme as it is updated
160+
Given a WP install
161+
And I run `wp theme delete --all --force`
162+
163+
When I run `wp theme install moina --version=1.0.2`
164+
Then STDOUT should not be empty
165+
166+
When I run `wp theme install twentytwelve --version=1.0`
167+
Then STDOUT should not be empty
168+
169+
When I try `wp theme update --all`
170+
Then STDOUT should contain:
171+
"""
172+
Updating Moina...
173+
"""
174+
175+
And STDOUT should contain:
176+
"""
177+
Success: Updated 2 of 2 themes.
178+
"""

phpcs.xml.dist

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
<exclude-pattern>*/src/WP_CLI/CommandWithUpgrade\.php$</exclude-pattern>
6262
<exclude-pattern>*/src/WP_CLI/(CommandWith|DestructivePlugin|DestructiveTheme)Upgrader\.php$</exclude-pattern>
6363
<exclude-pattern>*/src/WP_CLI/Parse(Plugin|Theme)NameInput\.php$</exclude-pattern>
64+
<exclude-pattern>*/src/WP_CLI/ExtensionUpgraderSkin\.php$</exclude-pattern>
6465
</rule>
6566

6667
<!-- Exclude classes from UselessOverridingMethod sniff as the method is used for generating docs. -->

src/Plugin_Command.php

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1235,6 +1235,10 @@ private function get_plugin_dependencies( $slug ) {
12351235
return [];
12361236
}
12371237

1238+
/**
1239+
* @var object{requires_plugins?: array} $api
1240+
*/
1241+
12381242
// Check if requires_plugins field exists and is not empty
12391243
if ( ! empty( $api->requires_plugins ) && is_array( $api->requires_plugins ) ) {
12401244
return $api->requires_plugins;
@@ -1508,6 +1512,10 @@ public function is_installed( $args, $assoc_args ) {
15081512
*
15091513
* Returns exit code 0 when active, 1 when not active.
15101514
*
1515+
* If the plugin does not exist but is still in WordPress's active plugins storage
1516+
* (such as the active plugins option or the sitewide plugins option for network-activated plugins),
1517+
* a warning will be emitted.
1518+
*
15111519
* ## OPTIONS
15121520
*
15131521
* <plugin>
@@ -1531,6 +1539,55 @@ public function is_active( $args, $assoc_args ) {
15311539
$plugin = $this->fetcher->get( $args[0] );
15321540

15331541
if ( ! $plugin ) {
1542+
// Plugin not found via fetcher, but it might still be in active_plugins option
1543+
// Check if it's in the active_plugins list
1544+
$input_name = $args[0];
1545+
// For network plugins: active_sitewide_plugins is an array where keys are plugin files and values are timestamps
1546+
// For regular plugins: active_plugins is an array of plugin file paths
1547+
$active_plugins = $network_wide ? get_site_option( 'active_sitewide_plugins', [] ) : get_option( 'active_plugins', [] );
1548+
1549+
// Ensure we have an array to work with
1550+
if ( ! is_array( $active_plugins ) ) {
1551+
$active_plugins = [];
1552+
}
1553+
1554+
// For network-wide plugins, extract the plugin files from the keys
1555+
if ( $network_wide ) {
1556+
$active_plugin_files = array_keys( $active_plugins );
1557+
} else {
1558+
$active_plugin_files = $active_plugins;
1559+
}
1560+
1561+
// Try to find a matching plugin file in active_plugins using the same logic as the fetcher
1562+
// This matches: exact file name, "name.php", or directory name
1563+
$found_in_active = '';
1564+
foreach ( $active_plugin_files as $plugin_file ) {
1565+
// Ensure plugin_file is a string
1566+
if ( ! is_string( $plugin_file ) ) {
1567+
continue;
1568+
}
1569+
1570+
// Check if the input matches the plugin file in various ways
1571+
// This mirrors the logic in WP_CLI\Fetchers\Plugin::get()
1572+
if (
1573+
"$input_name.php" === $plugin_file ||
1574+
$plugin_file === $input_name ||
1575+
( dirname( $plugin_file ) === $input_name && '.' !== $input_name )
1576+
) {
1577+
$found_in_active = $plugin_file;
1578+
break;
1579+
}
1580+
}
1581+
1582+
if ( $found_in_active ) {
1583+
// Plugin is in active_plugins but file doesn't exist
1584+
// Use validate_plugin to confirm the file is missing
1585+
$validation = validate_plugin( $found_in_active );
1586+
if ( is_wp_error( $validation ) ) {
1587+
WP_CLI::warning( "Plugin '{$input_name}' is marked as active but the plugin file does not exist." );
1588+
}
1589+
}
1590+
15341591
WP_CLI::halt( 1 );
15351592
}
15361593

src/WP_CLI/CommandWithUpgrade.php

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ abstract protected function get_item_list();
8181
*/
8282
abstract protected function filter_item_list( $items, $args );
8383

84+
/**
85+
* @return array<string, array{slug: string, name: string, update: string, recently_active?: bool, status: string, version: string}>
86+
*/
8487
abstract protected function get_all_items();
8588

8689
/**
@@ -237,6 +240,11 @@ public function install( $args, $assoc_args ) {
237240
&& ! preg_match( '#github\.com/[^/]+/[^/]+/(?:releases/download|raw)/#', $slug ) ) {
238241

239242
$filter = function ( $source ) use ( $slug ) {
243+
/**
244+
* @var \WP_Filesystem_Base $wp_filesystem
245+
*/
246+
global $wp_filesystem;
247+
240248
/**
241249
* @var string $path
242250
*/
@@ -255,7 +263,7 @@ public function install( $args, $assoc_args ) {
255263
}
256264
$new_path = substr_replace( $source, $slug_dir, (int) strrpos( $source, $source_dir ), strlen( $source_dir ) );
257265

258-
if ( $GLOBALS['wp_filesystem']->move( $source, $new_path ) ) {
266+
if ( $wp_filesystem->move( $source, $new_path ) ) {
259267
WP_CLI::log( sprintf( "Renamed Github-based project from '%s' to '%s'.", $source_dir, $slug_dir ) );
260268
return $new_path;
261269
}
@@ -398,7 +406,14 @@ protected function get_upgrader( $assoc_args ) {
398406
$force = Utils\get_flag_value( $assoc_args, 'force', false );
399407
$insecure = Utils\get_flag_value( $assoc_args, 'insecure', false );
400408
$upgrader_class = $this->get_upgrader_class( $force );
401-
return Utils\get_upgrader( $upgrader_class, $insecure );
409+
410+
if ( ! class_exists( '\WP_Upgrader_Skin' ) ) {
411+
if ( file_exists( ABSPATH . 'wp-admin/includes/class-wp-upgrader-skin.php' ) ) {
412+
include ABSPATH . 'wp-admin/includes/class-wp-upgrader-skin.php';
413+
}
414+
}
415+
416+
return Utils\get_upgrader( $upgrader_class, $insecure, new ExtensionUpgraderSkin() );
402417
}
403418

404419
protected function update_many( $args, $assoc_args ) {
@@ -876,6 +891,9 @@ protected function _search( $args, $assoc_args ) {
876891
// In older WP versions these used to be objects.
877892
foreach ( $items as $index => $item_object ) {
878893
if ( is_array( $item_object ) ) {
894+
/**
895+
* @var array{slug: string} $item_object
896+
*/
879897
$items[ $index ]['url'] = "https://wordpress.org/{$plural}/{$item_object['slug']}/";
880898
} elseif ( $item_object instanceof \stdClass ) {
881899
$item_object->url = "https://wordpress.org/{$plural}/{$item_object->slug}/";
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace WP_CLI;
4+
5+
use WP_CLI;
6+
7+
/**
8+
* An Upgrader Skin for extensions (plugins/themes) that displays which item is being updated
9+
*
10+
* @package wp-cli
11+
*
12+
* @property-read array{Name?: string}|null $plugin_info
13+
* @property-read \WP_Theme|null $theme_info
14+
*/
15+
class ExtensionUpgraderSkin extends UpgraderSkin {
16+
17+
/**
18+
* Called before an update is performed.
19+
*/
20+
public function before() {
21+
// These properties are defined in `Bulk_Plugin_Upgrader_Skin`/`Bulk_Theme_Upgrader_Skin`
22+
if ( isset( $this->plugin_info ) && is_array( $this->plugin_info ) && isset( $this->plugin_info['Name'] ) ) {
23+
WP_CLI::log( sprintf( 'Updating %s...', html_entity_decode( $this->plugin_info['Name'], ENT_QUOTES, get_bloginfo( 'charset' ) ) ) );
24+
} elseif ( isset( $this->theme_info ) && is_object( $this->theme_info ) && method_exists( $this->theme_info, 'get' ) ) {
25+
WP_CLI::log( sprintf( 'Updating %s...', html_entity_decode( $this->theme_info->get( 'Name' ), ENT_QUOTES, get_bloginfo( 'charset' ) ) ) );
26+
}
27+
}
28+
}

0 commit comments

Comments
 (0)