all_plugins ) ) { $this->all_plugins = $this->define_plugins(); } return $this->all_plugins; } /** * Define the recommended plugins in display order. * * The first 4 are shown initially. The rest serve as a reserve * to fill gaps as the user installs plugins from the list. * * @return array */ private function define_plugins() { return array( // Initial 4. 'all-in-one-seo-pack' => array( 'name' => __( 'AIOSEO', 'wpconsent-cookies-banner-privacy-suite' ), 'description' => __( 'Powerful SEO plugin to optimize your site, boost your rankings, and increase organic traffic.', 'wpconsent-cookies-banner-privacy-suite' ), 'slug' => 'all-in-one-seo-pack/all_in_one_seo_pack.php', 'pro_slug' => 'all-in-one-seo-pack-pro/all_in_one_seo_pack.php', 'icon' => 'icon-all-in-one-seo-pack.png', ), 'duplicator' => array( 'name' => __( 'Duplicator', 'wpconsent-cookies-banner-privacy-suite' ), 'description' => __( 'Simple way to move WordPress sites, create reliable backups, or clone a site for staging.', 'wpconsent-cookies-banner-privacy-suite' ), 'slug' => 'duplicator/duplicator.php', 'pro_slug' => 'duplicator-pro/duplicator-pro.php', 'icon' => 'icon-duplicator.png', ), 'search-replace-wpcode' => array( 'name' => __( 'Search & Replace Everything', 'wpconsent-cookies-banner-privacy-suite' ), 'description' => __( 'Search and replace text, media, and URLs across your entire WordPress database with ease.', 'wpconsent-cookies-banner-privacy-suite' ), 'slug' => 'search-replace-wpcode/wsrw.php', 'pro_slug' => 'search-replace-wpcode-pro/wsrw-premium.php', 'icon' => 'icon-search-replace-wpcode.png', ), 'insert-headers-and-footers' => array( 'name' => __( 'WPCode', 'wpconsent-cookies-banner-privacy-suite' ), 'description' => __( 'The most popular code snippets plugin for WordPress, used by over 3 million websites.', 'wpconsent-cookies-banner-privacy-suite' ), 'slug' => 'insert-headers-and-footers/ihaf.php', 'pro_slug' => 'wpcode-premium/wpcode.php', 'icon' => 'icon-wpcode.svg', ), // Reserve. 'uncanny-automator' => array( 'name' => __( 'Uncanny Automator', 'wpconsent-cookies-banner-privacy-suite' ), 'description' => __( 'The easiest and most powerful way to automate your WordPress site with no code.', 'wpconsent-cookies-banner-privacy-suite' ), 'slug' => 'uncanny-automator/uncanny-automator.php', 'pro_slug' => 'uncanny-automator-pro/uncanny-automator-pro.php', 'icon' => 'icon-uncanny-automator.png', ), 'wp-mail-smtp' => array( 'name' => __( 'WP Mail SMTP', 'wpconsent-cookies-banner-privacy-suite' ), 'description' => __( 'Fix email deliverability issues by reconfiguring WordPress to use a proper mailer.', 'wpconsent-cookies-banner-privacy-suite' ), 'slug' => 'wp-mail-smtp/wp_mail_smtp.php', 'pro_slug' => 'wp-mail-smtp-pro/wp_mail_smtp.php', 'icon' => 'icon-wp-mail-smtp.png', ), 'coming-soon' => array( 'name' => __( 'SeedProd', 'wpconsent-cookies-banner-privacy-suite' ), 'description' => __( 'Drag-and-drop WordPress builder for landing pages, coming soon, and maintenance mode pages.', 'wpconsent-cookies-banner-privacy-suite' ), 'slug' => 'coming-soon/coming-soon.php', 'pro_slug' => 'seedprod-coming-soon-pro-5/seedprod-coming-soon-pro-5.php', 'icon' => 'icon-seedprod.png', ), 'reviews-feed' => array( 'name' => __( 'Reviews Feed', 'wpconsent-cookies-banner-privacy-suite' ), 'description' => __( 'Display Google, Yelp, and Facebook reviews on your site to build trust and drive more sales.', 'wpconsent-cookies-banner-privacy-suite' ), 'slug' => 'reviews-feed/sb-reviews.php', 'pro_slug' => 'reviews-feed-pro/sb-reviews-pro.php', 'icon' => 'icon-reviews-feed.png', ), ); } /** * Get the list of recommended plugins to display, each tagged with `is_installed`. * * @return array */ public function get_recommended() { if ( ! function_exists( 'get_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $installed_map = array_flip( array_keys( get_plugins() ) ); $all_plugins = $this->get_all_plugins(); foreach ( $all_plugins as $key => $plugin ) { $all_plugins[ $key ]['is_installed'] = isset( $installed_map[ $plugin['slug'] ] ) || isset( $installed_map[ $plugin['pro_slug'] ] ); } $result = array(); foreach ( $this->get_or_rotate_visible_set( $all_plugins ) as $key ) { $result[ $key ] = $all_plugins[ $key ]; } return $result; } /** * Return the stored visible set, computing the initial pick or rotating when due. * * @param array $all_plugins Plugin definitions with `is_installed` flags. * * @return string[] Ordered list of plugin keys. */ private function get_or_rotate_visible_set( $all_plugins ) { $activated = get_option( 'wpconsent_activated', array() ); $visible = isset( $activated['recommended_visible'] ) ? $activated['recommended_visible'] : array(); if ( empty( $visible ) ) { $visible = $this->compute_initial_set( $all_plugins ); $activated['recommended_visible'] = $visible; update_option( 'wpconsent_activated', $activated ); return $visible; } $last_install = isset( $activated['recommended_last_widget_install'] ) ? (int) $activated['recommended_last_widget_install'] : 0; if ( ! $last_install || ( time() - $last_install ) < 14 * DAY_IN_SECONDS ) { return $visible; } $rotated = $this->rotate_visible_set( $visible, $all_plugins ); $activated['recommended_visible'] = $rotated; unset( $activated['recommended_last_widget_install'] ); update_option( 'wpconsent_activated', $activated ); return $rotated; } /** * Compute the initial visible set: 4 non-installed first, installed fill the rest. * * @param array $all_plugins Plugin definitions with `is_installed` flags. * * @return string[] Ordered list of plugin keys. */ private function compute_initial_set( $all_plugins ) { $display_count = 4; $non_installed = array(); $already_installed = array(); foreach ( $all_plugins as $key => $plugin ) { if ( ! empty( $plugin['is_installed'] ) ) { $already_installed[] = $key; } else { $non_installed[] = $key; } } $keys = array_slice( $non_installed, 0, $display_count ); if ( count( $keys ) < $display_count ) { $keys = array_merge( $keys, array_slice( $already_installed, 0, $display_count - count( $keys ) ) ); } return $keys; } /** * Swap installed slots in the visible set for non-installed reserves not yet shown. * * @param string[] $visible Current visible plugin keys. * @param array $all_plugins Plugin definitions with `is_installed` flags. * * @return string[] Rotated visible set (unchanged if no reserves exist). */ private function rotate_visible_set( $visible, $all_plugins ) { $reserve = array(); foreach ( $all_plugins as $key => $plugin ) { if ( empty( $plugin['is_installed'] ) && ! in_array( $key, $visible, true ) ) { $reserve[] = $key; } } if ( empty( $reserve ) ) { return $visible; } $rotated = array(); foreach ( $visible as $key ) { if ( ! empty( $all_plugins[ $key ]['is_installed'] ) && ! empty( $reserve ) ) { $rotated[] = array_shift( $reserve ); } else { $rotated[] = $key; } } return $rotated; } /** * Stamp the current time to (re)start the 14-day rotation timer. * * @return void */ private function track_widget_install() { $activated = get_option( 'wpconsent_activated', array() ); $activated['recommended_last_widget_install'] = time(); update_option( 'wpconsent_activated', $activated ); } /** * Get the icon URL for a plugin image. * * @param string $icon_filename The icon filename. * * @return string */ private function get_icon_url( $icon_filename ) { return WPCONSENT_PLUGIN_URL . 'admin/images/' . $icon_filename; } /** * Render the recommended plugins dashboard widget. * * Outputs nothing if there are no plugins to recommend. * * @return void */ public function recommended_plugins_widget() { $plugins = $this->get_recommended(); if ( empty( $plugins ) ) { return; } ?>
$activated->get_error_message() ) ); } $this->track_widget_install(); wp_send_json_success( array( 'message' => esc_html__( 'Plugin activated.', 'wpconsent-cookies-banner-privacy-suite' ), ) ); } /** * AJAX handler: install and activate a recommended plugin. * * Validates the slug against the allowlist, then tries (in order): * 1. Already active — return success immediately. * 2. Pro version installed but inactive — activate it. * 3. Lite version installed but inactive — activate it. * 4. Not installed — download from wordpress.org and activate. * * @return void */ public function ajax_install_plugin() { check_ajax_referer( 'wpconsent_admin' ); if ( ! current_user_can( 'install_plugins' ) || ! current_user_can( 'activate_plugins' ) ) { wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to install plugins. Please ask your site administrator to install it for you.', 'wpconsent-cookies-banner-privacy-suite' ), ) ); } if ( ! wp_is_file_mod_allowed( 'install_plugins' ) ) { wp_send_json_error( array( 'message' => esc_html__( 'Plugin installation is not allowed on this site. Please ask your site administrator to install it for you.', 'wpconsent-cookies-banner-privacy-suite' ), ) ); } $slug = isset( $_POST['slug'] ) ? sanitize_key( wp_unslash( $_POST['slug'] ) ) : ''; if ( ! array_key_exists( $slug, $this->get_all_plugins() ) ) { wp_send_json_error( array( 'message' => esc_html__( 'Invalid plugin.', 'wpconsent-cookies-banner-privacy-suite' ), ) ); } $plugin_info = $this->get_all_plugins()[ $slug ]; require_once ABSPATH . 'wp-admin/includes/file.php'; require_once ABSPATH . 'wp-admin/includes/template.php'; require_once ABSPATH . 'wp-admin/includes/class-wp-screen.php'; require_once ABSPATH . 'wp-admin/includes/screen.php'; require_once ABSPATH . 'wp-admin/includes/plugin.php'; // Set the current screen to avoid undefined notices. set_current_screen( 'toplevel_page_wpconsent' ); // Do not allow WordPress to search/download translations, as this will break JS output. remove_action( 'upgrader_process_complete', array( 'Language_Pack_Upgrader', 'async_upgrade' ), 20 ); // Already active (either version). if ( is_plugin_active( $plugin_info['slug'] ) || is_plugin_active( $plugin_info['pro_slug'] ) ) { $this->track_widget_install(); wp_send_json_success( array( 'message' => esc_html__( 'Plugin already active.', 'wpconsent-cookies-banner-privacy-suite' ), ) ); } $installed_plugins = array_keys( get_plugins() ); // Pro version installed but not active — activate it. if ( in_array( $plugin_info['pro_slug'], $installed_plugins, true ) ) { $this->activate_and_respond( $plugin_info['pro_slug'] ); } // Lite version installed but not active — activate it. if ( in_array( $plugin_info['slug'], $installed_plugins, true ) ) { $this->activate_and_respond( $plugin_info['slug'] ); } // Not installed — download from wordpress.org and activate. wpconsent_require_upgrader(); $installer = new Plugin_Upgrader( new WPConsent_Skin() ); $installer->install( 'https://downloads.wordpress.org/plugin/' . $slug . '.zip' ); wp_cache_delete( 'plugins', 'plugins' ); $plugin_basename = $installer->plugin_info(); if ( ! $plugin_basename ) { wp_send_json_error( array( 'message' => esc_html__( 'Plugin installation failed. Please try again.', 'wpconsent-cookies-banner-privacy-suite' ), ) ); } $activated = activate_plugin( $plugin_basename ); if ( is_wp_error( $activated ) ) { wp_send_json_error( array( 'message' => esc_html__( 'Plugin installed but could not be activated.', 'wpconsent-cookies-banner-privacy-suite' ), ) ); } $this->track_widget_install(); wp_send_json_success( array( 'message' => esc_html__( 'Plugin installed and activated.', 'wpconsent-cookies-banner-privacy-suite' ), ) ); } }