PluginProbe ʕ •ᴥ•ʔ
Hustle – Email Marketing, Lead Generation, Optins, Popups / 7.8.14
Hustle – Email Marketing, Lead Generation, Optins, Popups v7.8.14
7.8.14 7.8.14.1 7.8.13 7.8.13.1 trunk 3.0 3.1 3.1.1 3.1.2 3.1.3 3.1.4 4.3.2 4.4.4 4.4.5 4.4.5.1 4.4.5.4 4.6 4.6.1.1 4.6.1.4 4.7.0.2 4.7.0.3 4.7.0.7 4.7.0.9 4.7.1.0 4.7.1.1 4.8.0.0 5.0.0 5.0.1 5.0.1.1 5.0.1.2 5.1 5.1.1 5.1.2 5.1.3 5.1.3.1 5.1.3.2 5.1.4 5.1.5 6.0 6.0.1 6.0.2 6.0.3 6.0.4.2 6.0.5 6.0.6.1 6.0.7 6.0.8.1 6.0.9 7.0.0.1 7.0.2 7.0.3 7.0.4 7.1.0 7.1.1 7.2.0 7.2.1 7.3.0 7.3.1 7.3.3 7.3.5 7.3.6 7.3.7 7.4.0 7.4.1 7.4.11 7.4.13 7.4.13.1 7.4.2 7.4.3 7.4.4 7.4.5 7.4.5.1 7.4.5.2 7.4.6 7.4.7 7.5.0 7.6.0 7.6.1 7.6.3 7.6.4 7.6.6 7.7.0 7.7.1 7.8.0 7.8.1 7.8.10 7.8.10.1 7.8.10.2 7.8.11 7.8.12 7.8.12.1 7.8.2 7.8.3 7.8.4 7.8.5 7.8.6 7.8.7 7.8.8 7.8.9 7.8.9.1 7.8.9.2 7.8.9.3
wordpress-popup / lib / plugins-cross-sell-page / core / class-utilities.php
wordpress-popup / lib / plugins-cross-sell-page / core Last commit date
class-container.php 1 year ago class-loader.php 1 year ago class-rest-api.php 1 year ago class-utilities.php 5 months ago plugins-list.php 5 months ago
class-utilities.php
279 lines
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 * Retrieves the path of a plugin by its slug.
45 *
46 * @param string $plugin_slug The plugin slug.
47 * @return string
48 */
49 public function get_plugin_path_by_slug( string $plugin_slug = '' ): string {
50 $plugins_list = $this->get_plugins_list();
51
52 return $this->get_value_from_associative_array( 'path', $plugins_list, array( 'slug' => $plugin_slug ) );
53 }
54
55 /**
56 * Extracts the value of a key from the first associative array in an array that matches the given criteria.
57 *
58 * @param string $key The key whose value you want to extract.
59 * @param array $input_list The array of associative arrays to filter.
60 * @param array $args The criteria for filtering (key-value pairs).
61 * @param string $operator How to combine the criteria ('AND' or 'OR').
62 * @return mixed The value for the specified key if found, or null.
63 */
64 public function get_value_from_associative_array( string $key = '', array $input_list = array(), array $args = array(), string $operator = 'AND' ) {
65 if ( empty( $key ) || empty( $input_list ) ) {
66 return null;
67 }
68
69 $filtered = wp_list_filter( $input_list, $args, $operator );
70
71 if ( ! empty( $filtered ) ) {
72 $first_item = current( $filtered );
73
74 // Check if $first_item is an array.
75 if ( is_array( $first_item ) ) {
76 return isset( $first_item[ $key ] ) ? $first_item[ $key ] : '';
77 }
78
79 // Check if $first_item is an object.
80 if ( is_object( $first_item ) ) {
81 return isset( $first_item->$key ) ? $first_item->$key : '';
82 }
83 }
84
85 return null;
86 }
87
88 /**
89 * Checks if a plugin is installed.
90 *
91 * @param string $file The plugin file path.
92 * @return bool
93 */
94 public function is_plugin_installed( string $file = '' ): bool {
95 // Include necessary plugin functions.
96 if ( ! function_exists( 'get_plugins' ) ) {
97 require_once ABSPATH . 'wp-admin/includes/plugin.php';
98 }
99
100 $all_plugins = get_plugins();
101
102 return is_array( $all_plugins ) ? isset( $all_plugins[ $file ] ) : false;
103 }
104
105 /**
106 * Get plugin statistics from WordPress.org API
107 *
108 * @param string $plugin_slug The plugin slug from wordpress.org.
109 * @param bool $force_refresh Optional. Whether to force a refresh of the data from the API.
110 * @return array|false Plugin data or false on failure.
111 */
112 public function get_plugin_stats( string $plugin_slug = '', bool $force_refresh = false ): mixed {
113 if ( empty( $plugin_slug ) ) {
114 return false;
115 }
116
117 // Set cache key - add a prefix for easy identification/deletion.
118 $transient_key = 'wpmudev_pcs_plugin_stats_' . sanitize_key( $plugin_slug );
119
120 // Try to get cached data first (unless force refresh is requested).
121 if ( ! $force_refresh ) {
122 $cached_data = get_transient( $transient_key );
123 if ( false !== $cached_data ) {
124 return $cached_data;
125 }
126 }
127
128 // If not in cache or force refresh, fetch from API.
129 $url = "https://api.wordpress.org/plugins/info/1.2/?action=plugin_information&slug={$plugin_slug}";
130
131 $response = wp_remote_get(
132 $url,
133 array(
134 'timeout' => 15, // Increased timeout for potentially slow API responses.
135 )
136 );
137
138 if ( is_wp_error( $response ) ) {
139 return false;
140 }
141
142 $status_code = wp_remote_retrieve_response_code( $response );
143 if ( 200 !== $status_code ) {
144 return false;
145 }
146
147 $body = wp_remote_retrieve_body( $response );
148 $data = json_decode( $body, true );
149
150 if ( empty( $data ) || ! is_array( $data ) ) {
151 return false;
152 }
153
154 // Format the data (with fallbacks for each field).
155 $plugin_data = array(
156 'name' => $data['name'] ?? '',
157 'version' => $data['version'] ?? '',
158 'active_installs' => $data['active_installs'] ?? 0,
159 'rating' => isset( $data['rating'] ) ? round( $data['rating'] / 20, 1 ) : 0, // Convert to 5-star scale.
160 'num_ratings' => $data['num_ratings'] ?? 0,
161 'last_updated' => $data['last_updated'] ?? '',
162 'requires_wp' => $data['requires'] ?? '',
163 'tested_wp' => $data['tested'] ?? '',
164 'author' => isset( $data['author'] ) ? wp_strip_all_tags( $data['author'] ) : '',
165 'homepage' => $data['homepage'] ?? '',
166 );
167
168 // Cache the data.
169 $expiration = DAY_IN_SECONDS * 3;
170
171 // Store in transient.
172 set_transient( $transient_key, $plugin_data, $expiration );
173
174 return $plugin_data;
175 }
176
177 /**
178 * Clear plugin stats cache for a specific plugin or all plugin stats
179 *
180 * @param string $plugin_slug Optional. Clear cache for specific plugin. If empty, clears all plugin stats.
181 * @return bool True on success
182 */
183 public function clear_plugin_stats_cache( string $plugin_slug = '' ): bool {
184 global $wpdb;
185
186 // If plugin slug provided, clear only that plugin's cache.
187 if ( ! empty( $plugin_slug ) ) {
188 $transient_key = 'wpmudev_pcs_plugin_stats_' . sanitize_key( $plugin_slug );
189 return delete_transient( $transient_key );
190 }
191
192 // Otherwise clear all plugin stats transients.
193 $wpdb->query( "DELETE FROM $wpdb->options WHERE option_name LIKE '_transient_wpmudev_pcs_plugin_stats_%'" );
194 return true;
195 }
196
197 /**
198 * Validate a value against a given schema.
199 *
200 * @param mixed $value The value to check.
201 * @param mixed $schema The schema that defines the expected type or structure.
202 * @param bool $strict If true, value must not have extra keys/properties.
203 * @return bool
204 */
205 public function validate_schema( $value, $schema, bool $strict = false ): bool {
206 // If schema is a simple type (string), perform a type check.
207 if ( is_string( $schema ) ) {
208 return $this->validate_type( $value, $schema );
209 }
210
211 // If schema is an array, we expect the value to be an array or object.
212 if ( is_array( $schema ) ) {
213 if ( ! is_array( $value ) && ! is_object( $value ) ) {
214 return false;
215 }
216
217 // Convert objects to an associative array of properties.
218 $value_array = is_object( $value ) ? get_object_vars( $value ) : $value;
219
220 // Check each key defined in the schema.
221 foreach ( $schema as $key => $expected_type ) {
222 if ( $strict && ! array_key_exists( $key, $value_array ) ) {
223 // In strict mode, all keys in the schema must be present.
224 return false;
225 }
226 if ( array_key_exists( $key, $value_array ) ) {
227 // Recursively validate the value for this key.
228 if ( ! $this->validate_schema( $value_array[ $key ], $expected_type, $strict ) ) {
229 return false;
230 }
231 }
232 }
233
234 // In strict mode, also ensure that there are no extra keys.
235 if ( $strict && count( $value_array ) !== count( $schema ) ) {
236 return false;
237 }
238 return true;
239 }
240
241 // If the schema is neither a string nor an array, we don't know how to validate.
242 return false;
243 }
244
245 /**
246 * Check if a value is of the expected type.
247 *
248 * @param mixed $value The value to check.
249 * @param string $type The expected type (e.g., 'int', 'string', 'bool', 'array', 'object').
250 * @return bool
251 */
252 public function validate_type( $value, string $type ): bool {
253 switch ( $type ) {
254 case 'int':
255 case 'integer':
256 return is_int( $value );
257 case 'string':
258 return is_string( $value );
259 case 'bool':
260 case 'boolean':
261 return is_bool( $value );
262 case 'float':
263 case 'double':
264 return is_float( $value );
265 case 'array':
266 return is_array( $value );
267 case 'object':
268 return is_object( $value );
269 default:
270 // If $type is a class name, check if $value is an instance of that class.
271 if ( class_exists( $type ) ) {
272 return $value instanceof $type;
273 }
274 // Unknown type.
275 return false;
276 }
277 }
278 }
279