PluginProbe
Visualizer – Tables & Charts Manager with Built-in AI Generator / 4.0.8
Visualizer – Tables & Charts Manager with Built-in AI Generator v4.0.8
4.0.8 4.0.7 4.0.6 4.0.5 4.0.4 4.0.3 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.1 3.1.2 3.1.3 3.10.0 3.10.1 3.10.10 3.10.11 3.10.12 3.10.13 3.10.14 3.10.15 3.10.2 3.10.3 All 149 releases
← All changes | vendor/codeinwp/themeisle-sdk/src/Modules/Featured_plugins.php +201 -17 3.10.134.0.8 View file →
@@ -12,8 +12,9 @@
12 12 */
13 13 namespace ThemeisleSDK\Modules;
14 14
15 15 use ThemeisleSDK\Common\Abstract_Module;
16 +use ThemeisleSDK\Loader;
16 17 use ThemeisleSDK\Product;
17 18
18 19 // Exit if accessed directly.
19 20 if ( ! defined( 'ABSPATH' ) ) {
@@ -32,8 +33,15 @@
32 33 */
33 34 private $transient_key = 'themeisle_sdk_featured_plugins_';
34 35
35 36 /**
37 + * The current product instance.
38 + *
39 + * @var Product|null
40 + */
41 + protected $product = null;
42 +
43 + /**
36 44 * Check if the module can be loaded.
37 45 *
38 46 * @param Product $product Product data.
39 47 *
@@ -43,11 +51,9 @@
43 51 if ( $this->is_from_partner( $product ) ) {
44 52 return false;
45 53 }
46 54
47 - $slug = $product->get_slug();
48 - // only load for products that contain "pro" in the slug.
49 - if ( strpos( $slug, 'pro' ) === false ) {
55 + if ( $product->is_wordpress_available() ) {
50 56 return false;
51 57 }
52 58
53 59 return ! apply_filters( 'themeisle_sdk_disable_featured_plugins', false );
@@ -60,8 +66,10 @@
60 66 *
61 67 * @return void
62 68 */
63 69 public function load( $product ) {
70 + $this->product = $product;
71 +
64 72 if ( ! current_user_can( 'install_plugins' ) ) {
65 73 return;
66 74 }
67 75
@@ -70,12 +78,63 @@
70 78 return;
71 79 }
72 80 add_filter( 'themeisle_sdk_plugin_api_filter_registered', '__return_true' );
73 81
74 - add_filter( 'plugins_api_result', [ $this, 'filter_plugin_api_results' ], 10, 3 );
82 + add_filter( 'plugins_api_result', [ $this, 'filter_plugin_api_results' ], 11, 3 );
83 +
84 + // Enqueue inline JS only on plugin-install.php.
85 + add_action( 'admin_enqueue_scripts', [ $this, 'maybe_add_inline_js' ] );
75 86 }
76 87
77 88 /**
89 + * Enqueue inline JavaScript only on plugin-install.php.
90 + *
91 + * @return void
92 + */
93 + public function maybe_add_inline_js() {
94 + $screen = get_current_screen();
95 + if ( isset( $screen->base ) && 'plugin-install' === $screen->base ) {
96 + add_action(
97 + 'admin_footer',
98 + function() {
99 + $text = esc_html( sprintf( Loader::$labels['promotions']['recommended'], $this->product->get_friendly_name() ) );
100 +
101 + echo '<script>(function(){
102 + function onPluginCardFound(card) {
103 + var recommendedDiv = document.createElement("div");
104 + Object.assign(recommendedDiv.style, {
105 + display: "block",
106 + textAlign: "center",
107 + padding: "0 12px 12px",
108 + background: "#f6f7f7"
109 + });
110 + recommendedDiv.innerHTML = "' . esc_html( $text ) . '";
111 + card.appendChild(recommendedDiv);
112 + }
113 +
114 + function checkAndRun() {
115 + var card = document.querySelector(".plugin-card-learning-management-system");
116 + if (card && !card.dataset.recommendedAdded) {
117 + onPluginCardFound(card);
118 + card.dataset.recommendedAdded = "true";
119 + }
120 + }
121 +
122 + var observer = new MutationObserver(function(mutations) {
123 + checkAndRun();
124 + });
125 +
126 + observer.observe(document.body, { childList: true, subtree: true });
127 +
128 + // Initial check in case the card is already present.
129 + checkAndRun();
130 + })();</script>';
131 + }
132 + );
133 + }
134 + }
135 +
136 + /**
78 137 * Filter the plugin API results to include the featured plugins.
79 138 *
80 139 * @param object $res The result object.
81 140 * @param string $action The type of information being requested from the Plugin Install API.
@@ -88,22 +147,142 @@
88 147 if ( 'query_plugins' !== $action ) {
89 148 return $res;
90 149 }
91 150
151 + if ( isset( $args->page ) && 1 === (int) $args->page && isset( $args->search ) && ! empty( $args->search ) ) {
152 + $original_count = count( (array) $res->plugins );
153 + $res->plugins = $this->maybe_prepend_lms_plugin( $res->plugins, $args );
154 +
155 + return $this->adjust_results_count( $res, count( (array) $res->plugins ) - $original_count );
156 + }
157 +
92 158 if ( ! isset( $args->browse ) || $args->browse !== 'featured' ) {
93 159 return $res;
94 160 }
95 161
162 + // Inject only on the first page so the same plugins are not repeated on every page.
163 + if ( isset( $args->page ) && (int) $args->page > 1 ) {
164 + return $res;
165 + }
166 +
96 167 $featured = $this->query_plugins_by_author( $args );
97 168
98 - $plugins = array_merge( $featured, (array) $res->plugins );
99 - $plugins = array_slice( $plugins, 0, $res->info['results'] );
100 - $res->plugins = $plugins;
169 + $original_count = count( (array) $res->plugins );
170 + $plugins = $this->remove_plugins_by_slug( (array) $res->plugins, $this->get_plugin_slugs( $featured ) );
171 + $res->plugins = array_merge( $featured, $plugins );
101 172
173 + return $this->adjust_results_count( $res, count( $res->plugins ) - $original_count );
174 + }
175 +
176 + /**
177 + * Prepend the LMS plugin if the search query matches LMS-related terms.
178 + *
179 + * @param array $plugins The plugins array.
180 + * @param object $args The plugin API arguments.
181 + * @return array
182 + */
183 + private function maybe_prepend_lms_plugin( $plugins, $args ) {
184 + $search = isset( $args->search ) ? strtolower( $args->search ) : '';
185 + if ( $this->matches_lms_search_keywords( $search ) ) {
186 + $filter_slugs = apply_filters( 'themeisle_sdk_masteriyo_filter_slugs', [ 'learning-management-system' ] );
187 + $masteriyo = $this->get_plugins_filtered_from_author( $args, $filter_slugs, 'masteriyo' );
188 +
189 + if ( ! empty( $masteriyo ) ) {
190 + // Remove existing copies of the injected plugins to avoid duplicates.
191 + $plugins = $this->remove_plugins_by_slug( (array) $plugins, $this->get_plugin_slugs( $masteriyo ) );
192 + $plugins = array_merge( $masteriyo, $plugins );
193 + }
194 + }
195 + return $plugins;
196 + }
197 +
198 + /**
199 + * Extract the slugs from a list of plugin entries.
200 + *
201 + * @param array $plugins The plugins list.
202 + *
203 + * @return array
204 + */
205 + private function get_plugin_slugs( $plugins ) {
206 + $slugs = [];
207 + foreach ( $plugins as $plugin ) {
208 + $plugin = (array) $plugin;
209 + if ( isset( $plugin['slug'] ) ) {
210 + $slugs[] = $plugin['slug'];
211 + }
212 + }
213 +
214 + return $slugs;
215 + }
216 +
217 + /**
218 + * Remove the plugins matching the provided slugs from the list.
219 + *
220 + * @param array $plugins The plugins list.
221 + * @param array $slugs The slugs to remove.
222 + *
223 + * @return array
224 + */
225 + private function remove_plugins_by_slug( $plugins, $slugs ) {
226 + return array_values(
227 + array_filter(
228 + $plugins,
229 + function( $plugin ) use ( $slugs ) {
230 + $plugin = (array) $plugin;
231 +
232 + return ! isset( $plugin['slug'] ) || ! in_array( $plugin['slug'], $slugs, true );
233 + }
234 + )
235 + );
236 + }
237 +
238 + /**
239 + * Adjust the reported total results count after injecting plugins.
240 + *
241 + * @param object $res The result object.
242 + * @param int $delta The net number of plugins added to the list.
243 + *
244 + * @return object
245 + */
246 + private function adjust_results_count( $res, $delta ) {
247 + if ( 0 !== $delta && isset( $res->info['results'] ) && is_numeric( $res->info['results'] ) ) {
248 + $res->info['results'] = max( 0, (int) $res->info['results'] + $delta );
249 + }
250 +
102 251 return $res;
103 252 }
104 253
105 254 /**
255 + * Check if a plugin search query matches LMS-related terms.
256 + *
257 + * @param string $search Search query.
258 + *
259 + * @return bool True if the search query matches LMS-related terms.
260 + */
261 + private function matches_lms_search_keywords( $search ) {
262 + $lms_keywords = array(
263 + 'lms',
264 + 'learn',
265 + 'course',
266 + 'courses',
267 + 'learning',
268 + 'academy',
269 + 'training',
270 + 'student',
271 + 'students',
272 + 'quiz',
273 + );
274 +
275 + foreach ( $lms_keywords as $keyword ) {
276 + if ( preg_match( '/(^|[^a-z0-9])' . preg_quote( $keyword, '/' ) . '([^a-z0-9]|$)/', $search ) === 1 ) {
277 + return true;
278 + }
279 + }
280 +
281 + return false;
282 + }
283 +
284 + /**
106 285 * Query plugins by author.
107 286 *
108 287 * @param object $args The arguments for the query.
109 288 *
@@ -115,9 +294,9 @@
115 294 $optimole_filter_slugs = apply_filters( 'themeisle_sdk_optimole_filter_slugs', [ 'optimole-wp' ] );
116 295 $filtered_from_optimole = $this->get_plugins_filtered_from_author( $args, $optimole_filter_slugs, 'Optimole' );
117 296 $featured = array_merge( $featured, $filtered_from_optimole );
118 297
119 - $themeisle_filter_slugs = apply_filters( 'themeisle_sdk_themeisle_filter_slugs', [ 'otter-blocks' ] );
298 + $themeisle_filter_slugs = apply_filters( 'themeisle_sdk_themeisle_filter_slugs', [ 'otter-blocks', 'wp-cloudflare-page-cache' ] );
120 299 $filtered_from_themeisle = $this->get_plugins_filtered_from_author( $args, $themeisle_filter_slugs );
121 300 $featured = array_merge( $featured, $filtered_from_themeisle );
122 301
123 302 return $featured;
@@ -131,13 +310,13 @@
131 310 * @param string $author The author to filter.
132 311 *
133 312 * @return array
134 313 */
135 - private function get_plugins_filtered_from_author( $args, $filter_slugs = [], $author = 'Themeisle' ) {
314 + protected function get_plugins_filtered_from_author( $args, $filter_slugs = [], $author = 'Themeisle' ) {
136 315
137 316 $cached = get_transient( $this->transient_key . $author );
138 - if ( $cached ) {
139 - return $cached;
317 + if ( false !== $cached ) {
318 + return (array) $cached;
140 319 }
141 320
142 321 $new_args = [
143 322 'page' => 1,
@@ -148,17 +327,22 @@
148 327 ];
149 328
150 329 $api = plugins_api( 'query_plugins', $new_args );
151 330 if ( is_wp_error( $api ) ) {
331 + // Cache the failure for a shorter period to avoid hammering the API on every request.
332 + set_transient( $this->transient_key . $author, [], HOUR_IN_SECONDS );
333 +
152 334 return [];
153 335 }
154 336
155 - $filtered = array_filter(
156 - $api->plugins,
157 - function( $plugin ) use ( $filter_slugs ) {
158 - $array_plugin = (array) $plugin;
159 - return in_array( $array_plugin['slug'], $filter_slugs );
160 - }
337 + $filtered = array_values(
338 + array_filter(
339 + $api->plugins,
340 + function( $plugin ) use ( $filter_slugs ) {
341 + $array_plugin = (array) $plugin;
342 + return in_array( $array_plugin['slug'], $filter_slugs );
343 + }
344 + )
161 345 );
162 346
163 347 set_transient( $this->transient_key . $author, $filtered, 12 * HOUR_IN_SECONDS );
164 348