PluginProbe
MainWP Dashboard: Self-hosted WordPress Management for Agencies / 6.2.1
MainWP Dashboard: Self-hosted WordPress Management for Agencies v6.2.1
6.2.2 6.2.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 All 155 releases
← All changes | pages/page-mainwp-extensions.php +192 -97 5.2 → 6.2.1 View file →
@@ -6,8 +6,13 @@
6 6 */
7 7
8 8 namespace MainWP\Dashboard;
9 9
10 +// Exit if accessed directly.
11 +if ( ! defined( 'ABSPATH' ) ) {
12 + exit;
13 +}
14 +
10 15 /**
11 16 * Class MainWP_Extensions
12 17 */
13 18 class MainWP_Extensions { // phpcs:ignore Generic.Classes.OpeningBraceSameLine.ContentAfterBrace -- NOSONAR.
@@ -187,11 +192,40 @@
187 192 $api_slug = dirname( $slug );
188 193
189 194 if ( $is_cached ) {
190 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 + }
191 212 } else {
192 - $options = MainWP_Api_Manager::instance()->get_activation_info( $api_slug );
193 - $activations_cached[ $api_slug ] = $options;
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;
194 228 }
195 229
196 230 if ( ! is_array( $options ) ) {
197 231 $options = array();
@@ -196,9 +230,21 @@
196 230 if ( ! is_array( $options ) ) {
197 231 $options = array();
198 232 }
199 233
200 - $extension['api_key'] = isset( $options['api_key'] ) ? $options['api_key'] : '';
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'] );
201 247 $extension['activated_key'] = isset( $options['activated_key'] ) ? $options['activated_key'] : 'Deactivated';
202 248 $extension['deactivate_checkbox'] = isset( $options['deactivate_checkbox'] ) ? $options['deactivate_checkbox'] : 'off';
203 249 $extension['product_id'] = isset( $options['product_id'] ) ? $options['product_id'] : '';
204 250 $extension['instance_id'] = isset( $options['instance_id'] ) ? $options['instance_id'] : '';
@@ -215,9 +261,9 @@
215 261 $extension['type'] = $all_available_extensions[ $ext_slug ]['type']; // to fix.
216 262 }
217 263
218 264 $save_extensions[] = $extension;
219 - if ( mainwp_current_user_have_right( 'extension', dirname( $slug ) ) ) {
265 + if ( \mainwp_current_user_can( 'extension', dirname( $slug ) ) ) {
220 266 $callback = isset( $extension['callback'] ) ? $extension['callback'] : '';
221 267 $menu_name = MainWP_Extensions_Handler::polish_ext_name( $extension );
222 268 if ( MainWP_Extensions_Handler::added_on_menu( $slug ) ) {
223 269 $_page = add_submenu_page( 'mainwp_tab', $extension['name'], $menu_name, 'read', $extension['page'], $callback );
@@ -266,13 +312,13 @@
266 312 $subPages = array();
267 313 if ( ! MainWP_Menu::is_disable_menu_item( 2, 'Extensions' ) ) {
268 314 MainWP_Menu::add_left_menu(
269 315 array(
270 - 'title' => esc_html__( 'Extensions', 'mainwp' ),
316 + 'title' => esc_html__( 'Add-ons', 'mainwp' ),
271 317 'parent_key' => 'mainwp_tab',
272 318 'slug' => 'Extensions',
273 319 'href' => 'admin.php?page=Extensions',
274 - 'icon' => '<i class="puzzle piece icon"></i>',
320 + 'icon' => '<i class="box icon"></i>',
275 321 ),
276 322 0
277 323 );
278 324
@@ -277,9 +323,9 @@
277 323 );
278 324
279 325 $init_sub_subleftmenu = array(
280 326 array(
281 - 'title' => esc_html__( 'Manage Extensions', 'mainwp' ),
327 + 'title' => esc_html__( 'Manage Add-ons', 'mainwp' ),
282 328 'parent_key' => 'Extensions',
283 329 'href' => 'admin.php?page=Extensions',
284 330 'slug' => 'Extensions',
285 331 'right' => '',
@@ -326,9 +372,9 @@
326 372 return;
327 373 }
328 374 $html = '';
329 375 foreach ( $exts as $extension ) {
330 - if ( defined( 'MWP_TEAMCONTROL_PLUGIN_SLUG' ) && ( MWP_TEAMCONTROL_PLUGIN_SLUG === $extension['slug'] ) && ! mainwp_current_user_have_right( 'extension', dirname( MWP_TEAMCONTROL_PLUGIN_SLUG ) ) ) {
376 + if ( defined( 'MWP_TEAMCONTROL_PLUGIN_SLUG' ) && ( MWP_TEAMCONTROL_PLUGIN_SLUG === $extension['slug'] ) && ! \mainwp_current_user_can( 'extension', dirname( MWP_TEAMCONTROL_PLUGIN_SLUG ) ) ) {
331 377 continue;
332 378 }
333 379 if ( MainWP_Extensions_Handler::added_on_menu( $extension['slug'] ) ) {
334 380 continue;
@@ -375,8 +421,18 @@
375 421 MainWP_Post_Handler::instance()->secure_request( 'mainwp_extension_getpurchased' );
376 422
377 423 $api_key = isset( $_POST['api_key'] ) ? trim( wp_unslash( $_POST['api_key'] ) ) : false; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
378 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 +
379 435 $all_available_extensions_compatible_api_response = array();
380 436 $all_free_pro_exts = array();
381 437 $all_org_exts = array();
382 438 $map_extensions_group = array();
@@ -460,22 +516,20 @@
460 516 $error = '';
461 517 $ext_source_label = '';
462 518 $type = '';
463 519 $notice = '';
464 - $integration_type = '';
465 - $pri = (int) $ext['privacy'];
520 + $add_on_model = '';
521 + $model = $ext['model'];
466 522
467 - if ( 0 === $pri ) {
468 - $integration_type = 'standalone-extension';
469 - } elseif ( 1 === $pri ) {
470 - $integration_type = 'api-extension';
471 - } elseif ( 2 === $pri ) {
472 - $integration_type = 'plugin-extension';
523 + if ( 'extension' === $model ) {
524 + $add_on_model = 'addon-extension';
525 + } elseif ( 'integration' === $model ) {
526 + $add_on_model = 'addon-integration';
473 527 }
474 528
475 529 $item_slug = MainWP_Utility::get_dir_slug( $ext['slug'] );
476 530
477 - $privacy = '<a href="#" class="extension-privacy-info-link" base-slug="' . esc_attr( $item_slug ) . '" style="text-decoration:none"> <i class="shield alternate small icon"></i></a>';
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>';
478 532
479 533 $new = '';
480 534
481 535 $software_title = MainWP_Extensions_Handler::polish_string_name( $ext['title'] );
@@ -522,9 +576,9 @@
522 576 */
523 577 $package_url = apply_filters( 'mainwp_api_manager_upgrade_package_url', $product_info['package'], $product_info );
524 578
525 579 $item_html = '
526 - <div class="item extension extension-to-install ' . esc_attr( $integration_type ) . '" download-link="' . esc_url( $package_url ) . '" plugin-slug="" product-id="' . esc_attr( $product_id ) . '" slug="' . esc_attr( $ext['slug'] ) . '">
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'] ) . '">
527 581 <div class="ui stackable grid">
528 582 <div class="two column row">
529 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>
530 584 <div class="right aligned column"><span class="installing-extension" status="queue"></span></div>
@@ -539,9 +593,9 @@
539 593 }
540 594
541 595 if ( ! empty( $error ) ) {
542 596 $item_html = '
543 - <div class="item extension ' . esc_attr( $integration_type ) . '" product-id="' . esc_attr( $product_id ) . '">
597 + <div class="item extension ' . esc_attr( $add_on_model ) . '" product-id="' . esc_attr( $product_id ) . '" software-title="' . esc_attr( $software_title ) . '">
544 598 <div class="ui stackable grid">
545 599 <div class="two column row">
546 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>
547 601 <div class="right aligned column"><span data-tooltip="' . $error . '" data-inverted="" data-position="left center"><i class="times red icon"></i></span></div>
@@ -550,9 +604,9 @@
550 604 </div>';
551 605 }
552 606 } elseif ( 'org' === $type ) {
553 607 $item_html = '
554 - <div class="item extension extension-to-install ' . esc_attr( $integration_type ) . '" download-link="" plugin-slug="' . esc_attr( $ext['slug'] ) . '" product-id="' . esc_attr( $product_id ) . '" slug="' . esc_attr( $ext['slug'] ) . '">
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'] ) . '">
555 609 <div class="ui stackable grid">
556 610 <div class="two column row">
557 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>
558 612 <div class="right aligned column"><span class="installing-extension" status="queue"></span></div>
@@ -562,23 +616,23 @@
562 616 }
563 617 } elseif ( isset( $not_purchased_exts[ $product_id ] ) ) {
564 618 if ( 'free' === $type || 'pro' === $type ) {
565 619 $item_html = '
566 - <div class="item extension ' . esc_attr( $integration_type ) . '" product-id="' . esc_attr( $product_id ) . '" slug="' . esc_attr( $ext['slug'] ) . '">
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'] ) . '">
567 621 <div class="ui stackable grid">
568 622 <div class="two column row">
569 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>
570 - <div class="right aligned column"><a href="' . $ext['link'] . '" target="_blank" data-tooltip="' . esc_html__( 'Extension not purchased. Click to find out more.', 'mainwp' ) . '" data-position="left center" data-inverted=""><i class="info blue icon"></i></a></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>
571 625 </div>
572 626 </div>
573 627 </div>';
574 628 } elseif ( 'org' === $type ) {
575 629 $item_html = '
576 - <div class="item extension ' . esc_attr( $integration_type ) . '" product-id="' . esc_attr( $product_id ) . '" slug="' . esc_attr( $ext['slug'] ) . '">
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'] ) . '">
577 631 <div class="ui stackable grid">
578 632 <div class="two column row">
579 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>
580 - <div class="right aligned column"><a href="' . $ext['url'] . '" target="_blank" data-tooltip="' . esc_html__( 'Extension not installed. Click to find out more.', 'mainwp' ) . '" data-position="left center" data-inverted=""><i class="info blue icon"></i></a></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>
581 635 </div>
582 636 </div>
583 637 </div>';
584 638 }
@@ -583,13 +637,13 @@
583 637 </div>';
584 638 }
585 639 } elseif ( isset( $installed_softwares[ $product_id ] ) ) {
586 640 $item_html = '
587 - <div class="item extension ' . esc_attr( $integration_type ) . '" product-id="' . esc_attr( $product_id ) . '" slug="' . esc_attr( $ext['slug'] ) . '">
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'] ) . '">
588 642 <div class="ui stackable grid">
589 643 <div class="two column row">
590 - <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> ' . $notice . '</label></span>' . $privacy . ' ' . $new . '</div>
591 - <div class="right aligned column">Installed</div>
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>
592 646 </div>
593 647 </div>
594 648 </div>';
595 649 }
@@ -609,24 +663,22 @@
609 663
610 664 $html = '<div class="mainwp-installing-extensions">';
611 665
612 666 if ( empty( $installing_exts ) && count( $purchased_data ) === count( $all_free_pro_exts ) ) {
613 - $html .= '<div class="ui message yellow">' . esc_html__( 'All purchased extensions already installed.', 'mainwp' ) . '</div>';
667 + $html .= '<div class="ui message">' . esc_html__( 'All purchased add-ons already installed.', 'mainwp' ) . '</div>';
614 668 } else {
615 - if ( isset( $not_purchased_exts ) && ! empty( $not_purchased_exts ) ) {
616 - $html .= '<div class="ui message info">' . esc_html__( 'You have access to all our Free and third-party Extensions on WP.org and any that you have registered for, but you DO NOT need to install them. ', 'mainwp' );
617 - $html .= '<br /><br />';
618 - $html .= esc_html__( 'To avoid information overload, we highly recommend adding Extensions one at a time and as you need them. Skip any Extension you do not want to install at this time.', 'mainwp' );
619 - $html .= '<br /><br />';
620 - $html .= sprintf( esc_html__( 'After installing all your selected Extensions, close the modal by clicking the Close button and %1$sactivate Extensions API license%2$s.', 'mainwp' ), '<a href="https://kb.mainwp.com/docs/activate-extensions-api/" target="_blank">', '</a> <i class="external alternate icon"></i>' ) . '</div>'; // NOSONAR - noopener - open safe.
621 - } else {
622 - $html .= '<div class="ui message info">' . esc_html__( 'You have access to the MainWP Pro plan, which gives you access to all MainWP-created Extensions, but you DO NOT need to install all of them.', 'mainwp' );
623 - $html .= '<br /><br />';
624 - $html .= esc_html__( 'To avoid information overload, we highly recommend adding Extensions one at a time and as you need them. Skip any Extension you do not want to install at this time.', 'mainwp' );
625 - $html .= '<br /><br />';
626 - $html .= sprintf( esc_html__( 'After installing all your selected Extensions, close the modal by clicking the Close button and %1$sactivate Extensions API license%2$s.', 'mainwp' ), '<a href="https://kb.mainwp.com/docs/activate-extensions-api/" target="_blank">', '</a> <i class="external alternate icon"></i>' ) . '</div>'; // NOSONAR - noopener - open safe.
627 - }
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>';
628 678
679 + $html .= '<div class="ui hidden divider"></div>';
680 +
629 681 $html .= '<div id="mainwp-bulk-activating-extensions-status" class="ui message" style="display:none;"></div>';
630 682
631 683 $html .= '<div class="ui secondary green pointing tabular stackable menu" id="mainwp-install-extensions-menu">';
632 684 foreach ( $all_groups as $gr_id => $gr_name ) {
@@ -638,19 +690,39 @@
638 690 if ( isset( $grouped_exts['others'] ) && ! empty( $grouped_exts['others'] ) ) {
639 691 $html .= '<a class="item" data-tab="other">' . esc_html__( 'Other', 'mainwp' ) . '</a>';
640 692 }
641 693
642 - $html .= '<a class="item" data-tab="search"><i class="search icon"></i></a>';
694 + $html .= '</div>';
643 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>';
644 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>';
645 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 +
646 721 foreach ( $all_groups as $gr_id => $gr_name ) {
647 722 if ( isset( $grouped_exts[ $gr_id ] ) && 'all' !== $gr_id ) {
648 723 $html .= '<div class="ui tab" data-tab="' . $gr_id . '">';
649 - $html .= '<div class="ui hidden divider"></div>';
650 - $html .= '<h3>' . $gr_name . '</h3>';
651 - $html .= '<div class="ui hidden divider"></div>';
652 - $html .= '<div class="ui relaxed divided list">';
724 + $html .= '<div class="ui middle aligned divided list">';
653 725 $html .= $grouped_exts[ $gr_id ];
654 726 $html .= '</div>';
655 727 $html .= '</div>';
656 728 }
@@ -657,70 +729,93 @@
657 729 }
658 730
659 731 if ( isset( $grouped_exts['others'] ) && ! empty( $grouped_exts['others'] ) ) {
660 732 $html .= '<div class="ui tab" data-tab="other">';
661 - $html .= '<h3>Other</h3>';
662 - $html .= '<div class="ui relaxed divided list">';
733 + $html .= '<div class="ui middle aligned divided list">';
663 734 $html .= $grouped_exts['others'];
664 735 $html .= '</div>';
665 736 $html .= '</div>';
666 737 }
738 + }
667 739
668 - $html .= '<div class="ui tab" data-tab="search">';
669 - $html .= '<h3>Search Extensions</h3>';
670 - $html .= '<div id="mainwp-search-extensions-install" class="ui fluid search">
671 - <div class="ui icon fluid input">
672 - <input class="prompt" id="mainwp-search-extensions-install-input" type="text" placeholder="Find extension...">
673 - <i class="search icon"></i>
674 - </div>
675 - </div>';
676 - $html .= '<div class="ui relaxed divided list" id="mainwp-extensions-to-install-list">';
677 - $html .= $grouped_exts['all'];
678 - $html .= '</div>';
679 - $html .= '</div>';
740 + $html .= '<div class="ui hidden divider"></div>';
680 741
681 - }
682 - $html .= '<div class="ui hidden divider"></div>';
683 - $html .= '<div class="ui secondary segment">';
684 - $html .= '<div class="ui checkbox"><input type="checkbox" checked="" id="mainwp-standalone-extensions-filer" name="mainwp-standalone-extensions-filer"><label>' . esc_html__( 'Show standalone extensions', 'mainwp' ) . '</label></div><br/>';
685 - $html .= '<div class="ui checkbox"><input type="checkbox" checked="" id="mainwp-api-extensions-filer" name="mainwp-api-extensions-filer"><label>' . esc_html__( 'Show extensions that integrate with 3rd party API', 'mainwp' ) . '</label></div><br/>';
686 - $html .= '<div class="ui checkbox"><input type="checkbox" checked="" id="mainwp-plugin-extensions-filer" name="mainwp-plugin-extensions-filer"><label>' . esc_html__( 'Show extensions that integrate with 3rd party plugin', 'mainwp' ) . '</label></div>';
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>';
687 749 $html .= '</div>';
688 - $html .= '<div class="ui hidden divider"></div>';
689 - $html .= '<div class="ui secondary segment">';
690 - $html .= '<span class="ui mini green label">FREE</span> - ' . esc_html__( 'Free extension developed by MainWP', 'mainwp' ) . '<br/>';
691 - $html .= '<span class="ui mini blue label">PRO</span> - ' . esc_html__( 'Premium extension developed by MainWP', 'mainwp' ) . '<br/>';
692 - $html .= '<span class="ui mini grey label">.ORG</span> - ' . esc_html__( 'Free extension developed by 3rd party author, available on WordPress.org', 'mainwp' );
693 - $html .= '<div class="ui hidden fitted divider"></div>';
694 - $html .= '<i class="info circle icon"></i> ' . esc_html__( 'Extension requires the corresponding plugin on your MainWP Dashboard site too.', 'mainwp' ) . '<br/>';
695 - $html .= '<i class="shield alternate icon"></i> ' . esc_html__( 'Shows the extension privacy info.', 'mainwp' ) . '<br/>';
696 750 $html .= '</div>';
697 751
698 752 $html .= '<script>jQuery( "#mainwp-install-extensions-menu .item" ).tab();</script>';
699 753 $html .= '<script type="text/javascript">
700 754 jQuery( document ).ready( function () {
701 - jQuery( "#mainwp-search-extensions-install-input" ).on( "keyup", 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 () {
702 789 let searchQuery = jQuery( this ).val().toLowerCase();
703 - let extensions = jQuery( "#mainwp-extensions-to-install-list" ).find( ".item.extension" );
704 - for ( let i = 0; i < extensions.length; i++ ) {
705 - let currentExtension = jQuery( extensions[i] );
706 - let extensionTitle = jQuery( currentExtension ).attr( "product-id" ).toLowerCase();
707 - if ( extensionTitle.indexOf( searchQuery ) > -1 ) {
708 - currentExtension.show();
709 - } else {
710 - currentExtension.hide();
711 - }
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();
712 801 }
713 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 + } );
714 812 jQuery( "#mainwp-standalone-extensions-filer" ).on( "change", function () {
715 - jQuery( "#mainwp-get-purchased-extensions-modal .ui.relaxed.divided.list .item.extension.standalone-extension" ).toggle( 200 );
813 + jQuery( "#mainwp-get-purchased-extensions-modal .ui.list .item.extension.addon-extension" ).toggle( 200 );
716 814 } );
717 815 jQuery( "#mainwp-api-extensions-filer" ).on( "change", function () {
718 - jQuery( "#mainwp-get-purchased-extensions-modal .ui.relaxed.divided.list .item.extension.api-extension" ).toggle( 200 );
816 + jQuery( "#mainwp-get-purchased-extensions-modal .ui.list .item.extension.addon-integration" ).toggle( 200 );
719 817 } );
720 - jQuery( "#mainwp-plugin-extensions-filer" ).on( "change", function () {
721 - jQuery( "#mainwp-get-purchased-extensions-modal .ui.relaxed.divided.list .item.extension.plugin-extension" ).toggle( 200 );
722 - } );
723 818 } );
724 819 </script>';
725 820
726 821 if ( ! empty( $installing_exts ) ) {
@@ -780,9 +875,9 @@
780 875 */
781 876 public static function render() {
782 877
783 878 $params = array(
784 - 'title' => esc_html__( 'Extensions', 'mainwp' ),
879 + 'title' => esc_html__( 'Add-ons', 'mainwp' ),
785 880 );
786 881 MainWP_UI::render_top_header( $params );
787 882
788 883 MainWP_Extensions_View::render();
@@ -796,16 +891,16 @@
796 891 */
797 892 public static function mainwp_help_content() {
798 893 if ( isset( $_GET['page'] ) && 'Extensions' === $_GET['page'] ) { // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
799 894 ?>
800 - <p><?php esc_html_e( 'If you need help with your MainWP Extensions, please review following help documents', 'mainwp' ); ?></p>
895 + <p><?php esc_html_e( 'If you need help with your MainWP Add-ons, please review following help documents', 'mainwp' ); ?></p>
801 896 <div class="ui list">
802 - <div class="item"><i class="external alternate icon"></i> <a href="https://kb.mainwp.com/docs/manage-extensions/" target="_blank"><i class="fa fa-book"></i> What are the MainWP Extensions</a></div> <?php // NOSONAR - noopener - open safe. ?>
803 - <div class="item"><i class="external alternate icon"></i> <a href="https://kb.mainwp.com/docs/manage-extensions/#order-extensions" target="_blank"><i class="fa fa-book"></i> Order Extension(s)</a></div> <?php // NOSONAR - noopener - open safe. ?>
804 - <div class="item"><i class="external alternate icon"></i> <a href="https://kb.mainwp.com/docs/manage-extensions/#install-extensions" target="_blank"><i class="fa fa-book"></i> Install Extension(s)</a></div> <?php // NOSONAR - noopener - open safe. ?>
805 - <div class="item"><i class="external alternate icon"></i> <a href="https://kb.mainwp.com/docs/manage-extensions/#activate-extensions" target="_blank"><i class="fa fa-book"></i> Activate Extension(s) API</a></div> <?php // NOSONAR - noopener - open safe. ?>
806 - <div class="item"><i class="external alternate icon"></i> <a href="https://kb.mainwp.com/docs/manage-extensions/#update-extensions" target="_blank"><i class="fa fa-book"></i> Updating Extension(s)</a></div> <?php // NOSONAR - noopener - open safe. ?>
807 - <div class="item"><i class="external alternate icon"></i> <a href="https://kb.mainwp.com/docs/manage-extensions/#remove-unneeded-extensions" target="_blank"><i class="fa fa-book"></i> Remove Extension(s)</a></div> <?php // NOSONAR - noopener - open safe. ?>
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. ?>
808 903 </div>
809 904 <?php
810 905 /**
811 906 * Action: mainwp_extensions_help_item