PluginProbe
WPGraphQL / trunk
WPGraphQL vtrunk
2.22.3 2.22.2 2.22.1 2.22.0 2.21.1 2.21.0 2.20.0 2.19.0 2.18.0 2.17.0 2.16.0 2.15.1 2.15.0 2.14.1 2.14.0 2.13.0 2.2.0 2.3.0 2.3.3 2.3.6 2.3.8 2.5.0 2.5.1 2.5.2 2.5.3 All 177 releases
wp-graphql / src / Data / Connection / PluginConnectionResolver.php

PluginConnectionResolver.php in WPGraphQL trunk, at src/Data/Connection/PluginConnectionResolver.php

283 lines 9.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace WPGraphQL\Data\Connection;
3
4 /**
5 * Class PluginConnectionResolver - Connects plugins to other objects
6 *
7 * @package WPGraphQL\Data\Connection
8 * @since 0.0.5
9 * @extends \WPGraphQL\Data\Connection\AbstractConnectionResolver<array<string,array<string,mixed>>>
10 */
11 class PluginConnectionResolver extends AbstractConnectionResolver {
12
13 /**
14 * A list of all the installed plugins, keyed by their type.
15 *
16 * @var ?array{site:array<string,mixed>,mustuse:array<string,mixed>,dropins:array<string,mixed>}
17 */
18 protected $all_plugins;
19
20 /**
21 * {@inheritDoc}
22 */
23 public function get_ids_from_query() {
24 $ids = [];
25 $queried = ! empty( $this->query ) ? $this->query : [];
26
27 if ( empty( $queried ) ) {
28 return $ids;
29 }
30
31 foreach ( $queried as $key => $item ) {
32 $ids[ $key ] = $key;
33 }
34
35 return $ids;
36 }
37
38 /**
39 * {@inheritDoc}
40 */
41 protected function prepare_query_args( array $args ): array {
42 if ( ! empty( $args['where']['status'] ) ) {
43 $args['where']['stati'] = [ $args['where']['status'] ];
44 } elseif ( ! empty( $args['where']['stati'] ) && is_string( $args['where']['stati'] ) ) {
45 $args['where']['stati'] = [ $args['where']['stati'] ];
46 }
47
48 return $args;
49 }
50
51 /**
52 * {@inheritDoc}
53 *
54 * @return array<string,array<string,mixed>>
55 */
56 protected function query( array $query_args ) {
57 // Get all plugins.
58 $plugins = $this->get_all_plugins();
59
60 $all_plugins = array_merge( $plugins['site'], $plugins['mustuse'], $plugins['dropins'] );
61
62 // Bail early if no plugins.
63 if ( empty( $all_plugins ) ) {
64 return [];
65 }
66
67 // Holds the plugin names sorted by status. The other ` status => [ plugin_names ] ` will be added later.
68 $plugins_by_status = [
69 'mustuse' => array_flip( array_keys( $plugins['mustuse'] ) ),
70 'dropins' => array_flip( array_keys( $plugins['mustuse'] ) ),
71 ];
72
73 // Permissions.
74 $can_update = current_user_can( 'update_plugins' );
75 $can_view_autoupdates = $can_update && function_exists( 'wp_is_auto_update_enabled_for_type' ) && wp_is_auto_update_enabled_for_type( 'plugin' );
76 $show_network_plugins = apply_filters( 'show_network_active_plugins', current_user_can( 'manage_network_plugins' ) );
77
78 // Store the plugin stati as array keys for performance.
79 $active_stati = ! empty( $query_args['where']['stati'] ) ? array_flip( $query_args['where']['stati'] ) : [];
80
81 // Get additional plugin info.
82 $upgradable_list = $can_update && isset( $active_stati['upgrade'] ) ? get_site_transient( 'update_plugins' ) : [];
83 $recently_activated_list = isset( $active_stati['recently_activated'] ) ? get_site_option( 'recently_activated', [] ) : [];
84
85 // Loop through the plugins, add additional data, and store them in $plugins_by_status.
86 foreach ( $all_plugins as $plugin_file => $plugin_data ) {
87 if ( ! file_exists( WP_PLUGIN_DIR . '/' . $plugin_file ) ) {
88 unset( $all_plugins[ $plugin_file ] );
89 continue;
90 }
91
92 // Handle multisite plugins.
93 if ( is_multisite() && is_network_only_plugin( $plugin_file ) && ! is_plugin_active( $plugin_file ) ) {
94
95 // Check for inactive network plugins.
96 if ( $show_network_plugins ) {
97
98 // add the plugin to the network_inactive and network_inactive list since "network_inactive" are considered inactive
99 $plugins_by_status['inactive'][ $plugin_file ] = $plugin_file;
100 $plugins_by_status['network_inactive'][ $plugin_file ] = $plugin_file;
101 } else {
102 // Unset and skip to next plugin.
103 unset( $all_plugins[ $plugin_file ] );
104 continue;
105 }
106 } elseif ( is_plugin_active_for_network( $plugin_file ) ) {
107 // Check for active network plugins.
108 if ( $show_network_plugins ) {
109 // add the plugin to the network_activated and active list, since "network_activated" are active
110 $plugins_by_status['active'][ $plugin_file ] = $plugin_file;
111 $plugins_by_status['network_activated'][ $plugin_file ] = $plugin_file;
112 } else {
113 // Unset and skip to next plugin.
114 unset( $all_plugins[ $plugin_file ] );
115 continue;
116 }
117 }
118
119 // Populate active/inactive lists.
120 // @todo should this include MU/Dropins?
121 if ( is_plugin_active( $plugin_file ) ) {
122 $plugins_by_status['active'][ $plugin_file ] = $plugin_file;
123 } else {
124 $plugins_by_status['inactive'][ $plugin_file ] = $plugin_file;
125 }
126
127 // Populate recently activated list.
128 if ( isset( $recently_activated_list[ $plugin_file ] ) ) {
129 $plugins_by_status['recently_activated'][ $plugin_file ] = $plugin_file;
130 }
131
132 // Populate paused list.
133 if ( is_plugin_paused( $plugin_file ) ) {
134 $plugins_by_status['paused'][ $plugin_file ] = $plugin_file;
135 }
136
137 // Get update information.
138 if ( $can_update && isset( $upgradable_list->response[ $plugin_file ] ) ) {
139 // An update is available.
140 $plugin_data['update'] = true;
141 // Extra info if known.
142 $plugin_data = array_merge( (array) $upgradable_list->response[ $plugin_file ], [ 'update-supported' => true ], $plugin_data );
143
144 // Populate upgradable list.
145 $plugins_by_status['upgrade'][ $plugin_file ] = $plugin_file;
146 } elseif ( isset( $upgradable_list->no_update[ $plugin_file ] ) ) {
147 $plugin_data = array_merge( (array) $upgradable_list->no_update[ $plugin_file ], [ 'update-supported' => true ], $plugin_data );
148 } elseif ( empty( $plugin_data['update-supported'] ) ) {
149 $plugin_data['update-supported'] = false;
150 }
151
152 // Get autoupdate information.
153 if ( $can_view_autoupdates ) {
154 /*
155 * Create the payload that's used for the auto_update_plugin filter.
156 * This is the same data contained within $upgradable_list->(response|no_update) however
157 * not all plugins will be contained in those keys, this avoids unexpected warnings.
158 */
159 $filter_payload = [
160 'id' => $plugin_file,
161 'slug' => '',
162 'plugin' => $plugin_file,
163 'new_version' => '',
164 'url' => '',
165 'package' => '',
166 'icons' => [],
167 'banners' => [],
168 'banners_rtl' => [],
169 'tested' => '',
170 'requires_php' => '',
171 'compatibility' => new \stdClass(),
172 ];
173 $filter_payload = (object) wp_parse_args( $plugin_data, $filter_payload );
174
175 if ( function_exists( 'wp_is_auto_update_forced_for_item' ) ) {
176 $auto_update_forced = wp_is_auto_update_forced_for_item( 'plugin', null, $filter_payload );
177 $plugin_data['auto-update-forced'] = $auto_update_forced;
178 }
179 }
180
181 // Save any changes to the plugin data.
182 $all_plugins[ $plugin_file ] = $plugin_data;
183 }
184
185 $plugins_by_status['all'] = array_flip( array_keys( $all_plugins ) );
186
187 /**
188 * Filters the plugins by status.
189 * */
190 $filtered_plugins = ! empty( $active_stati ) ? array_values( array_intersect_key( $plugins_by_status, $active_stati ) ) : [];
191 // If plugins exist for the filter, flatten and return them. Otherwise, return the full list.
192 $filtered_plugins = ! empty( $filtered_plugins ) ? array_merge( [], ...$filtered_plugins ) : $plugins_by_status['all'];
193
194 if ( ! empty( $query_args['where']['search'] ) ) {
195 // Filter by search args.
196 $s = sanitize_text_field( $query_args['where']['search'] );
197 $matches = array_keys(
198 array_filter(
199 $all_plugins,
200 static function ( $plugin ) use ( $s ) {
201 foreach ( $plugin as $value ) {
202 if ( is_string( $value ) && false !== stripos( wp_strip_all_tags( $value ), $s ) ) {
203 return true;
204 }
205 }
206
207 return false;
208 }
209 )
210 );
211 if ( ! empty( $matches ) ) {
212 $filtered_plugins = array_intersect_key( $filtered_plugins, array_flip( $matches ) );
213 }
214 }
215
216 // Return plugin data filtered by args.
217 return ! empty( $filtered_plugins ) ? array_intersect_key( $all_plugins, $filtered_plugins ) : [];
218 }
219
220 /**
221 * {@inheritDoc}
222 */
223 protected function loader_name(): string {
224 return 'plugin';
225 }
226
227 /**
228 * {@inheritDoc}
229 */
230 public function is_valid_offset( $offset ) {
231 $plugins = $this->get_all_plugins();
232
233 $all_plugins = array_merge( $plugins['site'], $plugins['mustuse'], $plugins['dropins'] );
234
235 return array_key_exists( $offset, $all_plugins );
236 }
237
238 /**
239 * {@inheritDoc}
240 */
241 public function should_execute() {
242 if ( is_multisite() ) {
243 // update_, install_, and delete_ are handled above with is_super_admin().
244 $menu_perms = get_site_option( 'menu_items', [] );
245 if ( empty( $menu_perms['plugins'] ) && ! current_user_can( 'manage_network_plugins' ) ) {
246 return false;
247 }
248 } elseif ( ! current_user_can( 'activate_plugins' ) ) {
249 return false;
250 }
251
252 return true;
253 }
254
255 /**
256 * Gets all the installed plugins, including must use and drop in plugins.
257 *
258 * The result is cached in the ConnectionResolver instance.
259 *
260 * @return array{site:array<string,mixed>,mustuse:array<string,mixed>,dropins:array<string,mixed>}
261 */
262 protected function get_all_plugins(): array {
263 if ( ! isset( $this->all_plugins ) ) {
264 if ( ! function_exists( 'get_plugins' ) ) {
265 require_once ABSPATH . 'wp-admin/includes/plugin.php';
266 }
267
268 // This is missing must use and drop in plugins, so we need to fetch and merge them separately.
269 $site_plugins = apply_filters( 'all_plugins', get_plugins() );
270 $mu_plugins = apply_filters( 'show_advanced_plugins', true, 'mustuse' ) ? get_mu_plugins() : [];
271 $dropin_plugins = apply_filters( 'show_advanced_plugins', true, 'dropins' ) ? get_dropins() : [];
272
273 $this->all_plugins = [
274 'site' => is_array( $site_plugins ) ? $site_plugins : [],
275 'mustuse' => $mu_plugins,
276 'dropins' => $dropin_plugins,
277 ];
278 }
279
280 return $this->all_plugins;
281 }
282 }
283