PluginProbe
Analytify – Google Analytics Dashboard For WordPress (GA4 analytics tracking) / trunk
Analytify – Google Analytics Dashboard For WordPress (GA4 analytics tracking) vtrunk
9.1.2 9.1.1 9.1.0 9.0.2 9.0.1 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.1.0 1.1.1 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.3.0 1.3.1 1.3.2 All 153 releases
wp-analytify / inc / page-addons.php

page-addons.php in Analytify – Google Analytics Dashboard For WordPress (GA4 analytics tracking) trunk, at inc/page-addons.php

1,182 lines 42.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName -- File naming is acceptable for this plugin structure.
2 /**
3 * Addons Page Handler
4 *
5 * @package WP_Analytify
6 */
7
8 // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName -- File naming is acceptable
9
10 if ( ! class_exists( 'WP_Analytify_Addons' ) ) {
11
12 /**
13 * WP Analytify Addons Class
14 */
15 class WP_Analytify_Addons {
16
17 /**
18 * List of plugins.
19 *
20 * @var array<string, mixed>
21 */
22 protected $plugins_list;
23
24 /**
25 * List of modules.
26 *
27 * @var array<string, mixed>
28 */
29 protected $modules_list;
30
31 /**
32 * Constructor
33 *
34 * @version 7.0.5
35 * @return void
36 */
37 public function __construct() {
38 $this->plugins_list = get_plugins();
39 $this->modules_list = $this->modules();
40
41 // Use priority 20 to ensure it runs after scripts-styles.php (which uses default priority 10).
42 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_scripts' ), 20 );
43 }
44
45 /**
46 * Returns a list of addons
47 *
48 * @return array<string, mixed>
49 * @since 1.3
50 */
51 public function addons() {
52 $logger = function_exists( 'analytify_get_logger' ) ? analytify_get_logger() : null;
53
54 if ( ! class_exists( 'WP_Analytify_Pro' ) || ( defined( 'ANALYTIFY_PRO_VERSION' ) && version_compare( ANALYTIFY_PRO_VERSION, '6.0.0', '<' ) ) ) {
55
56 // Get the transient where the addons are stored on-site.
57 $data = get_transient( 'analytify_api_addons' );
58
59 // If we already have data, return it.
60 if ( ! empty( $data ) && is_array( $data ) && count( $data ) > 0 ) {
61 // Validate the cached data structure.
62 $valid_count = 0;
63 foreach ( $data as $item ) {
64 if ( is_object( $item ) && isset( $item->slug ) && isset( $item->title ) ) {
65 ++$valid_count;
66 }
67 }
68
69 // Only return if we have valid addon data.
70 if ( $valid_count > 0 ) {
71 return $data;
72 } else {
73 // Clear bad cached data.
74 delete_transient( 'analytify_api_addons' );
75 }
76 }
77
78 // Make sure this matches the exact URL from your site.
79 $url = 'https://analytify.io/wp-json/analytify/v1/plugins';
80
81 // Allow SSL verification to be disabled for local development.
82 $sslverify = apply_filters( 'analytify_api_sslverify', true );
83
84 $response = wp_remote_get(
85 $url,
86 array(
87 'timeout' => 20,
88 'sslverify' => $sslverify,
89 )
90 );
91
92 if ( ! is_wp_error( $response ) ) {
93 $response_code = wp_remote_retrieve_response_code( $response );
94
95 // Only process if we got a successful response.
96 if ( 200 === (int) $response_code ) {
97 // Decode the data that we got.
98 $body = wp_remote_retrieve_body( $response );
99
100 if ( ! empty( $body ) ) {
101 $data = json_decode( $body, false );
102
103 // Validate decoded data - can be array or object.
104 if ( json_last_error() === JSON_ERROR_NONE ) {
105 // Convert object to array if needed.
106 if ( is_object( $data ) ) {
107 $data = json_decode( $body, true );
108 }
109
110 // Ensure we have a valid array with at least one valid addon.
111 if ( ! empty( $data ) && is_array( $data ) ) {
112 // Validate structure - ensure we have objects with required fields.
113 $valid_data = array();
114 foreach ( $data as $key => $item ) {
115 // Convert array items to objects if needed.
116 if ( is_array( $item ) ) {
117 $item = (object) $item;
118 }
119 if ( is_object( $item ) && isset( $item->slug ) && isset( $item->title ) ) {
120 $valid_data[ $key ] = $item;
121 }
122 }
123
124 if ( ! empty( $valid_data ) ) {
125 // Store the data for a week.
126 set_transient( 'analytify_api_addons', $valid_data, 7 * DAY_IN_SECONDS );
127 return $valid_data;
128 }
129 }
130 }
131 }
132 }
133 } else {
134 // Log the error for debugging, but don't expose it to users.
135 if ( $logger && method_exists( $logger, 'warning' ) ) {
136 $logger->warning(
137 'Failed to fetch addons from API.',
138 array(
139 'source' => 'addons',
140 'error' => $response->get_error_message(),
141 'ssl_verify' => $sslverify,
142 )
143 );
144 }
145 // Try again with SSL verification disabled if it was enabled.
146 if ( $sslverify ) {
147 $retry_response = wp_remote_get(
148 $url,
149 array(
150 'timeout' => 20,
151 'sslverify' => false,
152 )
153 );
154
155 if ( ! is_wp_error( $retry_response ) ) {
156 $retry_code = wp_remote_retrieve_response_code( $retry_response );
157 if ( 200 === (int) $retry_code ) {
158 $retry_body = wp_remote_retrieve_body( $retry_response );
159 if ( ! empty( $retry_body ) ) {
160 $retry_data = json_decode( $retry_body, false );
161 if ( json_last_error() === JSON_ERROR_NONE ) {
162 // Convert object to array if needed.
163 if ( is_object( $retry_data ) ) {
164 $retry_data = json_decode( $retry_body, true );
165 }
166
167 // Validate structure.
168 if ( ! empty( $retry_data ) && is_array( $retry_data ) ) {
169 $valid_data = array();
170 foreach ( $retry_data as $key => $item ) {
171 if ( is_array( $item ) ) {
172 $item = (object) $item;
173 }
174 if ( is_object( $item ) && isset( $item->slug ) && isset( $item->title ) ) {
175 $valid_data[ $key ] = $item;
176 }
177 }
178
179 if ( ! empty( $valid_data ) ) {
180 set_transient( 'analytify_api_addons', $valid_data, 7 * DAY_IN_SECONDS );
181 return $valid_data;
182 }
183 }
184 }
185 }
186 }
187 }
188 }
189 }
190
191 return array();
192 }
193
194 // API URL to fetch addons.
195 $url = 'https://analytify.io/wp-json/analytify/v1/plugins';
196
197 // Allow SSL verification to be disabled for local development.
198 $sslverify = apply_filters( 'analytify_api_sslverify', true );
199
200 // Fetch data from the API.
201 $response = wp_remote_get(
202 $url,
203 array(
204 'timeout' => 20,
205 'sslverify' => $sslverify,
206 )
207 );
208
209 if ( ! is_wp_error( $response ) ) {
210 $response_code = wp_remote_retrieve_response_code( $response );
211
212 // Only process if we got a successful response.
213 if ( 200 === (int) $response_code ) {
214 // Decode the JSON response.
215 $body = wp_remote_retrieve_body( $response );
216 if ( ! empty( $body ) ) {
217 $data = json_decode( $body, false );
218
219 // Validate decoded data - can be array or object.
220 if ( json_last_error() === JSON_ERROR_NONE ) {
221 // Convert object to array if needed.
222 if ( is_object( $data ) ) {
223 $data = json_decode( $body, true );
224 }
225
226 // Ensure we have a valid array with at least one valid addon.
227 if ( ! empty( $data ) && is_array( $data ) ) {
228 // Validate structure - ensure we have objects with required fields.
229 $valid_data = array();
230 foreach ( $data as $key => $item ) {
231 if ( is_array( $item ) ) {
232 $item = (object) $item;
233 }
234 if ( is_object( $item ) && isset( $item->slug ) && isset( $item->title ) ) {
235 $valid_data[ $key ] = $item;
236 }
237 }
238
239 if ( ! empty( $valid_data ) ) {
240 // Cache the data for a week.
241 set_transient( 'analytify_api_addons', $valid_data, 7 * DAY_IN_SECONDS );
242 }
243 }
244 }
245 }
246 }
247 } else {
248 // Log the error for debugging, but don't expose it to users.
249 if ( $logger && method_exists( $logger, 'warning' ) ) {
250 $logger->warning(
251 'Failed to fetch addons from API (retry section).',
252 array(
253 'source' => 'addons',
254 'error' => $response->get_error_message(),
255 'ssl_verify' => $sslverify,
256 )
257 );
258 }
259 // Try again with SSL verification disabled if it was enabled.
260 if ( $sslverify ) {
261 $retry_response = wp_remote_get(
262 $url,
263 array(
264 'timeout' => 20,
265 'sslverify' => false,
266 )
267 );
268
269 if ( ! is_wp_error( $retry_response ) ) {
270 $retry_code = wp_remote_retrieve_response_code( $retry_response );
271 if ( 200 === (int) $retry_code ) {
272 $retry_body = wp_remote_retrieve_body( $retry_response );
273 if ( ! empty( $retry_body ) ) {
274 $retry_data = json_decode( $retry_body, false );
275 if ( json_last_error() === JSON_ERROR_NONE ) {
276 // Convert object to array if needed.
277 if ( is_object( $retry_data ) ) {
278 $retry_data = json_decode( $retry_body, true );
279 }
280
281 if ( ! empty( $retry_data ) && is_array( $retry_data ) ) {
282 $valid_data = array();
283 foreach ( $retry_data as $key => $item ) {
284 if ( is_array( $item ) ) {
285 $item = (object) $item;
286 }
287 if ( is_object( $item ) && isset( $item->slug ) && isset( $item->title ) ) {
288 $valid_data[ $key ] = $item;
289 }
290 }
291
292 if ( ! empty( $valid_data ) ) {
293 set_transient( 'analytify_api_addons', $valid_data, 7 * DAY_IN_SECONDS );
294 }
295 }
296 }
297 }
298 }
299 }
300 }
301 }
302
303 // Fetch the cached API data or an empty array if not available.
304 $api_data = get_transient( 'analytify_api_addons' );
305 $api_data = $api_data ? $api_data : array();
306
307 // Filter out WooCommerce and EDD add-ons.
308 if ( version_compare( ANALYTIFY_PRO_VERSION, '6.1.0', '<' ) ) {
309 $filtered_addons = array_filter(
310 $api_data,
311 function ( $addon ) {
312 return ! (
313 isset( $addon->slug ) && (
314 strpos( $addon->slug, 'woocommerce' ) !== false ||
315 strpos( $addon->slug, 'edd' ) !== false ||
316 strpos( $addon->slug, 'campaigns' ) !== false ||
317 strpos( $addon->slug, 'authors' ) !== false
318 )
319 );
320 }
321 );
322 } else {
323 $filtered_addons = array_filter(
324 $api_data,
325 function ( $addon ) {
326 return ! (
327 isset( $addon->slug ) && (
328 strpos( $addon->slug, 'woocommerce' ) !== false ||
329 strpos( $addon->slug, 'edd' ) !== false ||
330 strpos( $addon->slug, 'campaigns' ) !== false ||
331 strpos( $addon->slug, 'authors' ) !== false ||
332 strpos( $addon->slug, 'forms' ) !== false ||
333 strpos( $addon->slug, 'email' ) !== false ||
334 strpos( $addon->slug, 'goals' ) !== false ||
335 strpos( $addon->slug, 'lifterlms' ) !== false ||
336 strpos( $addon->slug, 'pmpro' ) !== false ||
337 strpos( $addon->slug, 'learndash' ) !== false
338
339 )
340 );
341 }
342 );
343 }
344 // Return the filtered addons as associative array.
345 return $filtered_addons;
346 }
347 /**
348 * Enqueue admin scripts and localize variables.
349 *
350 * @version 9.1.1
351 * @return void
352 */
353 public function enqueue_admin_scripts() {
354 // Get current screen to check if we're on addons page.
355 $screen = get_current_screen();
356 if ( ! $screen || strpos( $screen->base, 'analytify-addons' ) === false ) {
357 return;
358 }
359
360 $addons = $this->addons();
361
362 // Extract slugs from addons array for JavaScript validation.
363 $slugs = array();
364
365 // Add Pro addon slugs from the pro_addons option.
366 $pro_addons_local = get_option( 'wp_analytify_pro_addons' );
367 if ( ! empty( $pro_addons_local ) && is_array( $pro_addons_local ) ) {
368 foreach ( $pro_addons_local as $slug => $data ) {
369 $slugs[] = $slug;
370 // Also add the full plugin path format for compatibility.
371 $slugs[] = $slug . '/' . $slug . '.php';
372 }
373 }
374
375 // Add API addon slugs.
376 if ( ! empty( $addons ) && is_array( $addons ) ) {
377 foreach ( $addons as $key => $addon ) {
378 // Handle both object and array formats.
379 if ( is_object( $addon ) && isset( $addon->slug ) ) {
380 $slug = $addon->slug;
381 if ( ! in_array( $slug, $slugs, true ) ) {
382 $slugs[] = $slug;
383 }
384 // Also add the full plugin path format for compatibility.
385 $plugin_path = $slug . '/' . ( 'analytify-analytics-dashboard-widget' === $slug ? 'wp-analytify-dashboard' : $slug ) . '.php';
386 if ( ! in_array( $plugin_path, $slugs, true ) ) {
387 $slugs[] = $plugin_path;
388 }
389 } elseif ( is_array( $addon ) && isset( $addon['slug'] ) ) {
390 $slug = $addon['slug'];
391 if ( ! in_array( $slug, $slugs, true ) ) {
392 $slugs[] = $slug;
393 }
394 $plugin_path = $slug . '/' . ( 'analytify-analytics-dashboard-widget' === $slug ? 'wp-analytify-dashboard' : $slug ) . '.php';
395 if ( ! in_array( $plugin_path, $slugs, true ) ) {
396 $slugs[] = $plugin_path;
397 }
398 } elseif ( is_string( $key ) ) {
399 // If key is the slug (for associative arrays).
400 if ( ! in_array( $key, $slugs, true ) ) {
401 $slugs[] = $key;
402 }
403 $plugin_path = $key . '/' . ( 'analytify-analytics-dashboard-widget' === $key ? 'wp-analytify-dashboard' : $key ) . '.php';
404 if ( ! in_array( $plugin_path, $slugs, true ) ) {
405 $slugs[] = $plugin_path;
406 }
407 }
408 }
409 }
410
411 // Add module slugs.
412 $modules_local = $this->modules();
413 if ( ! empty( $modules_local ) && is_array( $modules_local ) ) {
414 foreach ( $modules_local as $slug => $data ) {
415 if ( is_array( $data ) && isset( $data['slug'] ) ) {
416 $module_slug = $data['slug'];
417 if ( ! in_array( $module_slug, $slugs, true ) ) {
418 $slugs[] = $module_slug;
419 }
420 } elseif ( is_string( $slug ) ) {
421 if ( ! in_array( $slug, $slugs, true ) ) {
422 $slugs[] = $slug;
423 }
424 }
425 }
426 }
427
428 // Ensure script is enqueued (should already be done by scripts-styles.php).
429 if ( ! wp_script_is( 'analytify-addons-js', 'enqueued' ) ) {
430 $plugin_file = defined( 'WP_ANALYTIFY_PLUGIN_DIR' ) ? WP_ANALYTIFY_PLUGIN_DIR . '/wp-analytify.php' : dirname( __DIR__ ) . '/wp-analytify.php';
431 wp_enqueue_script( 'analytify-addons-js', plugins_url( 'assets/js/wp-analytify-addons' . analytify_get_asset_suffix() . '.js', $plugin_file ), array( 'jquery' ), ANALYTIFY_VERSION, false );
432 }
433
434 // Localize to the correct script handle.
435 // wp_localize_script replaces any previous localization.
436 $localized = wp_localize_script(
437 'analytify-addons-js',
438 'analytify_addons',
439 array(
440 'ajaxurl' => admin_url( 'admin-ajax.php' ),
441 'nonce' => wp_create_nonce( 'addons' ),
442 'install_nonce' => wp_create_nonce( 'updates' ),
443 'activate_dashboard_nonce' => wp_create_nonce( 'activate-analytify-dashboard' ),
444 'allowed_slugs' => $slugs,
445 'i18n' => array(
446 'installing' => __( 'Installing...', 'wp-analytify' ),
447 'activating' => __( 'Activating...', 'wp-analytify' ),
448 'installed' => __( 'Installed & Activated', 'wp-analytify' ),
449 'get_addon' => __( 'Get this add-on', 'wp-analytify' ),
450 ),
451 )
452 );
453 }
454
455 /**
456 * Check plugin status
457 *
458 * @param string $slug Plugin slug.
459 * @param string $extension_or_status Extension or status.
460 * @return array<string, mixed>
461 * @since 1.3
462 */
463 public function pro_addons_status( $slug, $extension_or_status ) {
464 $nonce = wp_create_nonce( $slug );
465
466 if ( 'active' === $extension_or_status ) {
467 printf( // translators: Deactivate add-on.
468 esc_html__( '%1$s Deactivate add-on %2$s', 'wp-analytify' ),
469 '<button type="button" class="button-primary analytify-addon-state analytify-deactivate-addon" data-slug="' . esc_attr( $slug ) . '" data-set-state="deactive" data-nonce="' . esc_attr( $nonce ) . '" >',
470 '</button>'
471 );
472 return array(
473 'status' => 'active',
474 'slug' => $slug,
475 );
476 } else {
477 printf( // translators: Activate add-on.
478 esc_html__( '%1$s Activate add-on %2$s', 'wp-analytify' ),
479 '<button type="button" class="button-primary analytify-addon-state analytify-activate-addon" data-slug="' . esc_attr( $slug ) . '" data-set-state="active" data-nonce="' . esc_attr( $nonce ) . '" >',
480 '</button>'
481 );
482 return array(
483 'status' => 'inactive',
484 'slug' => $slug,
485 );
486 }
487 }
488
489 /**
490 * Get addon status.
491 *
492 * @param string $slug Plugin slug.
493 * @param mixed $extension Extension data.
494 * @version 9.1.1
495 * @return void
496 */
497 public function addons_status( $slug, $extension ) {
498 $folder_slug = $slug;
499 // Free addon has different filename.
500 $addon_file_name = ( 'analytify-analytics-dashboard-widget' === $slug ) ? 'wp-analytify-dashboard' : $slug;
501 $plugin_file = $slug . '/' . $addon_file_name . '.php';
502
503 if ( is_plugin_active( $plugin_file ) ) {
504 // translators: Deactivate add-on.
505 printf( esc_html__( '%1$s Deactivate add-on %2$s', 'wp-analytify' ), '<button type="button" class="button-primary analytify-module-state analytify-deactivate-module" data-slug="' . esc_attr( $plugin_file ) . '" data-set-state="deactive" data-internal-module="false">', '</button>' );
506
507 } elseif ( array_key_exists( $plugin_file, $this->plugins_list ) ) {
508 // translators: Activate add-on.
509 printf( esc_html__( '%1$s Activate add-on %2$s', 'wp-analytify' ), '<button type="button" class="button-primary analytify-module-state analytify-activate-module" data-slug="' . esc_attr( $plugin_file ) . '" data-set-state="active" data-internal-module="false">', '</button>' );
510
511 } elseif ( is_plugin_inactive( $plugin_file ) ) {
512
513 if ( isset( $extension->status ) && '' !== $extension->status ) {
514 // translators: Simple shortcodes.
515 printf( esc_html__( '%1$s Download %2$s', 'wp-analytify' ), '<a target="_blank" href="' . esc_url( isset( $extension->url ) ? $extension->url : '#' ) . '" class="button-primary">', '</a>' );
516 } elseif ( 'analytify-analytics-dashboard-widget' === $folder_slug && current_user_can( 'install_plugins' ) ) {
517 printf(
518 /* translators: 1: opening button tag, 2: closing button tag */
519 esc_html__( '%1$s Get this add-on %2$s', 'wp-analytify' ),
520 '<button type="button" class="button-primary analytify-install-dashboard-addon" data-slug="' . esc_attr( $plugin_file ) . '" data-install-nonce="' . esc_attr( wp_create_nonce( 'updates' ) ) . '" data-activate-nonce="' . esc_attr( wp_create_nonce( 'activate-analytify-dashboard' ) ) . '">',
521 '</button>'
522 );
523 } else {
524 // translators: Get add-on.
525 printf( esc_html__( '%1$s Get this add-on %2$s', 'wp-analytify' ), '<a target="_blank" href="' . esc_url( isset( $extension->url ) ? $extension->url : '#' ) . '" class="button-primary">', '</a>' );
526 }
527 }
528 }
529
530
531 /**
532 * Check if pro version is supporitng modules.
533 *
534 * @version 7.0.5
535 * @return bool
536 */
537 public function check_pro_support() {
538
539 if ( class_exists( 'WP_Analytify_Pro' ) ) {
540 $plugins = get_plugins();
541
542 if ( version_compare( $plugins['wp-analytify-pro/wp-analytify-pro.php']['Version'], '4.0', '>=' ) ) {
543 return true;
544 }
545 }
546
547 return false;
548 }
549
550 /**
551 * Whether a bundled Pro module main file exists (Pro ZIP includes the integration).
552 *
553 * @param string $slug Module slug, e.g. wp-analytify-pmpro.
554 * @return bool
555 */
556 public function pro_bundled_module_is_installed( $slug ) {
557 if ( ! is_string( $slug ) || '' === $slug ) {
558 return false;
559 }
560 if ( ! defined( 'ANALYTIFY_PRO_ROOT_PATH' ) ) {
561 return false;
562 }
563 // Pixels ships as class-analytify-pixels-tracking.php (not slug/slug.php).
564 if ( 'pixels-tracking' === $slug ) {
565 $main = ANALYTIFY_PRO_ROOT_PATH . '/inc/modules/pixels-tracking/class-analytify-pixels-tracking.php';
566 return file_exists( $main );
567 }
568 $main = ANALYTIFY_PRO_ROOT_PATH . '/inc/modules/' . $slug . '/' . $slug . '.php';
569 return file_exists( $main );
570 }
571
572 /**
573 * Changelog URL for “Update Analytify Pro” CTAs on the Add-ons screen.
574 *
575 * @param string $utm_content UTM content slug for analytics.
576 * @return string
577 */
578 public function get_analytify_pro_changelog_url( $utm_content = 'addons' ) {
579 return 'https://analytify.io/changelog/?utm_source=analytify-pro&utm_medium=addons&utm_campaign=update-pro&utm_content=' . rawurlencode( (string) $utm_content );
580 }
581
582 /**
583 * Returns a list of modules.
584 *
585 * @return array<string, mixed>
586 */
587 public function modules() {
588
589 if ( get_option( 'wp_analytify_modules' ) ) {
590 return get_option( 'wp_analytify_modules' );
591 }
592
593 return array();
594 }
595
596 /**
597 * Reorder an associative list keyed by slug (e.g. pro add-ons or modules) for the Add-ons screen.
598 *
599 * @param array<string, mixed> $items Rows keyed by slug.
600 * @param array<int, string> $slug_order Preferred slug order.
601 * @return array<string, mixed>
602 */
603 public function reorder_assoc_by_slug_list( $items, $slug_order ) {
604 if ( empty( $items ) || ! is_array( $items ) ) {
605 return $items;
606 }
607 if ( ! is_array( $slug_order ) ) {
608 $slug_order = array();
609 }
610 $ordered = array();
611 foreach ( $slug_order as $slug ) {
612 if ( isset( $items[ $slug ] ) ) {
613 $ordered[ $slug ] = $items[ $slug ];
614 }
615 }
616 foreach ( $items as $slug => $row ) {
617 if ( ! isset( $ordered[ $slug ] ) ) {
618 $ordered[ $slug ] = $row;
619 }
620 }
621 return $ordered;
622 }
623
624 /**
625 * Reorder API add-on objects (each has a slug) for the Add-ons screen.
626 *
627 * @param array<int, object> $extensions Objects with a slug property.
628 * @param array<int, string> $slug_order Slugs to show first, in order.
629 * @return array<int, object>
630 */
631 public function reorder_extension_list_by_slugs( $extensions, $slug_order ) {
632 if ( empty( $extensions ) || ! is_array( $extensions ) ) {
633 return $extensions;
634 }
635 if ( ! is_array( $slug_order ) ) {
636 $slug_order = array();
637 }
638 $by_slug = array();
639 $no_slug = array();
640 foreach ( $extensions as $item ) {
641 if ( is_object( $item ) && isset( $item->slug ) && '' !== $item->slug ) {
642 $by_slug[ $item->slug ] = $item;
643 } else {
644 $no_slug[] = $item;
645 }
646 }
647 $out = array();
648 foreach ( $slug_order as $slug ) {
649 if ( isset( $by_slug[ $slug ] ) ) {
650 $out[] = $by_slug[ $slug ];
651 unset( $by_slug[ $slug ] );
652 }
653 }
654 foreach ( $by_slug as $item ) {
655 $out[] = $item;
656 }
657 foreach ( $no_slug as $item ) {
658 $out[] = $item;
659 }
660 return $out;
661 }
662
663 /**
664 * Check module status.
665 *
666 * @param string $slug Plugin slug.
667 * @return void
668 */
669 public function check_module_status( $slug ) {
670
671 if ( ! isset( $this->modules_list[ $slug ] ) || ! is_array( $this->modules_list[ $slug ] ) ) {
672 return;
673 }
674
675 if ( 'pixels-tracking' === $slug && class_exists( 'WP_Analytify_Pro', false )
676 && function_exists( 'wp_analytify_pro_pixels_tracking_module_file_exists' )
677 && ! wp_analytify_pro_pixels_tracking_module_file_exists()
678 ) {
679 $update_url = 'https://analytify.io/changelog/?utm_source=wp-analytify&utm_medium=addons&utm_campaign=pixels-pro-update';
680 printf(
681 /* translators: %1$s: Opening anchor tag, %2$s: Closing anchor tag. */
682 esc_html__( '%1$s Update Analytify Pro %2$s', 'wp-analytify' ),
683 '<a target="_blank" rel="noopener noreferrer" class="button-primary" href="' . esc_url( $update_url ) . '">',
684 '</a>'
685 );
686 return;
687 }
688
689 $nonce = wp_create_nonce( $slug );
690
691 $pro_module_cards = array(
692 'wp-analytify-pmpro',
693 'wp-analytify-learndash',
694 'wp-analytify-lifterlms',
695 );
696
697 if ( in_array( $slug, $pro_module_cards, true ) && class_exists( 'WP_Analytify_Pro' ) && ! $this->pro_bundled_module_is_installed( $slug ) ) {
698 $changelog = $this->get_analytify_pro_changelog_url( $slug );
699 printf(
700 /* translators: %1$s: opening anchor tag, %2$s: closing anchor tag. */
701 esc_html__( '%1$s Update Analytify Pro %2$s', 'wp-analytify' ),
702 '<a class="button-primary" href="' . esc_url( $changelog ) . '" target="_blank" rel="noopener noreferrer">',
703 '</a>'
704 );
705 return;
706 }
707
708 if ( 'active' === $this->modules_list[ $slug ]['status'] && $this->check_pro_support() ) {
709 // translators: Deactivate add-on.
710 printf( esc_html__( '%1$s Deactivate add-on %2$s', 'wp-analytify' ), '<button type="button" class="button-primary analytify-module-state analytify-deactivate-module" data-slug="' . esc_attr( $slug ) . '" data-set-state="deactive" data-internal-module="true" data-nonce="' . esc_attr( $nonce ) . '" >', '</button>' );
711
712 } elseif ( ! $this->modules_list[ $slug ]['status'] && $this->check_pro_support() ) {
713 // translators: Activate add-on.
714 printf( esc_html__( '%1$s Activate add-on %2$s', 'wp-analytify' ), '<button type="button" class="button-primary analytify-module-state analytify-activate-module" data-slug="' . esc_attr( $slug ) . '" data-set-state="active" data-internal-module="true" data-nonce="' . esc_attr( $nonce ) . '" >', '</button>' );
715
716 } elseif ( 'deactive' === $this->modules_list[ $slug ]['status'] && $this->check_pro_support() ) {
717 // translators: Activate add-on.
718 printf( esc_html__( '%1$s Activate add-on %2$s', 'wp-analytify' ), '<button type="button" class="button-primary analytify-module-state analytify-activate-module" data-slug="' . esc_attr( $slug ) . '" data-set-state="active" data-internal-module="true" data-nonce="' . esc_attr( $nonce ) . '" >', '</button>' );
719
720 } else {
721 // translators: Get add-on.
722 printf( esc_html__( '%1$s Get this add-on %2$s', 'wp-analytify' ), '<a type="button" class="button-primary analytify-activate-module" href=" ' . esc_url( $this->modules_list[ $slug ]['url'] ) . '?utm_source=analytify-lite" target="_blank">', '</a>' );
723 }
724 }
725
726 /**
727 * Get addon icon URL.
728 *
729 * @param string $slug Plugin slug.
730 * @return string
731 */
732 public function get_addon_icon( $slug ) {
733
734 return ( defined( 'ANALYTIFY_PLUGIN_URL' ) ? ANALYTIFY_PLUGIN_URL : '' ) . '/assets/img/addons-svgs/' . $slug . '.svg';
735 }
736
737 /**
738 * Print one API-sourced add-on card (Add-ons admin screen).
739 *
740 * @param object $extension Add-on object from addons().
741 * @param string|int $loop_key Key for loaders(); use slug when known.
742 * @return void
743 */
744 public function render_api_addon_card( $extension, $loop_key = '' ) {
745 if ( ! is_object( $extension ) || ! isset( $extension->url ) || ! isset( $extension->title ) ) {
746 return;
747 }
748
749 $icon_url = '';
750 if ( isset( $extension->media ) && is_object( $extension->media ) &&
751 isset( $extension->media->icon ) && is_object( $extension->media->icon ) &&
752 isset( $extension->media->icon->url ) ) {
753 $icon_url = (string) $extension->media->icon->url;
754 }
755
756 $excerpt = isset( $extension->excerpt ) ? $extension->excerpt : '';
757 $slug = isset( $extension->slug ) ? $extension->slug : '';
758 $loader = ( '' !== $loop_key && is_scalar( $loop_key ) ) ? (string) $loop_key : $slug;
759 $class = $slug ? $slug : $loader;
760 $class = sanitize_html_class( str_replace( '/', '-', $class ) );
761
762 $title_class = 'analytify-addon-card__title';
763 if ( ! empty( $icon_url ) ) {
764 $title_class .= ' analytify-addon-card__title--has-icon';
765 }
766
767 echo '<div class="wp-extension ' . esc_attr( $class ) . '">';
768 echo '<a target="_blank" href="' . esc_url( $extension->url ) . '">';
769 echo '<h3 class="' . esc_attr( $title_class ) . '"';
770 if ( ! empty( $icon_url ) ) {
771 echo ' style="' . esc_attr( '--analytify-addon-icon: url(' . esc_url( $icon_url ) . ')' ) . '"';
772 }
773 echo '>' . esc_html( $extension->title ) . '</h3></a>';
774 echo '<p>' . wp_kses_post( wpautop( wp_strip_all_tags( $excerpt ) ) ) . '</p><p>';
775 $this->addons_status( $slug, $extension );
776 echo '</p>';
777 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Safe internal HTML from loaders().
778 echo $this->loaders( $loader, $icon_url );
779 echo '</div>';
780 }
781
782 /**
783 * Print one internal module add-on card (Add-ons admin screen).
784 *
785 * @param array<string, mixed> $module Row from wp_analytify_modules.
786 * @return void
787 */
788 public function render_module_addon_card( $module ) {
789 if ( ! is_array( $module ) || empty( $module['slug'] ) ) {
790 return;
791 }
792
793 $slug = (string) $module['slug'];
794 $title = isset( $module['title'] ) ? (string) $module['title'] : '';
795 $url = isset( $module['url'] ) ? (string) $module['url'] : '';
796 $image = isset( $module['image'] ) ? (string) $module['image'] : '';
797 $desc = isset( $module['description'] ) ? (string) $module['description'] : '';
798
799 echo '<div class="wp-extension ' . esc_attr( $slug ) . '">';
800 echo '<a target="_blank" href="' . esc_url( $url ) . '">';
801 echo '<h3 class="analytify-addon-card__title analytify-addon-card__title--has-icon"';
802 echo ' style="' . esc_attr( '--analytify-addon-icon: url(' . esc_url( $image ) . ')' ) . '">';
803 echo esc_html( $title );
804 echo '</h3></a>';
805 echo '<p>' . esc_html( $desc ) . '</p><p>';
806 $this->check_module_status( $slug );
807 echo '</p>';
808 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Safe internal HTML from loaders().
809 echo $this->loaders( $title, $image );
810 echo '</div>';
811 }
812
813 /**
814 * Slugs present in an API add-ons list.
815 *
816 * @param array<int, object> $addons Add-ons from addons().
817 * @return array<string, true>
818 */
819 public function get_api_addon_slugs( $addons ) {
820 $slugs = array();
821 if ( ! is_array( $addons ) ) {
822 return $slugs;
823 }
824 foreach ( $addons as $item ) {
825 if ( is_object( $item ) && ! empty( $item->slug ) ) {
826 $slugs[ (string) $item->slug ] = true;
827 }
828 }
829 return $slugs;
830 }
831
832 /**
833 * Print one Pro-bundled add-on card (saved in wp_analytify_pro_addons).
834 *
835 * @param string $slug Folder slug, e.g. wp-analytify-learndash.
836 * @param array<string, mixed> $meta name, url, description, status.
837 * @return void
838 */
839 public function render_pro_addon_card( $slug, $meta ) {
840 if ( ! is_array( $meta ) || empty( $meta['name'] ) ) {
841 return;
842 }
843
844 $url = isset( $meta['url'] ) ? (string) $meta['url'] : '';
845 $desc = isset( $meta['description'] ) ? (string) $meta['description'] : '';
846 $name = (string) $meta['name'];
847 $icon = $this->get_addon_icon( $slug );
848
849 $use_pro_actions = class_exists( 'WP_Analytify_Pro' ) && defined( 'ANALYTIFY_PRO_VERSION' ) &&
850 version_compare( ANALYTIFY_PRO_VERSION, '6.0.0', '>=' );
851
852 echo '<div class="wp-extension ' . esc_attr( $name ) . '">';
853 echo '<a target="_blank" rel="noopener noreferrer" href="' . esc_url( $url ) . '">';
854 echo '<h3 class="analytify-addon-card__title analytify-addon-card__title--has-icon"';
855 echo ' style="' . esc_attr( '--analytify-addon-icon: url(' . esc_url( $icon ) . ')' ) . '">';
856 echo esc_html( $name );
857 echo '</h3></a>';
858 echo '<p>' . wp_kses_post( wpautop( wp_strip_all_tags( $desc ) ) ) . '</p><p>';
859 if ( $use_pro_actions ) {
860 $this->pro_addons_status( $slug, isset( $meta['status'] ) ? $meta['status'] : 'inactive' );
861 } else {
862 $buy_url = '' !== $url ? $url : 'https://analytify.io/pricing';
863 printf(
864 /* translators: %1$s: Opening anchor tag, %2$s: Closing anchor tag for the purchase link. */
865 esc_html__( '%1$s Get this add-on %2$s', 'wp-analytify' ),
866 '<a target="_blank" rel="noopener noreferrer" class="button-primary" href="' . esc_url( $buy_url ) . '">',
867 '</a>'
868 );
869 }
870 echo '</p>';
871 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Safe internal HTML from loaders().
872 echo $this->loaders( $name, $icon );
873 echo '</div>';
874 }
875
876 /**
877 * Loaders HTML.
878 *
879 * @version 7.0.5
880 * @param string $addon Addon name.
881 * @param string $logo Logo url.
882 * @return string $html HTML markup string.
883 */
884 public function loaders( $addon, $logo ) {
885
886 $html = '<div class="analytify-addons-loader-container"><div class="wp-analytify-addon-enable analytify-loader" style="display:none !important;">
887 <div class="analytify-logo-container">
888 <img src="' . $logo . '" alt="' . $addon . '">
889 <svg class="circular-loader" viewBox="25 25 50 50" >
890 <circle class="loader-path" cx="50" cy="50" r="18" fill="none" stroke="#d8d8d8" stroke-width="1" />
891 </svg>
892 </div>
893 <p>' . __( 'Activating...', 'wp-analytify' ) . '</p>
894 </div>';
895 $html .= '<div class="wp-analytify-addon-install analytify-loader activated" style="display:none !important;">
896 <svg class="circular-loader2" viewBox="25 25 50 50" >
897 <circle class="loader-path2" cx="50" cy="50" r="18" fill="none" stroke="#00c853" stroke-width="1" />
898 </svg>
899 <div class="checkmark draw"></div>
900 <p>' . __( 'Activated', 'wp-analytify' ) . '</p>
901 </div>';
902 $html .= '<div class="wp-analytify-addon-uninstalling analytify-loader activated" style="display:none !important;">
903 <div class="analytify-logo-container">
904 <img src="' . $logo . '" alt="' . esc_attr( $addon ) . '">
905 <svg class="circular-loader" viewBox="25 25 50 50">
906 <circle class="loader-path" cx="50" cy="50" r="18" fill="none" stroke="#d8d8d8" stroke-width="1" />
907 </svg>
908 </div>
909 <p>' . __( 'Deactivating...', 'wp-analytify' ) . '</p>
910 </div>';
911 $html .= '<div class="wp-analytify-addon-uninstall analytify-loader activated" style="display:none !important;">
912 <svg class="circular-loader2" viewBox="25 25 50 50" >
913 <circle class="loader-path2" cx="50" cy="50" r="18" fill="none" stroke="#ff0000" stroke-width="1" />
914 </svg>
915 <div class="checkmark draw"></div>
916 <p>' . __( 'Deactivated', 'wp-analytify' ) . '</p>
917 </div>';
918 $html .= '<div class="wp-analytify-addon-wrong activated analytify-loader" style="display:none !important;">
919 <svg class="checkmark_login" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52">
920 <circle class="checkmark__circle" cx="26" cy="26" r="25" fill="none"></circle>
921 <path class="checkmark__check" stroke="#ff0000" fill="none" d="M16 16 36 36 M36 16 16 36"></path>
922 </svg>
923 <p>' . __( 'Something Went Wrong', 'wp-analytify' ) . '</p>
924 </div></div>';
925
926 return $html;
927 }
928 }
929 }
930
931 $obj_wp_analytify_addons = new WP_Analytify_Addons();
932
933 // Clear transient if requested for debugging (add ?clear_addons_cache=1 to URL).
934 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Cache clearing is a safe admin action, nonce verified via capability check.
935 if ( isset( $_GET['clear_addons_cache'] ) && '1' === $_GET['clear_addons_cache'] && current_user_can( 'manage_options' ) ) {
936 delete_transient( 'analytify_api_addons' );
937 }
938
939 $addons = $obj_wp_analytify_addons->addons();
940 $pro_addons = get_option( 'wp_analytify_pro_addons' );
941 $modules = $obj_wp_analytify_addons->modules();
942
943 $pro_addons_slug_order = apply_filters(
944 'analytify_pro_addons_display_slug_order',
945 array(
946 'wp-analytify-forms',
947 'wp-analytify-authors',
948 'wp-analytify-goals',
949 'wp-analytify-email',
950 'wp-analytify-campaigns',
951 'wp-analytify-edd',
952 'wp-analytify-woocommerce',
953 )
954 );
955 if ( is_array( $pro_addons ) ) {
956 $pro_addons = $obj_wp_analytify_addons->reorder_assoc_by_slug_list( $pro_addons, $pro_addons_slug_order );
957 }
958
959 $api_addons_slug_order = apply_filters(
960 'analytify_api_addons_display_slug_order',
961 array( 'analytify-analytics-dashboard-widget' )
962 );
963 if ( is_array( $addons ) ) {
964 $addons = $obj_wp_analytify_addons->reorder_extension_list_by_slugs( $addons, $api_addons_slug_order );
965 }
966
967 $modules_slug_order = apply_filters(
968 'analytify_modules_display_slug_order',
969 array(
970 'pixels-tracking',
971 'wp-analytify-pmpro',
972 'wp-analytify-learndash',
973 'wp-analytify-lifterlms',
974 'custom-dimensions',
975 'amp',
976 'events-tracking',
977 'google-ads-tracking',
978 )
979 );
980 if ( is_array( $modules ) ) {
981 $modules = $obj_wp_analytify_addons->reorder_assoc_by_slug_list( $modules, $modules_slug_order );
982 }
983
984 // When Pro add-ons load first, pull GA4 widget out of the API list so it can render 3rd (after Authors).
985 $ga4_dashboard_addon = null;
986 $ga4_slug = 'analytify-analytics-dashboard-widget';
987 $use_pro_addons_grid = class_exists( 'WP_Analytify_Pro' ) && defined( 'ANALYTIFY_PRO_VERSION' ) &&
988 version_compare( ANALYTIFY_PRO_VERSION, '6.0.0', '>=' ) && ! empty( $pro_addons );
989
990 if ( $use_pro_addons_grid && is_array( $addons ) ) {
991 foreach ( $addons as $idx => $extension ) {
992 if ( is_object( $extension ) && isset( $extension->slug ) && $ga4_slug === $extension->slug ) {
993 $ga4_dashboard_addon = $extension;
994 unset( $addons[ $idx ] );
995 break;
996 }
997 }
998 $addons = array_values( $addons );
999 }
1000
1001 // When Pro add-ons load first, pull Pixels out of the module list so it can sit after WooCommerce.
1002 $pixels_module_after_woo = null;
1003 $pixels_slug = 'pixels-tracking';
1004 if ( $use_pro_addons_grid && is_array( $modules ) && is_array( $pro_addons ) &&
1005 isset( $pro_addons['wp-analytify-woocommerce'] ) ) {
1006 if ( isset( $modules[ $pixels_slug ] ) && is_array( $modules[ $pixels_slug ] ) ) {
1007 $pixels_module_after_woo = $modules[ $pixels_slug ];
1008 unset( $modules[ $pixels_slug ] );
1009 } else {
1010 foreach ( $modules as $mkey => $mrow ) {
1011 if ( is_array( $mrow ) && isset( $mrow['slug'] ) && $pixels_slug === $mrow['slug'] ) {
1012 $pixels_module_after_woo = $mrow;
1013 unset( $modules[ $mkey ] );
1014 break;
1015 }
1016 }
1017 }
1018 }
1019
1020 // When Pixels renders after WooCommerce, keep PMPro / LearnDash / LifterLMS module cards next to it.
1021 $lms_modules_after_pixels = array();
1022 $lms_slugs_after_pixels = array(
1023 'wp-analytify-pmpro',
1024 'wp-analytify-learndash',
1025 'wp-analytify-lifterlms',
1026 );
1027 if ( $use_pro_addons_grid && is_array( $modules ) && $pixels_module_after_woo ) {
1028 foreach ( $lms_slugs_after_pixels as $lms_slug ) {
1029 $found_lms = null;
1030 $found_key = null;
1031 if ( isset( $modules[ $lms_slug ] ) && is_array( $modules[ $lms_slug ] ) ) {
1032 $found_lms = $modules[ $lms_slug ];
1033 $found_key = $lms_slug;
1034 } else {
1035 foreach ( $modules as $mkey => $mrow ) {
1036 if ( is_array( $mrow ) && isset( $mrow['slug'] ) && $lms_slug === $mrow['slug'] ) {
1037 $found_lms = $mrow;
1038 $found_key = $mkey;
1039 break;
1040 }
1041 }
1042 }
1043 if ( null !== $found_lms && null !== $found_key ) {
1044 $lms_modules_after_pixels[] = $found_lms;
1045 unset( $modules[ $found_key ] );
1046 }
1047 }
1048 }
1049
1050 $analytify_screen = get_current_screen() ? get_current_screen()->base : '';
1051 $version = defined( 'ANALYTIFY_PRO_VERSION' ) ? ANALYTIFY_PRO_VERSION : ( defined( 'ANALYTIFY_VERSION' ) ? ANALYTIFY_VERSION : '1.0.0' ); ?>
1052
1053 <div class="wpanalytify analytify-dashboard-nav">
1054 <div class="wpb_plugin_wraper">
1055 <div class="wpb_plugin_header_wraper">
1056 <div class="graph"></div>
1057 <div class="wpb_plugin_header">
1058 <div class="wpb_plugin_header_title"></div>
1059 <div class="wpb_plugin_header_info">
1060 <a href="https://analytify.io/changelog/" target="_blank" class="btn">View Changelog</a>
1061 </div>
1062 <div class="wpb_plugin_header_logo">
1063 <img src="<?php echo esc_url( ( defined( 'ANALYTIFY_PLUGIN_URL' ) ? ANALYTIFY_PLUGIN_URL : '' ) . 'assets/img/logo.svg' ); ?>" alt="Analytify">
1064 </div>
1065 </div>
1066 </div>
1067
1068 <div class="analytify-settings-body-container">
1069 <div class="wpb_plugin_body_wraper">
1070 <div class="wpb_plugin_body">
1071 <div class="wpa-tab-wrapper">
1072 <ul class="analytify_nav_tab_wrapper nav-tab-wrapper">
1073 <li><a href="<?php echo esc_url( admin_url( 'admin.php?page=analytify-addons' ) ); ?>"
1074 class="analytify_nav_tab <?php echo ( 'analytify_page_analytify-addons' === $analytify_screen ) ? 'nav-tab-active' : ''; ?>"><?php esc_html_e( 'Addons', 'wp-analytify' ); ?></a>
1075 </li>
1076 <li><a href="<?php echo esc_url( admin_url( 'admin.php?page=analytify-settings#wp-analytify-license' ) ); ?>"
1077 class="analytify_nav_tab">License</a></li>
1078 </ul>
1079 </div>
1080
1081 <div class="wpb_plugin_tabs_content analytify-dashboard-content">
1082 <div class="wrap analytify-addons-wrapper">
1083
1084 <h2 class='opt-title'><span id='icon-options-general' class='analytics-options'><img
1085 src="<?php echo esc_url( plugins_url( '../assets/img/wp-analytics-logo.png', __FILE__ ) ); ?>"
1086 alt="analytics"></span>
1087 <?php esc_html_e( 'Powerful Add-ons to Get More from Analytify.', 'wp-analytify' ); ?>
1088 </h2>
1089
1090 <div class="tabwrapper analytify-addons-grid">
1091
1092 <?php
1093
1094 if ( class_exists( 'WP_Analytify_Pro' ) && defined( 'ANALYTIFY_PRO_VERSION' ) && version_compare( ANALYTIFY_PRO_VERSION, '6.0.0', '>=' ) && ! empty( $pro_addons ) ) {
1095 foreach ( $pro_addons as $slug => $meta ) :
1096 $obj_wp_analytify_addons->render_pro_addon_card( $slug, $meta );
1097 if ( 'wp-analytify-authors' === $slug && $ga4_dashboard_addon ) {
1098 $obj_wp_analytify_addons->render_api_addon_card(
1099 $ga4_dashboard_addon,
1100 $ga4_slug
1101 );
1102 }
1103 if ( 'wp-analytify-woocommerce' === $slug && $pixels_module_after_woo ) {
1104 $obj_wp_analytify_addons->render_module_addon_card( $pixels_module_after_woo );
1105 foreach ( $lms_modules_after_pixels as $lms_module_row ) {
1106 $obj_wp_analytify_addons->render_module_addon_card( $lms_module_row );
1107 }
1108 }
1109 endforeach;
1110 foreach ( $addons as $name => $extension ) :
1111 $obj_wp_analytify_addons->render_api_addon_card( $extension, $name );
1112 endforeach;
1113
1114 } else {
1115 foreach ( $addons as $name => $extension ) :
1116 $obj_wp_analytify_addons->render_api_addon_card( $extension, $name );
1117 endforeach;
1118
1119 // Pro-only rows (LearnDash, LifterLMS, PMPro, …) live in wp_analytify_pro_addons; the API
1120 // list omits them when Pro is off — still show cards using saved option + purchase links.
1121 $api_addon_slugs = $obj_wp_analytify_addons->get_api_addon_slugs( $addons );
1122 if ( ! empty( $pro_addons ) && is_array( $pro_addons ) ) {
1123 $pro_for_lite = $obj_wp_analytify_addons->reorder_assoc_by_slug_list(
1124 $pro_addons,
1125 $pro_addons_slug_order
1126 );
1127 $pro_rows_now_modules = array(
1128 'wp-analytify-pmpro',
1129 'wp-analytify-learndash',
1130 'wp-analytify-lifterlms',
1131 );
1132 foreach ( $pro_for_lite as $slug => $meta ) {
1133 if ( ! is_array( $meta ) || isset( $api_addon_slugs[ $slug ] ) ) {
1134 continue;
1135 }
1136 if ( in_array( $slug, $pro_rows_now_modules, true ) ) {
1137 continue;
1138 }
1139 $obj_wp_analytify_addons->render_pro_addon_card( $slug, $meta );
1140 }
1141 }
1142
1143 // Show message if no valid addons found.
1144 $valid_addons_count = 0;
1145 foreach ( $addons as $extension ) {
1146 if ( is_object( $extension ) && isset( $extension->url ) && isset( $extension->title ) ) {
1147 ++$valid_addons_count;
1148 }
1149 }
1150 if ( ! empty( $pro_addons ) && is_array( $pro_addons ) ) {
1151 foreach ( $pro_addons as $slug => $meta ) {
1152 if ( is_array( $meta ) && ! isset( $api_addon_slugs[ $slug ] ) ) {
1153 ++$valid_addons_count;
1154 }
1155 }
1156 }
1157
1158 if ( 0 === $valid_addons_count && empty( $modules ) ) {
1159 ?>
1160 <div class="notice notice-error">
1161 <p><?php esc_html_e( 'Unable to load addons. Please check your internet connection and try refreshing the page.', 'wp-analytify' ); ?></p>
1162 </div>
1163 <?php
1164 }
1165 }
1166 ?>
1167
1168 <?php
1169 foreach ( $modules as $module ) :
1170 $obj_wp_analytify_addons->render_module_addon_card( $module );
1171 endforeach;
1172 ?>
1173
1174 </div>
1175 </div>
1176 </div>
1177 </div>
1178 </div>
1179 </div>
1180 </div>
1181 </div>
1182