PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 5.4.1
Defender Security – Malware Scanner, Login Security & Firewall v5.4.1
6.3.0 6.2.3 6.2.4 6.2.0 6.2.1 6.2.2 6.1.0 5.3.1 5.4.0 5.4.1 5.5.0 5.5.1 5.6.0 5.6.1 5.6.2 5.7.0 5.7.1 5.7.2 5.8.0 5.8.1 5.9.0 6.0.0 6.0.1 3.0.1 3.1.0 All 141 releases
defender-security / extra / plugins-cross-sell-page / core / class-utilities.php

class-utilities.php in Defender Security – Malware Scanner, Login Security & Firewall 5.4.1, at extra/plugins-cross-sell-page/core/class-utilities.php

300 lines 8.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Utilities class contains a list of common useful helper methods.
4 *
5 * @link https://wpmudev.com/
6 * @since 1.0.0
7 *
8 * @author WPMUDEV (https://wpmudev.com)
9 * @package WPMUDEV/Plugin_Cross_Sell
10 *
11 * @copyright (c) 2025, Incsub (http://incsub.com)
12 */
13
14 namespace WPMUDEV\Modules\Plugin_Cross_Sell;
15
16 // If this file is called directly, abort.
17 if ( ! defined( 'WPINC' ) ) {
18 die;
19 }
20
21 /**
22 * A list of general purpose utility methods.
23 */
24 class Utilities {
25 /**
26 * Returns the list of all plugins.
27 *
28 * @return array
29 */
30 public function get_plugins_list() {
31 static $plugins_list = null;
32
33 if ( is_null( $plugins_list ) ) {
34 $plugins_file = untrailingslashit( WPMUDEV_MODULE_PLUGIN_CROSS_SELL_DIR ) . '/core/plugins-list.php';
35 if ( file_exists( $plugins_file ) ) {
36 $plugins_list = require_once $plugins_file;
37 }
38 }
39
40 return is_array( $plugins_list ) ? $plugins_list : array();
41 }
42
43 /**
44 * Returns the list of free plugins.
45 *
46 * @return array
47 */
48 public function get_free_plugins(): array {
49 $plugins = $this->get_plugins_list();
50 return is_array( $plugins ) && ! empty( $plugins['free-plugins'] ) ? $plugins['free-plugins'] : array();
51 }
52
53 /**
54 * Returns the list of pro plugins.
55 *
56 * @return array
57 */
58 public function get_pro_plugins(): array {
59 $plugins = $this->get_plugins_list();
60 return is_array( $plugins ) && ! empty( $plugins['pro-plugins'] ) ? $plugins['pro-plugins'] : array();
61 }
62
63 /**
64 * Retrieves the path of a plugin by its slug.
65 *
66 * @param string $plugin_slug The plugin slug.
67 * @return string
68 */
69 public function get_plugin_path_by_slug( string $plugin_slug = '' ): string {
70 $free_plugins = $this->get_free_plugins();
71 $free_plugins = is_array( $free_plugins ) && ! empty( $free_plugins['free-plugins'] ) ? $free_plugins['free-plugins'] : $free_plugins;
72
73 return $this->get_value_from_associative_array( 'path', $free_plugins, array( 'slug' => $plugin_slug ) );
74 }
75
76 /**
77 * Extracts the value of a key from the first associative array in an array that matches the given criteria.
78 *
79 * @param string $key The key whose value you want to extract.
80 * @param array $input_list The array of associative arrays to filter.
81 * @param array $args The criteria for filtering (key-value pairs).
82 * @param string $operator How to combine the criteria ('AND' or 'OR').
83 * @return mixed The value for the specified key if found, or null.
84 */
85 public function get_value_from_associative_array( string $key = '', array $input_list = array(), array $args = array(), string $operator = 'AND' ) {
86 if ( empty( $key ) || empty( $input_list ) ) {
87 return null;
88 }
89
90 $filtered = wp_list_filter( $input_list, $args, $operator );
91
92 if ( ! empty( $filtered ) ) {
93 $first_item = current( $filtered );
94
95 // Check if $first_item is an array.
96 if ( is_array( $first_item ) ) {
97 return isset( $first_item[ $key ] ) ? $first_item[ $key ] : '';
98 }
99
100 // Check if $first_item is an object.
101 if ( is_object( $first_item ) ) {
102 return isset( $first_item->$key ) ? $first_item->$key : '';
103 }
104 }
105
106 return null;
107 }
108
109 /**
110 * Checks if a plugin is installed.
111 *
112 * @param string $file The plugin file path.
113 * @return bool
114 */
115 public function is_plugin_installed( string $file = '' ): bool {
116 // Include necessary plugin functions.
117 if ( ! function_exists( 'get_plugins' ) ) {
118 require_once ABSPATH . 'wp-admin/includes/plugin.php';
119 }
120
121 $all_plugins = get_plugins();
122
123 return is_array( $all_plugins ) ? isset( $all_plugins[ $file ] ) : false;
124 }
125
126 /**
127 * Get plugin statistics from WordPress.org API
128 *
129 * @param string $plugin_slug The plugin slug from wordpress.org.
130 * @param bool $force_refresh Optional. Whether to force a refresh of the data from the API.
131 * @return array|false Plugin data or false on failure.
132 */
133 public function get_plugin_stats( string $plugin_slug = '', bool $force_refresh = false ): mixed {
134 if ( empty( $plugin_slug ) ) {
135 return false;
136 }
137
138 // Set cache key - add a prefix for easy identification/deletion.
139 $transient_key = 'wpmudev_pcs_plugin_stats_' . sanitize_key( $plugin_slug );
140
141 // Try to get cached data first (unless force refresh is requested).
142 if ( ! $force_refresh ) {
143 $cached_data = get_transient( $transient_key );
144 if ( false !== $cached_data ) {
145 return $cached_data;
146 }
147 }
148
149 // If not in cache or force refresh, fetch from API.
150 $url = "https://api.wordpress.org/plugins/info/1.2/?action=plugin_information&slug={$plugin_slug}";
151
152 $response = wp_remote_get(
153 $url,
154 array(
155 'timeout' => 15, // Increased timeout for potentially slow API responses.
156 )
157 );
158
159 if ( is_wp_error( $response ) ) {
160 return false;
161 }
162
163 $status_code = wp_remote_retrieve_response_code( $response );
164 if ( 200 !== $status_code ) {
165 return false;
166 }
167
168 $body = wp_remote_retrieve_body( $response );
169 $data = json_decode( $body, true );
170
171 if ( empty( $data ) || ! is_array( $data ) ) {
172 return false;
173 }
174
175 // Format the data (with fallbacks for each field).
176 $plugin_data = array(
177 'name' => $data['name'] ?? '',
178 'version' => $data['version'] ?? '',
179 'active_installs' => $data['active_installs'] ?? 0,
180 'rating' => isset( $data['rating'] ) ? round( $data['rating'] / 20, 1 ) : 0, // Convert to 5-star scale.
181 'num_ratings' => $data['num_ratings'] ?? 0,
182 'last_updated' => $data['last_updated'] ?? '',
183 'requires_wp' => $data['requires'] ?? '',
184 'tested_wp' => $data['tested'] ?? '',
185 'author' => isset( $data['author'] ) ? wp_strip_all_tags( $data['author'] ) : '',
186 'homepage' => $data['homepage'] ?? '',
187 );
188
189 // Cache the data.
190 $expiration = DAY_IN_SECONDS * 3;
191
192 // Store in transient.
193 set_transient( $transient_key, $plugin_data, $expiration );
194
195 return $plugin_data;
196 }
197
198 /**
199 * Clear plugin stats cache for a specific plugin or all plugin stats
200 *
201 * @param string $plugin_slug Optional. Clear cache for specific plugin. If empty, clears all plugin stats.
202 * @return bool True on success
203 */
204 public function clear_plugin_stats_cache( string $plugin_slug = '' ): bool {
205 global $wpdb;
206
207 // If plugin slug provided, clear only that plugin's cache.
208 if ( ! empty( $plugin_slug ) ) {
209 $transient_key = 'wpmudev_pcs_plugin_stats_' . sanitize_key( $plugin_slug );
210 return delete_transient( $transient_key );
211 }
212
213 // Otherwise clear all plugin stats transients.
214 $wpdb->query( "DELETE FROM $wpdb->options WHERE option_name LIKE '_transient_wpmudev_pcs_plugin_stats_%'" );
215 return true;
216 }
217
218 /**
219 * Validate a value against a given schema.
220 *
221 * @param mixed $value The value to check.
222 * @param mixed $schema The schema that defines the expected type or structure.
223 * @param bool $strict If true, value must not have extra keys/properties.
224 * @return bool
225 */
226 public function validate_schema( $value, $schema, bool $strict = false ): bool {
227 // If schema is a simple type (string), perform a type check.
228 if ( is_string( $schema ) ) {
229 return $this->validate_type( $value, $schema );
230 }
231
232 // If schema is an array, we expect the value to be an array or object.
233 if ( is_array( $schema ) ) {
234 if ( ! is_array( $value ) && ! is_object( $value ) ) {
235 return false;
236 }
237
238 // Convert objects to an associative array of properties.
239 $value_array = is_object( $value ) ? get_object_vars( $value ) : $value;
240
241 // Check each key defined in the schema.
242 foreach ( $schema as $key => $expected_type ) {
243 if ( $strict && ! array_key_exists( $key, $value_array ) ) {
244 // In strict mode, all keys in the schema must be present.
245 return false;
246 }
247 if ( array_key_exists( $key, $value_array ) ) {
248 // Recursively validate the value for this key.
249 if ( ! $this->validate_schema( $value_array[ $key ], $expected_type, $strict ) ) {
250 return false;
251 }
252 }
253 }
254
255 // In strict mode, also ensure that there are no extra keys.
256 if ( $strict && count( $value_array ) !== count( $schema ) ) {
257 return false;
258 }
259 return true;
260 }
261
262 // If the schema is neither a string nor an array, we don't know how to validate.
263 return false;
264 }
265
266 /**
267 * Check if a value is of the expected type.
268 *
269 * @param mixed $value The value to check.
270 * @param string $type The expected type (e.g., 'int', 'string', 'bool', 'array', 'object').
271 * @return bool
272 */
273 public function validate_type( $value, string $type ): bool {
274 switch ( $type ) {
275 case 'int':
276 case 'integer':
277 return is_int( $value );
278 case 'string':
279 return is_string( $value );
280 case 'bool':
281 case 'boolean':
282 return is_bool( $value );
283 case 'float':
284 case 'double':
285 return is_float( $value );
286 case 'array':
287 return is_array( $value );
288 case 'object':
289 return is_object( $value );
290 default:
291 // If $type is a class name, check if $value is an instance of that class.
292 if ( class_exists( $type ) ) {
293 return $value instanceof $type;
294 }
295 // Unknown type.
296 return false;
297 }
298 }
299 }
300