| @@ -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 .= ' <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 .= ' <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(); |
| @@ -791,23 +886,34 @@ | ||
| 791 | 886 | |
| 792 | 887 | /** |
| 793 | 888 | * Method mainwp_help_content() |
| 794 | 889 | * |
| 795 | - * Create the MainWP Help Document List for the help component in the sidebar. | |
| 890 | + * Creates the MainWP Help Documentation List for the help component in the sidebar. | |
| 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> | |
| 801 | - <div class="ui relaxed bulleted list"> | |
| 802 | - <div class="item"><a href="https://kb.mainwp.com/docs/what-are-mainwp-extensions/" target="_blank"><i class="fa fa-book"></i> What are the MainWP Extensions</a> <i class="external alternate icon"></i></div> <?php // NOSONAR - noopener - open safe. ?> | |
| 803 | - <div class="item"><a href="https://kb.mainwp.com/docs/order-extensions/" target="_blank"><i class="fa fa-book"></i> Order Extension(s)</a> <i class="external alternate icon"></i></div> <?php // NOSONAR - noopener - open safe. ?> | |
| 804 | - <div class="item"><a href="https://kb.mainwp.com/docs/my-downloads-and-api-keys/" target="_blank"><i class="fa fa-book"></i> My Downloads and API Keys</a> <i class="external alternate icon"></i></div> <?php // NOSONAR - noopener - open safe. ?> | |
| 805 | - <div class="item"><a href="https://kb.mainwp.com/docs/install-extensions/" target="_blank"><i class="fa fa-book"></i> Install Extension(s)</a> <i class="external alternate icon"></i></div> <?php // NOSONAR - noopener - open safe. ?> | |
| 806 | - <div class="item"><a href="https://kb.mainwp.com/docs/activate-extensions-api/" target="_blank"><i class="fa fa-book"></i> Activate Extension(s) API</a> <i class="external alternate icon"></i></div> <?php // NOSONAR - noopener - open safe. ?> | |
| 807 | - <div class="item"><a href="https://kb.mainwp.com/docs/updating-extensions/" target="_blank"><i class="fa fa-book"></i> Updating Extension(s)</a> <i class="external alternate icon"></i></div> <?php // NOSONAR - noopener - open safe. ?> | |
| 808 | - <div class="item"><a href="https://kb.mainwp.com/docs/remove-extensions/" target="_blank"><i class="fa fa-book"></i> Remove Extension(s)</a> <i class="external alternate icon"></i></div> <?php // NOSONAR - noopener - open safe. ?> | |
| 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. ?> | |
| 809 | 903 | </div> |
| 810 | 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' ); | |
| 811 | 917 | } |
| 812 | 918 | } |
| 813 | 919 | } |