PluginProbe
MainWP Dashboard: Self-hosted WordPress Management for Agencies / 6.1
MainWP Dashboard: Self-hosted WordPress Management for Agencies v6.1
6.2 6.1.8 6.1.7 6.1.6 6.1.5 6.1.4 6.1.3 6.1.2 6.1.1 6.1 6.0.12 6.0.11 4.6.0.1 5.0 5.0.1 5.0.2 5.0.3 5.0.3.1 5.0.3.2 5.1 5.1.1 5.2 5.2.1 5.2.2 5.3 All 153 releases
mainwp / pages / page-mainwp-extensions.php

page-mainwp-extensions.php in MainWP Dashboard: Self-hosted WordPress Management for Agencies 6.1, at pages/page-mainwp-extensions.php

920 lines 48.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * MainWP Extension Page
4 *
5 * @package MainWP/Dashboard
6 */
7
8 namespace MainWP\Dashboard;
9
10 // Exit if accessed directly.
11 if ( ! defined( 'ABSPATH' ) ) {
12 exit;
13 }
14
15 /**
16 * Class MainWP_Extensions
17 */
18 class MainWP_Extensions { // phpcs:ignore Generic.Classes.OpeningBraceSameLine.ContentAfterBrace -- NOSONAR.
19
20 /**
21 * Method get_class_name()
22 *
23 * Get Class Name.
24 *
25 * @return object Class name.
26 */
27 public static function get_class_name() {
28 return __CLASS__;
29 }
30
31 /**
32 * Instantiate action hooks.
33 *
34 * @uses \MainWP\Dashboard\MainWP_Extensions_Handler::get_class_name()
35 */
36 public static function init() {
37 /**
38 * This hook allows you to render the Extensions page header via the 'mainwp-pageheader-extensions' action.
39 *
40 * @link http://codex.mainwp.com/#mainwp-pageheader-extensions
41 *
42 * @see \MainWP_Extensions::render_header
43 */
44 add_action( 'mainwp-pageheader-extensions', array( static::get_class_name(), 'render_header' ) ); // @deprecated Use 'mainwp_pageheader_extensions' instead.
45 add_action( 'mainwp_pageheader_extensions', array( static::get_class_name(), 'render_header' ) );
46
47 /**
48 * This hook allows you to render the Extensions page footer via the 'mainwp-pagefooter-extensions' action.
49 *
50 * @link http://codex.mainwp.com/#mainwp-pagefooter-extensions
51 *
52 * @see \MainWP_Extensions::render_footer
53 */
54 add_action( 'mainwp-pagefooter-extensions', array( static::get_class_name(), 'render_footer' ) ); // @deprecated Use 'mainwp_pagefooter_extensions' instead.
55 add_action( 'mainwp_pagefooter_extensions', array( static::get_class_name(), 'render_footer' ) );
56
57 add_action( 'mainwp_help_sidebar_content', array( static::get_class_name(), 'mainwp_help_content' ) );
58
59 add_filter( 'mainwp-extensions-apigeneratepassword', array( MainWP_Extensions_Handler::get_class_name(), 'gen_api_password' ), 10, 3 );
60 add_filter( 'mainwp_extensions_apigeneratepassword', array( MainWP_Extensions_Handler::get_class_name(), 'gen_api_password' ), 10, 3 );
61 }
62
63 /**
64 * Method init_menu()
65 *
66 * Instantiate Extensions Menu.
67 *
68 * @uses \MainWP\Dashboard\MainWP_Api_Manager::get_activation_info()
69 * @uses \MainWP\Dashboard\MainWP_Extensions_View::init_menu()
70 * @uses \MainWP\Dashboard\MainWP_Menu::is_disable_menu_item()
71 * @uses \MainWP\Dashboard\MainWP_Extensions_Handler::polish_ext_name()
72 * @uses \MainWP\Dashboard\MainWP_Extensions_Handler::added_on_menu()
73 * @uses \MainWP\Dashboard\MainWP_Extensions_Handler::get_extensions()
74 * @uses \MainWP\Dashboard\MainWP_Utility::update_option()
75 */
76 public static function init_menu() { // phpcs:ignore -- NOSONAR - complex function. Current complexity is the only way to achieve desired results, pull request solutions appreciated.
77 if ( ! MainWP_Menu::is_disable_menu_item( 2, 'Extensions' ) ) {
78 MainWP_Extensions_View::init_menu();
79 }
80
81 $save_extensions = array();
82
83 /**
84 * Get extensions
85 *
86 * Adds extension to MainWP system.
87 *
88 * @since 4.1
89 */
90 $init_extensions = array();
91 $init_extensions = apply_filters_deprecated( 'mainwp-getextensions', array( $init_extensions ), '4.0.7.2', 'mainwp_getextensions' ); // NOSONAR - not IP.
92 $init_extensions = apply_filters( 'mainwp_getextensions', $init_extensions );
93
94 $all_available_extensions = MainWP_Extensions_View::get_available_extensions( 'all' );
95
96 $activations_cached = get_option( 'mainwp_extensions_all_activation_cached', array() );
97
98 if ( ! is_array( $activations_cached ) ) {
99 $activations_cached = array();
100 }
101
102 $is_cached = ! empty( $activations_cached ) ? true : false;
103
104 $extraHeaders = array(
105 'IconURI' => 'Icon URI',
106 'SupportForumURI' => 'Support Forum URI',
107 'DocumentationURI' => 'Documentation URI',
108 );
109
110 $extsPages = array();
111
112 $compatible_v4_checks = array(
113 'advanced-uptime-monitor-extension/advanced-uptime-monitor-extension.php',
114 'mainwp-article-uploader-extension/mainwp-article-uploader-extension.php',
115 'mainwp-backwpup-extension/mainwp-backwpup-extension.php',
116 'boilerplate-extension/boilerplate-extension.php',
117 'mainwp-branding-extension/mainwp-branding-extension.php',
118 'mainwp-bulk-settings-manager/mainwp-bulk-settings-manager.php',
119 'mainwp-clean-and-lock-extension/mainwp-clean-and-lock-extension.php',
120 'mainwp-client-reports-extension/mainwp-client-reports-extension.php',
121 'mainwp-clone-extension/mainwp-clone-extension.php',
122 'mainwp-code-snippets-extension/mainwp-code-snippets-extension.php',
123 'mainwp-comments-extension/mainwp-comments-extension.php',
124 'mainwp-favorites-extension/mainwp-favorites-extension.php',
125 'mainwp-file-uploader-extension/mainwp-file-uploader-extension.php',
126 'mainwp-google-analytics-extension/mainwp-google-analytics-extension.php',
127 'mainwp-maintenance-extension/mainwp-maintenance-extension.php',
128 'mainwp-piwik-extension/mainwp-piwik-extension.php',
129 'mainwp-post-dripper-extension/mainwp-post-dripper-extension.php',
130 'mainwp-rocket-extension/mainwp-rocket-extension.php',
131 'mainwp-sucuri-extension/mainwp-sucuri-extension.php',
132 'mainwp-team-control/mainwp-team-control.php',
133 'mainwp-updraftplus-extension/mainwp-updraftplus-extension.php',
134 'mainwp-url-extractor-extension/mainwp-url-extractor-extension.php',
135 'mainwp-woocommerce-shortcuts-extension/mainwp-woocommerce-shortcuts-extension.php',
136 'mainwp-woocommerce-status-extension/mainwp-woocommerce-status-extension.php',
137 'mainwp-wordfence-extension/mainwp-wordfence-extension.php',
138 'wordpress-seo-extension/wordpress-seo-extension.php',
139 'mainwp-page-speed-extension/mainwp-page-speed-extension.php',
140 'mainwp-ithemes-security-extension/mainwp-ithemes-security-extension.php',
141 'mainwp-post-plus-extension/mainwp-post-plus-extension.php',
142 'mainwp-staging-extension/mainwp-staging-extension.php',
143 'mainwp-custom-post-types/mainwp-custom-post-types.php',
144 'mainwp-buddy-extension/mainwp-buddy-extension.php',
145 'mainwp-vulnerability-checker-extension/mainwp-vulnerability-checker-extension.php',
146 'mainwp-timecapsule-extension/mainwp-timecapsule-extension.php',
147 'activity-log-mainwp/activity-log-mainwp.php',
148 );
149 include_once ABSPATH . '/wp-admin/includes/plugin.php'; // NOSONAR - WP compatible.
150
151 $deactivated_imcompatible = array();
152 foreach ( $init_extensions as $extension ) {
153 $slug = plugin_basename( $extension['plugin'] );
154 $plugin_data = get_plugin_data( $extension['plugin'], true, false );
155 $file_data = get_file_data( $extension['plugin'], $extraHeaders );
156
157 if ( ! isset( $plugin_data['Name'] ) || ( '' === $plugin_data['Name'] ) ) {
158 continue;
159 }
160
161 if ( in_array( $slug, $compatible_v4_checks ) ) {
162 $check_minver = '3.99999';
163 if ( 'advanced-uptime-monitor-extension/advanced-uptime-monitor-extension.php' === $slug ) {
164 $check_minver = '4.6.2';
165 } elseif ( 'activity-log-mainwp/activity-log-mainwp.php' === $slug ) {
166 $check_minver = '1.0.5';
167 }
168
169 if ( isset( $plugin_data['Version'] ) && version_compare( $plugin_data['Version'], $check_minver, '<' ) ) {
170 $deactivated_imcompatible[] = $plugin_data['Name'];
171 deactivate_plugins( $slug, true );
172 continue;
173 }
174 }
175
176 $extension['slug'] = $slug;
177
178 if ( ! isset( $extension['name'] ) ) {
179 $extension['name'] = $plugin_data['Name'];
180 }
181 $extension['version'] = $plugin_data['Version'];
182 $extension['description'] = $plugin_data['Description'];
183 $extension['author'] = $plugin_data['Author'];
184 $extension['icon'] = isset( $extension['icon'] ) ? trim( $extension['icon'] ) : '';
185 $extension['iconURI'] = $file_data['IconURI'];
186 $extension['SupportForumURI'] = $file_data['SupportForumURI'];
187 $extension['DocumentationURI'] = $file_data['DocumentationURI'];
188 $extension['page'] = 'Extensions-' . str_replace( ' ', '-', ucwords( str_replace( '-', ' ', dirname( $slug ) ) ) );
189
190 if ( isset( $extension['apiManager'] ) && $extension['apiManager'] ) {
191
192 $api_slug = dirname( $slug );
193
194 if ( $is_cached ) {
195 $options = isset( $activations_cached[ $api_slug ] ) ? $activations_cached[ $api_slug ] : array();
196 // MWP-1546 (Codex follow-up): pre-MWP-1546 builds persisted
197 // the full decrypted activation array (including plaintext
198 // 'api_key') into mainwp_extensions_all_activation_cached.
199 // On upgrade those rows survive as long as the cache stays
200 // populated. Treat any cache-hit entry that still carries
201 // 'api_key' as untrusted legacy data: strip it, derive
202 // has_api_key, and rewrite the sanitized entry in the
203 // cache so the plaintext never sees another disk write.
204 if ( is_array( $options ) && array_key_exists( 'api_key', $options ) ) {
205 $had_key = ! empty( $options['api_key'] );
206 unset( $options['api_key'] );
207 $options['has_api_key'] = $had_key;
208 $activations_cached[ $api_slug ] = $options;
209 // Force the post-loop update_option write below to fire.
210 $is_cached = false;
211 }
212 } else {
213 $options = MainWP_Api_Manager::instance()->get_activation_info( $api_slug );
214 if ( ! is_array( $options ) ) {
215 $options = array();
216 }
217 // MWP-1546: the activations_cached option is persisted via
218 // update_option(). Storing the decrypted api_key here would
219 // re-leak the plaintext into another database row,
220 // defeating the at-rest encryption added to set_activation_info().
221 // Cache only the non-credential fields plus a has_api_key
222 // boolean so the cache-hit path knows whether a key is
223 // configured without ever holding the plaintext.
224 $cached_options = $options;
225 unset( $cached_options['api_key'] );
226 $cached_options['has_api_key'] = ! empty( $options['api_key'] );
227 $activations_cached[ $api_slug ] = $cached_options;
228 }
229
230 if ( ! is_array( $options ) ) {
231 $options = array();
232 }
233
234 // MWP-1546: stop persisting the per-extension license key
235 // into the aggregated mainwp_extensions option. The original
236 // 'api_key' field on $extension was a plaintext string sourced
237 // from get_activation_info(); render code that needs to know
238 // "is a key configured?" should consult 'has_api_key' instead.
239 // The deactivate flow uses a server-side sentinel-resolution
240 // path (see MainWP_Post_Extension_Handler::deactivate_extension)
241 // that fetches the real key from the per-slug option at the
242 // moment of use, never via the aggregate.
243 //
244 // The cache-hit path stores has_api_key directly (see above);
245 // the fresh-load path infers it from the decrypted plaintext.
246 $extension['has_api_key'] = isset( $options['has_api_key'] ) ? (bool) $options['has_api_key'] : ! empty( $options['api_key'] );
247 $extension['activated_key'] = isset( $options['activated_key'] ) ? $options['activated_key'] : 'Deactivated';
248 $extension['deactivate_checkbox'] = isset( $options['deactivate_checkbox'] ) ? $options['deactivate_checkbox'] : 'off';
249 $extension['product_id'] = isset( $options['product_id'] ) ? $options['product_id'] : '';
250 $extension['instance_id'] = isset( $options['instance_id'] ) ? $options['instance_id'] : '';
251 $extension['software_version'] = isset( $options['software_version'] ) ? $options['software_version'] : '';
252 $extension['mainwp_version'] = isset( $options['mainwp_version'] ) ? $options['mainwp_version'] : '';
253
254 if ( isset( $options['product_item_id'] ) ) {
255 $extension['product_item_id'] = $options['product_item_id'];
256 }
257 }
258
259 $ext_slug = dirname( $slug );
260 if ( isset( $all_available_extensions[ $ext_slug ] ) ) {
261 $extension['type'] = $all_available_extensions[ $ext_slug ]['type']; // to fix.
262 }
263
264 $save_extensions[] = $extension;
265 if ( \mainwp_current_user_can( 'extension', dirname( $slug ) ) ) {
266 $callback = isset( $extension['callback'] ) ? $extension['callback'] : '';
267 $menu_name = MainWP_Extensions_Handler::polish_ext_name( $extension );
268 if ( MainWP_Extensions_Handler::added_on_menu( $slug ) ) {
269 $_page = add_submenu_page( 'mainwp_tab', $extension['name'], $menu_name, 'read', $extension['page'], $callback );
270 } else {
271 $_page = add_submenu_page( 'mainwp_tab', $extension['name'], '<div class="mainwp-hidden">' . $extension['name'] . '</div>', 'read', $extension['page'], $callback );
272 }
273
274 if ( isset( $extension['on_load_callback'] ) && ! empty( $extension['on_load_callback'] ) ) {
275 add_action( 'load-' . $_page, $extension['on_load_callback'] );
276 }
277
278 $_item = array(
279 'title' => $menu_name,
280 'slug' => $extension['page'],
281 'href' => isset( $extension['href'] ) ? $extension['href'] : '',
282 );
283
284 $extsPages[] = $_item;
285 }
286 }
287
288 if ( ! empty( $deactivated_imcompatible ) ) {
289 set_transient( 'mainwp_transient_deactivated_incomtible_exts', $deactivated_imcompatible );
290 }
291
292 MainWP_Utility::update_option( 'mainwp_extensions', $save_extensions );
293 MainWP_Extensions_Handler::get_extensions( true ); // forced reload.
294
295 if ( ! $is_cached ) {
296 update_option( 'mainwp_extensions_all_activation_cached', $activations_cached );
297 }
298 static::init_left_menu( $extsPages );
299 }
300
301 /**
302 * Method init_left_menu()
303 *
304 * Initiate top level Extensions Menues.
305 *
306 * @param array $extPages List of extension pages.
307 * @uses \MainWP\Dashboard\MainWP_Menu::is_disable_menu_item()
308 * @uses \MainWP\Dashboard\MainWP_Menu::add_left_menu()
309 * @uses \MainWP\Dashboard\MainWP_Menu::init_subpages_left_menu()
310 */
311 public static function init_left_menu( $extPages ) {
312 $subPages = array();
313 if ( ! MainWP_Menu::is_disable_menu_item( 2, 'Extensions' ) ) {
314 MainWP_Menu::add_left_menu(
315 array(
316 'title' => esc_html__( 'Add-ons', 'mainwp' ),
317 'parent_key' => 'mainwp_tab',
318 'slug' => 'Extensions',
319 'href' => 'admin.php?page=Extensions',
320 'icon' => '<i class="box icon"></i>',
321 ),
322 0
323 );
324
325 $init_sub_subleftmenu = array(
326 array(
327 'title' => esc_html__( 'Manage Add-ons', 'mainwp' ),
328 'parent_key' => 'Extensions',
329 'href' => 'admin.php?page=Extensions',
330 'slug' => 'Extensions',
331 'right' => '',
332 ),
333 );
334
335 MainWP_Menu::init_subpages_left_menu( $subPages, $init_sub_subleftmenu, 'Extensions', 'Extensions' );
336
337 foreach ( $init_sub_subleftmenu as $item ) {
338 if ( MainWP_Menu::is_disable_menu_item( 3, $item['slug'] ) ) {
339 continue;
340 }
341 MainWP_Menu::add_left_menu( $item, 2 );
342 }
343
344 if ( ! empty( $extPages ) ) {
345
346 $init_sub_subleftmenu = array();
347 $slug = '';
348 MainWP_Menu::init_subpages_left_menu( $extPages, $init_sub_subleftmenu, 'Extensions', $slug );
349
350 foreach ( $init_sub_subleftmenu as $item ) {
351 if ( MainWP_Menu::is_disable_menu_item( 3, $item['slug'] ) ) {
352 continue;
353 }
354 MainWP_Menu::add_left_menu( $item, 2 );
355 }
356 }
357 }
358 }
359
360 /**
361 * Method init_subpages_menu()
362 *
363 * Initiate Extensions Subpage Menu.
364 *
365 * @uses \MainWP\Dashboard\MainWP_Extensions_Handler::get_extensions()
366 * @uses \MainWP\Dashboard\MainWP_Extensions_Handler::added_on_menu()
367 * @uses \MainWP\Dashboard\MainWP_Extensions_Handler::polish_ext_name()
368 */
369 public static function init_subpages_menu() {
370 $exts = MainWP_Extensions_Handler::get_extensions();
371 if ( empty( $exts ) ) {
372 return;
373 }
374 $html = '';
375 foreach ( $exts as $extension ) {
376 if ( defined( 'MWP_TEAMCONTROL_PLUGIN_SLUG' ) && ( MWP_TEAMCONTROL_PLUGIN_SLUG === $extension['slug'] ) && ! \mainwp_current_user_can( 'extension', dirname( MWP_TEAMCONTROL_PLUGIN_SLUG ) ) ) {
377 continue;
378 }
379 if ( MainWP_Extensions_Handler::added_on_menu( $extension['slug'] ) ) {
380 continue;
381 }
382 $menu_name = MainWP_Extensions_Handler::polish_ext_name( $extension );
383
384 if ( isset( $extension['direct_page'] ) ) {
385 $html .= '<a href="' . esc_url( admin_url( 'admin.php?page=' . $extension['direct_page'] ) ) . '" class="mainwp-submenu">' . $menu_name . '</a>';
386 } else {
387 $html .= '<a href="' . esc_url( admin_url( 'admin.php?page=' . $extension['page'] ) ) . '" class="mainwp-submenu">' . $menu_name . '</a>';
388 }
389 }
390 if ( empty( $html ) ) {
391 return;
392 }
393 ?>
394 <div id="menu-mainwp-Extensions" class="mainwp-submenu-wrapper" xmlns="http://www.w3.org/1999/html">
395 <div class="wp-submenu sub-open" style="">
396 <div class="mainwp_boxout mainwp-submenu-wide">
397 <div class="mainwp_boxoutin"></div>
398 <?php echo $html; // phpcs:ignore WordPress.Security.EscapeOutput ?>
399 </div>
400 </div>
401 </div>
402 <?php
403 }
404
405
406 /**
407 * Method ajax_get_purchased_extensions()
408 *
409 * Get purchased MainWP Extensions.
410 *
411 * @uses \MainWP\Dashboard\MainWP_Api_Manager::get_purchased_extension()
412 * @uses \MainWP\Dashboard\MainWP_Api_Manager::check_response_for_intall_errors()
413 * @uses \MainWP\Dashboard\MainWP_Extensions_View::get_available_extensions()
414 * @uses \MainWP\Dashboard\MainWP_Extensions_View::get_extension_groups()
415 * @uses \MainWP\Dashboard\MainWP_Post_Handler::secure_request()
416 * @uses \MainWP\Dashboard\MainWP_Extensions_Handler::get_extensions()
417 * @uses \MainWP\Dashboard\MainWP_Utility::update_option()
418 */
419 public static function ajax_get_purchased_extensions() { // phpcs:ignore -- NOSONAR - complex function. Current complexity is the only way to achieve desired results, pull request solutions appreciated.
420
421 MainWP_Post_Handler::instance()->secure_request( 'mainwp_extension_getpurchased' );
422
423 $api_key = isset( $_POST['api_key'] ) ? trim( wp_unslash( $_POST['api_key'] ) ) : false; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
424
425 // MWP-1547: the Extensions page renders the sentinel placeholder
426 // when a master key is already stored. The browser submits whatever
427 // value is in #mainwp_com_api_key, which is the sentinel for
428 // remembered-license dashboards. Resolve to the stored decrypted
429 // master key server-side rather than sending the placeholder
430 // upstream.
431 if ( MainWP_Credential_Render::is_sentinel( $api_key ) ) {
432 $api_key = MainWP_Api_Manager_Key::instance()->get_decrypt_master_api_key();
433 }
434
435 $all_available_extensions_compatible_api_response = array();
436 $all_free_pro_exts = array();
437 $all_org_exts = array();
438 $map_extensions_group = array();
439 $purchased_data = array();
440
441 $all_groups = MainWP_Extensions_View::get_extension_groups();
442
443 $grouped_exts = array(
444 'others' => '',
445 'all' => '',
446 );
447
448 foreach ( MainWP_Extensions_View::get_available_extensions( 'all' ) as $ext ) {
449 $all_available_extensions_compatible_api_response[ $ext['product_id'] ] = $ext;
450 $map_extensions_group[ $ext['product_id'] ] = current( $ext['group'] );
451 if ( 'free' === $ext['type'] || 'pro' === $ext['type'] ) {
452 $all_free_pro_exts[ $ext['product_id'] ] = $ext['product_id'];
453 } elseif ( 'org' === $ext['type'] ) {
454 $all_org_exts[ $ext['product_id'] ] = $ext['product_id'];
455 }
456 }
457
458 $extensions = MainWP_Extensions_Handler::get_extensions();
459 $installed_softwares = array();
460
461 foreach ( $extensions as $extension ) {
462 if ( isset( $extension['type'] ) ) {
463 if ( 'free' === $extension['type'] || 'pro' === $extension['type'] ) {
464 if ( isset( $extension['product_id'] ) && ! empty( $extension['product_id'] ) ) {
465 $installed_softwares[ $extension['product_id'] ] = $extension['product_id'];
466 }
467 } elseif ( 'org' === $extension['type'] ) {
468 $ext_slug = dirname( $extension['slug'] );
469 $installed_softwares[ $ext_slug ] = $ext_slug;
470 }
471 }
472 }
473
474 $all_disabled_extensions = MainWP_Extensions_Handler::get_extensions_disabled( true );
475
476 foreach ( $all_disabled_extensions as $ext ) {
477 $installed_softwares[ $ext['product_id'] ] = $ext['product_id'];
478 }
479
480 $not_purchased_exts = $all_free_pro_exts;
481 $not_installed_org_exts = array_diff_key( $all_org_exts, $installed_softwares );
482 $installing_exts = $not_installed_org_exts;
483
484 if ( ! empty( $api_key ) ) {
485
486 $data = MainWP_Api_Manager::instance()->get_purchased_extension( $api_key );
487
488 $result = json_decode( $data, true );
489 $return = array();
490
491 if ( is_array( $result ) ) {
492 if ( isset( $result['success'] ) && $result['success'] ) {
493 $purchased_data = ( isset( $result['purchased_data'] ) && is_array( $result['purchased_data'] ) ) ? $result['purchased_data'] : array();
494 $not_purchased_exts = array_diff_key( $all_free_pro_exts, $purchased_data );
495 $installing_exts = array_diff_key( $purchased_data, $installed_softwares );
496 $installing_exts = array_merge( $installing_exts, $not_installed_org_exts );
497 } elseif ( isset( $result['error'] ) ) {
498 $return = array( 'error' => $result['error'] );
499 }
500 } else {
501 $apisslverify = get_option( 'mainwp_api_sslVerifyCertificate' );
502 if ( 1 === $apisslverify ) {
503 MainWP_Utility::update_option( 'mainwp_api_sslVerifyCertificate', 0 );
504 $return['retry_action'] = 1;
505 }
506 }
507
508 if ( ! empty( $return ) ) {
509 wp_send_json( $return );
510 }
511 }
512
513 foreach ( $all_available_extensions_compatible_api_response as $product_id => $ext ) {
514
515 $item_html = '';
516 $error = '';
517 $ext_source_label = '';
518 $type = '';
519 $notice = '';
520 $add_on_model = '';
521 $model = $ext['model'];
522
523 if ( 'extension' === $model ) {
524 $add_on_model = 'addon-extension';
525 } elseif ( 'integration' === $model ) {
526 $add_on_model = 'addon-integration';
527 }
528
529 $item_slug = MainWP_Utility::get_dir_slug( $ext['slug'] );
530
531 $privacy = '<a href="#" class="extension-privacy-info-link" base-slug="' . esc_attr( $item_slug ) . '" style="text-decoration:none"> <i class="fingerprint small icon"></i></a>';
532
533 $new = '';
534
535 $software_title = MainWP_Extensions_Handler::polish_string_name( $ext['title'] );
536
537 if ( ! empty( $ext['type'] ) ) {
538 $type = $ext['type'];
539 if ( 'free' === $type ) {
540 $ext_source_label = '<span class="ui mini green label">FREE</span>';
541 } elseif ( 'pro' === $type ) {
542 $ext_source_label = '<span class="ui mini blue label">PRO</span>';
543 } elseif ( 'org' === $type ) {
544 $ext_source_label = '<span class="ui mini grey label">.ORG</span>';
545 }
546 }
547
548 if ( isset( $ext['release_date'] ) && ( time() - $ext['release_date'] < MONTH_IN_SECONDS ) ) {
549 $new = '<span class="ui mini green label">NEW!</span>';
550 }
551
552 if ( 'MainWP WordPress SEO Extension' === $product_id || 'seopress-for-mainwp' === $product_id ) {
553 $notice = ' <i class="info circle small icon"></i>';
554 }
555
556 $ext_source_label .= '&nbsp;';
557
558 if ( isset( $installing_exts[ $product_id ] ) ) {
559
560 $product_info = isset( $installing_exts[ $product_id ] ) ? $installing_exts[ $product_id ] : array();
561 if ( ! is_array( $product_info ) ) {
562 $product_info = array();
563 }
564
565 if ( 'free' === $type || 'pro' === $type ) {
566
567 if ( $product_info && isset( $product_info['package'] ) && ! empty( $product_info['package'] ) ) {
568
569 /**
570 * API Manager Upgrade URL
571 *
572 * Filters the Upgrade URL for extensions.
573 *
574 * @since Unknown
575 * @ignore
576 */
577 $package_url = apply_filters( 'mainwp_api_manager_upgrade_package_url', $product_info['package'], $product_info );
578
579 $item_html = '
580 <div class="item extension extension-to-install ' . esc_attr( $add_on_model ) . '" download-link="' . esc_url( $package_url ) . '" plugin-slug="" product-id="' . esc_attr( $product_id ) . '" software-title="' . esc_attr( $software_title ) . '" slug="' . esc_attr( $ext['slug'] ) . '">
581 <div class="ui stackable grid">
582 <div class="two column row">
583 <div class="column"><span class="ui checkbox"><input type="checkbox" status="queue"><label>' . $ext_source_label . '<strong><a href="' . esc_url( $ext['link'] ) . '" target="_blank">' . esc_html( $software_title ) . '</a>' . $privacy . ' ' . $notice . ' ' . $new . '</strong></label></span></div>
584 <div class="right aligned column"><span class="installing-extension" status="queue"></span></div>
585 </div>
586 </div>
587 </div>';
588
589 } elseif ( isset( $product_info['error'] ) && ! empty( $product_info['error'] ) ) {
590 $error = MainWP_Api_Manager::instance()->check_response_for_intall_errors( $product_info, $software_title );
591 } else {
592 $error = esc_html__( 'Undefined error occurred. Please try again.', 'mainwp' );
593 }
594
595 if ( ! empty( $error ) ) {
596 $item_html = '
597 <div class="item extension ' . esc_attr( $add_on_model ) . '" product-id="' . esc_attr( $product_id ) . '" software-title="' . esc_attr( $software_title ) . '">
598 <div class="ui stackable grid">
599 <div class="two column row">
600 <div class="column"><span class="ui checkbox"><input type="checkbox" disabled="disabled"><label>' . $ext_source_label . ' <a href="' . esc_url( $ext['link'] ) . '" target="_blank">' . esc_html( $software_title ) . '</a>' . $privacy . ' ' . $notice . ' ' . $new . '</label></span></div>
601 <div class="right aligned column"><span data-tooltip="' . $error . '" data-inverted="" data-position="left center"><i class="times red icon"></i></span></div>
602 </div>
603 </div>
604 </div>';
605 }
606 } elseif ( 'org' === $type ) {
607 $item_html = '
608 <div class="item extension extension-to-install ' . esc_attr( $add_on_model ) . '" download-link="" plugin-slug="' . esc_attr( $ext['slug'] ) . '" product-id="' . esc_attr( $product_id ) . '" software-title="' . esc_attr( $software_title ) . '" slug="' . esc_attr( $ext['slug'] ) . '">
609 <div class="ui stackable grid">
610 <div class="two column row">
611 <div class="column"><span class="ui checkbox"><input type="checkbox" status="queue"><label>' . $ext_source_label . '<strong><a href="' . esc_url( $ext['link'] ) . '" target="_blank">' . esc_html( $software_title ) . '</a>' . $privacy . ' ' . $notice . ' ' . $new . '</strong></label></span></div>
612 <div class="right aligned column"><span class="installing-extension" status="queue"></span></div>
613 </div>
614 </div>
615 </div>';
616 }
617 } elseif ( isset( $not_purchased_exts[ $product_id ] ) ) {
618 if ( 'free' === $type || 'pro' === $type ) {
619 $item_html = '
620 <div class="item extension ' . esc_attr( $add_on_model ) . '" product-id="' . esc_attr( $product_id ) . '" software-title="' . esc_attr( $software_title ) . '" slug="' . esc_attr( $ext['slug'] ) . '">
621 <div class="ui stackable grid">
622 <div class="two column row">
623 <div class="column"><span class="ui checkbox"><input type="checkbox" disabled="disabled"><label>' . $ext_source_label . ' <a href="' . esc_url( $ext['link'] ) . '" target="_blank">' . esc_html( $software_title ) . '</a>' . $privacy . ' ' . $notice . ' ' . $new . '</label></span></div>
624 <div class="right aligned column"><a href="' . $ext['link'] . '" target="_blank" data-tooltip="' . esc_html__( 'Add-on not purchased. Click to find out more.', 'mainwp' ) . '" data-position="left center" data-inverted=""><i class="info blue icon"></i></a></div>
625 </div>
626 </div>
627 </div>';
628 } elseif ( 'org' === $type ) {
629 $item_html = '
630 <div class="item extension ' . esc_attr( $add_on_model ) . '" product-id="' . esc_attr( $product_id ) . '" software-title="' . esc_attr( $software_title ) . '" slug="' . esc_attr( $ext['slug'] ) . '">
631 <div class="ui stackable grid">
632 <div class="two column row">
633 <div class="column"><span class="ui checkbox"><input type="checkbox" disabled="disabled"><label>' . $ext_source_label . ' <a href="' . esc_url( $ext['link'] ) . '" target="_blank">' . esc_html( $software_title ) . '</a>' . $privacy . ' ' . $notice . ' ' . $new . '</label></span></div>
634 <div class="right aligned column"><a href="' . $ext['url'] . '" target="_blank" data-tooltip="' . esc_attr__( 'Add-on not installed. Click to find out more.', 'mainwp' ) . '" data-position="left center" data-inverted=""><i class="info blue icon"></i></a></div>
635 </div>
636 </div>
637 </div>';
638 }
639 } elseif ( isset( $installed_softwares[ $product_id ] ) ) {
640 $item_html = '
641 <div class="item extension ' . esc_attr( $add_on_model ) . '" product-id="' . esc_attr( $product_id ) . '" software-title="' . esc_attr( $software_title ) . '" slug="' . esc_attr( $ext['slug'] ) . '">
642 <div class="ui stackable grid">
643 <div class="two column row">
644 <div class="column"><span data-tooltip="' . esc_attr__( 'Already installed', 'mainwp' ) . '" data-inverted="" data-position="right center"><span class="ui checkbox"><input type="checkbox" disabled="disabled"><label>' . $ext_source_label . ' <a href="' . esc_url( $ext['link'] ) . '" target="_blank">' . esc_html( $software_title ) . '</a> ' . $notice . '</label></label></span>' . $privacy . ' ' . $new . '</div>
645 <div class="right aligned column"><span data-tooltip="' . esc_attr__( 'Already installed', 'mainwp' ) . '" data-inverted="" data-position="left center"><i class="grey box icon"></i></span></div>
646 </div>
647 </div>
648 </div>';
649 }
650
651 $group_id = isset( $map_extensions_group[ $product_id ] ) ? $map_extensions_group[ $product_id ] : false;
652 if ( ! empty( $group_id ) && isset( $all_groups[ $group_id ] ) ) {
653 if ( isset( $grouped_exts[ $group_id ] ) ) {
654 $grouped_exts[ $group_id ] .= $item_html;
655 } else {
656 $grouped_exts[ $group_id ] = $item_html;
657 }
658 } else {
659 $grouped_exts['others'] .= $item_html;
660 }
661 $grouped_exts['all'] .= $item_html;
662 }
663
664 $html = '<div class="mainwp-installing-extensions">';
665
666 if ( empty( $installing_exts ) && count( $purchased_data ) === count( $all_free_pro_exts ) ) {
667 $html .= '<div class="ui message">' . esc_html__( 'All purchased add-ons already installed.', 'mainwp' ) . '</div>';
668 } else {
669 $html .= '<div class="ui icon message">';
670 $html .= '<em data-emoji=":bulb:" class="medium"></em>';
671 $html .= '<div class="content">';
672 $html .= '<div class="header">' . esc_html__( 'Install Only What You Need', 'mainwp' ) . '</div>';
673 $html .= '<p>';
674 $html .= esc_html__( 'For a cleaner dashboard and better focus, start with only the add-ons you need right now. You can always install more later.', 'mainwp' );
675 $html .= '</p>';
676 $html .= '</div>';
677 $html .= '</div>';
678
679 $html .= '<div class="ui hidden divider"></div>';
680
681 $html .= '<div id="mainwp-bulk-activating-extensions-status" class="ui message" style="display:none;"></div>';
682
683 $html .= '<div class="ui secondary green pointing tabular stackable menu" id="mainwp-install-extensions-menu">';
684 foreach ( $all_groups as $gr_id => $gr_name ) {
685 if ( isset( $grouped_exts[ $gr_id ] ) ) {
686 $html .= '<a class="item" data-tab="' . $gr_id . '">' . $gr_name . '</a>';
687 }
688 }
689
690 if ( isset( $grouped_exts['others'] ) && ! empty( $grouped_exts['others'] ) ) {
691 $html .= '<a class="item" data-tab="other">' . esc_html__( 'Other', 'mainwp' ) . '</a>';
692 }
693
694 $html .= '</div>';
695
696 $html .= '<div class="ui grid">';
697 $html .= '<div class="eight wide left aligned middle aligned column">';
698 $html .= esc_html__( 'Show: ', 'mainwp' );
699 $html .= '<div class="ui checkbox"><input type="checkbox" checked="" id="mainwp-standalone-extensions-filer" name="mainwp-standalone-extensions-filer"><label><strong>' . esc_html__( 'Extensions', 'mainwp' ) . '</label></div>';
700 $html .= '&nbsp;&nbsp; <div class="ui checkbox"><input type="checkbox" checked="" id="mainwp-api-extensions-filer" name="mainwp-api-extensions-filer"><label>' . esc_html__( 'Integrations', 'mainwp' ) . '</label></div>';
701 $html .= '&nbsp;&nbsp;&nbsp;&nbsp;<span class="ui small grey text" data-tooltip="' . esc_attr__( 'Extensions add Dashboard features without third-party dependencies. Integrations connect your Dashboard to third-party tools you use.', 'mainwp' ) . '" data-position="right center" data-inverted=""><i class="question circle icon"></i> ' . esc_html__( 'What\'s the difference?', 'mainwp' ) . '</span>';
702 $html .= '</div>';
703 $html .= '<div class="eight wide right aligned middle aligned column">';
704 $html .= '<div id="mainwp-search-extensions-install" class="ui fluid search">
705 <div class="ui icon mini input">
706 <input class="prompt" id="mainwp-search-extensions-install-input" type="text" placeholder="' . esc_attr__( 'Find add-on...', 'mainwp' ) . '">
707 <i class="search icon"></i>
708 </div>
709 </div>';
710 $html .= '</div>';
711 $html .= '</div>';
712
713 $html .= '<div class="ui divider"></div>';
714
715 $html .= '<div id="mainwp-extensions-search-results" style="display:none;">';
716 $html .= '<div class="ui middle aligned divided list">';
717 $html .= $grouped_exts['all'];
718 $html .= '</div>';
719 $html .= '</div>';
720
721 foreach ( $all_groups as $gr_id => $gr_name ) {
722 if ( isset( $grouped_exts[ $gr_id ] ) && 'all' !== $gr_id ) {
723 $html .= '<div class="ui tab" data-tab="' . $gr_id . '">';
724 $html .= '<div class="ui middle aligned divided list">';
725 $html .= $grouped_exts[ $gr_id ];
726 $html .= '</div>';
727 $html .= '</div>';
728 }
729 }
730
731 if ( isset( $grouped_exts['others'] ) && ! empty( $grouped_exts['others'] ) ) {
732 $html .= '<div class="ui tab" data-tab="other">';
733 $html .= '<div class="ui middle aligned divided list">';
734 $html .= $grouped_exts['others'];
735 $html .= '</div>';
736 $html .= '</div>';
737 }
738 }
739
740 $html .= '<div class="ui hidden divider"></div>';
741
742 $html .= '<div class="ui secondary vertically fitted segment">';
743 $html .= '<div class="ui horizontal mini list">';
744 $html .= '<div class="item"><span class="ui mini green label">FREE</span> ' . esc_html__( 'Free add-on by MainWP', 'mainwp' ) . '</div>';
745 $html .= '<div class="item"><span class="ui mini blue label">PRO</span> ' . esc_html__( 'Premium add-on by MainWP', 'mainwp' ) . '</div>';
746 $html .= '<div class="item"><span class="ui mini grey label">.ORG</span> ' . esc_html__( 'Free add-on by a third party (WP.org)', 'mainwp' ) . '</div>';
747 $html .= '<div class="item"><i class="info circle icon"></i> ' . esc_html__( 'Requires the matching plugin on too.', 'mainwp' ) . '</div>';
748 $html .= '<div class="item"><i class="fingerprint icon"></i> ' . esc_html__( 'Add-on privacy info.', 'mainwp' ) . '</div>';
749 $html .= '</div>';
750 $html .= '</div>';
751
752 $html .= '<script>jQuery( "#mainwp-install-extensions-menu .item" ).tab();</script>';
753 $html .= '<script type="text/javascript">
754 jQuery( document ).ready( function () {
755 let $menu = jQuery( "#mainwp-install-extensions-menu" );
756 let $modal = jQuery( "#mainwp-get-purchased-extensions-modal" );
757 let $searchInput = jQuery( "#mainwp-search-extensions-install-input" );
758 let $searchResults = jQuery( "#mainwp-extensions-search-results" );
759 let $tabsContainer = $modal.find( ".ui.tab[data-tab]" );
760
761 $searchResults.find( "input[type=\"checkbox\"]" ).removeAttr( "status" );
762
763 function syncToTabs( productId, checked ) {
764 $tabsContainer.find( ".extension-to-install[product-id=\"" + productId + "\"] input[type=\"checkbox\"]" ).prop( "checked", checked );
765 }
766
767 function syncToSearch( productId, checked ) {
768 $searchResults.find( ".extension-to-install[product-id=\"" + productId + "\"] input[type=\"checkbox\"]" ).prop( "checked", checked );
769 }
770
771 $searchResults.on( "change", "input[type=\"checkbox\"]", function () {
772 let productId = jQuery( this ).closest( ".extension-to-install" ).attr( "product-id" );
773 syncToTabs( productId, jQuery( this ).prop( "checked" ) );
774 } );
775
776 $tabsContainer.on( "change", "input[type=\"checkbox\"]", function () {
777 let productId = jQuery( this ).closest( ".extension-to-install" ).attr( "product-id" );
778 syncToSearch( productId, jQuery( this ).prop( "checked" ) );
779 } );
780
781 function exitSearchMode() {
782 $searchResults.hide();
783 $searchResults.find( ".item.extension" ).show();
784 $tabsContainer.css( "display", "" );
785 $menu.find( ".item" ).first().trigger( "click" );
786 }
787
788 $searchInput.on( "keyup", function () {
789 let searchQuery = jQuery( this ).val().toLowerCase();
790
791 if ( searchQuery.length > 0 ) {
792 $menu.find( ".item.active" ).removeClass( "active" );
793 $tabsContainer.css( "display", "none" );
794 $searchResults.show();
795 $searchResults.find( ".item.extension" ).each( function () {
796 let extensionTitle = jQuery( this ).attr( "software-title" ).toLowerCase();
797 jQuery( this ).toggle( extensionTitle.indexOf( searchQuery ) > -1 );
798 } );
799 } else {
800 exitSearchMode();
801 }
802 } );
803
804 $menu.on( "click", ".item", function () {
805 if ( $searchResults.is( ":visible" ) ) {
806 $searchInput.val( "" );
807 $searchResults.hide();
808 $searchResults.find( ".item.extension" ).show();
809 $tabsContainer.css( "display", "" );
810 }
811 } );
812 jQuery( "#mainwp-standalone-extensions-filer" ).on( "change", function () {
813 jQuery( "#mainwp-get-purchased-extensions-modal .ui.list .item.extension.addon-extension" ).toggle( 200 );
814 } );
815 jQuery( "#mainwp-api-extensions-filer" ).on( "change", function () {
816 jQuery( "#mainwp-get-purchased-extensions-modal .ui.list .item.extension.addon-integration" ).toggle( 200 );
817 } );
818 } );
819 </script>';
820
821 if ( ! empty( $installing_exts ) ) {
822 $html .= '<p>
823 <span class="extension_api_loading">
824 <i class="ui active inline loader tiny" style="display: none;"></i><span class="status hidden"></span>
825 </span>
826 </p> ';
827 }
828
829 $html .= '</div>';
830 $return = array(
831 'result' => 'SUCCESS',
832 'data' => $html,
833 );
834
835 wp_send_json( $return );
836 }
837
838 /**
839 * Method render_header()
840 *
841 * Render page header.
842 *
843 * @param string $shownPage The page slug shown at this moment.
844 *
845 * @uses \MainWP\Dashboard\MainWP_Deprecated_Hooks::maybe_handle_deprecated_hook()
846 * @uses \MainWP\Dashboard\MainWP_Extensions_View::render_header()
847 */
848 public static function render_header( $shownPage = '' ) {
849 MainWP_Deprecated_Hooks::maybe_handle_deprecated_hook();
850 MainWP_Extensions_View::render_header( $shownPage );
851 }
852
853 /**
854 * Method render_footer()
855 *
856 * Render page footer.
857 *
858 * @param string $shownPage The page slug shown at this moment.
859 *
860 * @uses \MainWP\Dashboard\MainWP_Deprecated_Hooks::maybe_handle_deprecated_hook()
861 * @uses \MainWP\Dashboard\MainWP_Extensions_View::render_footer()
862 */
863 public static function render_footer( $shownPage ) {
864 MainWP_Deprecated_Hooks::maybe_handle_deprecated_hook();
865 MainWP_Extensions_View::render_footer( $shownPage );
866 }
867
868 /**
869 * Method render()
870 *
871 * Render page content.
872 *
873 * @uses \MainWP\Dashboard\MainWP_Extensions_View::render()
874 * @uses \MainWP\Dashboard\MainWP_UI::render_top_header()
875 */
876 public static function render() {
877
878 $params = array(
879 'title' => esc_html__( 'Add-ons', 'mainwp' ),
880 );
881 MainWP_UI::render_top_header( $params );
882
883 MainWP_Extensions_View::render();
884 echo '</div>';
885 }
886
887 /**
888 * Method mainwp_help_content()
889 *
890 * Creates the MainWP Help Documentation List for the help component in the sidebar.
891 */
892 public static function mainwp_help_content() {
893 if ( isset( $_GET['page'] ) && 'Extensions' === $_GET['page'] ) { // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
894 ?>
895 <p><?php esc_html_e( 'If you need help with your MainWP Add-ons, please review following help documents', 'mainwp' ); ?></p>
896 <div class="ui list">
897 <div class="item"><i class="external alternate icon"></i> <a href="https://docs.mainwp.com/dashboard/overview/manage-extensions" target="_blank"><i class="fa fa-book"></i> What are the MainWP Add-ons</a></div> <?php // NOSONAR - noopener - open safe. ?>
898 <div class="item"><i class="external alternate icon"></i> <a href="https://docs.mainwp.com/dashboard/overview/manage-extensions#order-add-ons" target="_blank"><i class="fa fa-book"></i> Order Add-on(s)</a></div> <?php // NOSONAR - noopener - open safe. ?>
899 <div class="item"><i class="external alternate icon"></i> <a href="https://docs.mainwp.com/dashboard/overview/manage-extensions#install-add-ons" target="_blank"><i class="fa fa-book"></i> Install Add-on(s)</a></div> <?php // NOSONAR - noopener - open safe. ?>
900 <div class="item"><i class="external alternate icon"></i> <a href="https://docs.mainwp.com/dashboard/overview/manage-extensions#activate-add-ons" target="_blank"><i class="fa fa-book"></i> Activate Add-on(s) API</a></div> <?php // NOSONAR - noopener - open safe. ?>
901 <div class="item"><i class="external alternate icon"></i> <a href="https://docs.mainwp.com/dashboard/overview/manage-extensions#update-add-ons" target="_blank"><i class="fa fa-book"></i> Updating Add-on(s)</a></div> <?php // NOSONAR - noopener - open safe. ?>
902 <div class="item"><i class="external alternate icon"></i> <a href="https://docs.mainwp.com/dashboard/overview/manage-extensions#remove-unneeded-add-ons" target="_blank"><i class="fa fa-book"></i> Remove Add-on(s)</a></div> <?php // NOSONAR - noopener - open safe. ?>
903 </div>
904 <?php
905 /**
906 * Action: mainwp_extensions_help_item
907 *
908 * Fires at the bottom of the help articles list in the Help sidebar on the Extensions page.
909 *
910 * Suggested HTML markup:
911 *
912 * <div class="item"><a href="Your custom URL">Your custom text</a></div>
913 *
914 * @since 5.2
915 */
916 do_action( 'mainwp_extensions_help_item' );
917 }
918 }
919 }
920