PluginProbe
MainWP Dashboard: Self-hosted WordPress Management for Agencies / 6.2.2
MainWP Dashboard: Self-hosted WordPress Management for Agencies v6.2.2
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 | class/class-mainwp-post-extension-handler.php +482 -368 5.0.3 → 6.2.2 View file →
@@ -7,8 +7,13 @@
7 7 */
8 8
9 9 namespace MainWP\Dashboard;
10 10
11 +// Exit if accessed directly.
12 +if ( ! defined( 'ABSPATH' ) ) {
13 + exit;
14 +}
15 +
11 16 /**
12 17 * Class MainWP_Post_Extension_Handler
13 18 *
14 19 * @package MainWP\Dashboard
@@ -14,422 +19,531 @@
14 19 * @package MainWP\Dashboard
15 20 *
16 21 * @uses \MainWP\Dashboard\MainWP_Post_Base_Handler
17 22 */
18 -class MainWP_Post_Extension_Handler extends MainWP_Post_Base_Handler {
23 +class MainWP_Post_Extension_Handler extends MainWP_Post_Base_Handler { // phpcs:ignore Generic.Classes.OpeningBraceSameLine.ContentAfterBrace -- NOSONAR.
19 24
20 - /**
21 - * Public static varibale to hold the instance.
22 - *
23 - * @var null Default value.
24 - */
25 - private static $instance = null;
25 + /**
26 + * Public static varibale to hold the instance.
27 + *
28 + * @var null Default value.
29 + */
30 + private static $instance = null;
26 31
27 - /**
28 - * Method instance()
29 - *
30 - * Create public static instance.
31 - *
32 - * @return self $instance.
33 - */
34 - public static function instance() {
35 - if ( null === self::$instance ) {
36 - self::$instance = new self();
37 - }
38 - return self::$instance;
39 - }
32 + /**
33 + * Method instance()
34 + *
35 + * Create public static instance.
36 + *
37 + * @return self $instance.
38 + */
39 + public static function instance() {
40 + if ( null === static::$instance ) {
41 + static::$instance = new self();
42 + }
43 + return static::$instance;
44 + }
40 45
41 - /**
42 - * Init extensions actions
43 - *
44 - * @uses \MainWP\Dashboard\MainWP_Extensions::get_class_name()
45 - */
46 - public function init() {
46 + /**
47 + * Init extensions actions
48 + *
49 + * @uses \MainWP\Dashboard\MainWP_Extensions::get_class_name()
50 + */
51 + public function init() {
47 52
48 - $this->add_action( 'mainwp_extension_add_menu', array( &$this, 'add_extension_menu' ) );
49 - $this->add_action( 'mainwp_extension_remove_menu', array( &$this, 'remove_extension_menu_from_mainwp_menu' ) );
53 + $this->add_action( 'mainwp_extension_add_menu', array( &$this, 'add_extension_menu' ) );
54 + $this->add_action( 'mainwp_extension_remove_menu', array( &$this, 'remove_extension_menu_from_mainwp_menu' ) );
50 55
51 - $this->add_action( 'mainwp_extension_api_activate', array( &$this, 'activate_api_extension' ) );
52 - $this->add_action( 'mainwp_extension_deactivate', array( &$this, 'deactivate_extension' ) );
53 - $this->add_action( 'mainwp_extension_testextensionapilogin', array( &$this, 'test_extensions_api_login' ) );
56 + $this->add_action( 'mainwp_extension_api_activate', array( &$this, 'activate_api_extension' ) );
57 + $this->add_action( 'mainwp_extension_deactivate', array( &$this, 'deactivate_extension' ) );
58 + $this->add_action( 'mainwp_extension_testextensionapilogin', array( &$this, 'test_extensions_api_login' ) );
54 59
55 - $this->add_action( 'mainwp_extension_plugin_action', array( &$this, 'ajax_extension_plugin_action' ) );
60 + $this->add_action( 'mainwp_extension_plugin_action', array( &$this, 'ajax_extension_plugin_action' ) );
56 61
57 - if ( mainwp_current_user_have_right( 'dashboard', 'bulk_install_and_activate_extensions' ) ) {
58 - $this->add_action( 'mainwp_extension_grabapikey', array( &$this, 'grab_extension_api_key' ) );
59 - $this->add_action( 'mainwp_extension_saveextensionapilogin', array( &$this, 'save_extensions_api_login' ) );
60 - $this->add_action( 'mainwp_extension_getpurchased', array( MainWP_Extensions::get_class_name(), 'ajax_get_purchased_extensions' ) );
61 - $this->add_action( 'mainwp_extension_downloadandinstall', array( &$this, 'download_and_install' ) );
62 - $this->add_action( 'mainwp_extension_bulk_activate', array( &$this, 'bulk_activate' ) );
63 - $this->add_action( 'mainwp_extension_apisslverifycertificate', array( &$this, 'save_api_ssl_verify' ) );
64 - }
62 + if ( \mainwp_current_user_can( 'dashboard', 'manage_extensions' ) ) {
63 + $this->add_action( 'mainwp_extension_update_check_retry', array( &$this, 'retry_extension_update_check' ) );
64 + }
65 65
66 - // Page: ManageSites.
67 - $this->add_action( 'mainwp_ext_applypluginsettings', array( &$this, 'mainwp_ext_applypluginsettings' ) );
68 - }
66 + if ( \mainwp_current_user_can( 'dashboard', 'bulk_install_and_activate_extensions' ) ) {
67 + $this->add_action( 'mainwp_extension_grabapikey', array( &$this, 'grab_extension_api_key' ) );
68 + $this->add_action( 'mainwp_extension_saveextensionapilogin', array( &$this, 'save_extensions_api_login' ) );
69 + $this->add_action( 'mainwp_extension_getpurchased', array( MainWP_Extensions::get_class_name(), 'ajax_get_purchased_extensions' ) );
70 + $this->add_action( 'mainwp_extension_downloadandinstall', array( &$this, 'download_and_install' ) );
71 + $this->add_action( 'mainwp_extension_bulk_activate', array( &$this, 'bulk_activate' ) );
72 + $this->add_action( 'mainwp_extension_apisslverifycertificate', array( &$this, 'save_api_ssl_verify' ) );
73 + }
69 74
70 - /**
71 - * Apply plugin settings.
72 - *
73 - * @return mixed success|error.
74 - *
75 - * @uses \MainWP\Dashboard\MainWP_Manage_Sites_Handler::apply_plugin_settings()
76 - */
77 - public function mainwp_ext_applypluginsettings() {
78 - $this->check_security( 'mainwp_ext_applypluginsettings' );
79 - MainWP_Manage_Sites_Handler::apply_plugin_settings();
80 - }
75 + // Page: ManageSites.
76 + $this->add_action( 'mainwp_ext_applypluginsettings', array( &$this, 'mainwp_ext_applypluginsettings' ) );
77 + }
81 78
82 - /**
83 - * Ajax add extension menu.
84 - *
85 - * @return void
86 - *
87 - * @uses \MainWP\Dashboard\MainWP_Extensions_Handler::add_extension_menu()
88 - */
89 - public function add_extension_menu() {
90 - $this->check_security( 'mainwp_extension_add_menu' );
91 - $slug = isset( $_POST['slug'] ) ? wp_unslash( $_POST['slug'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
92 - MainWP_Extensions_Handler::add_extension_menu( $slug );
93 - die( wp_json_encode( array( 'result' => 'SUCCESS' ) ) );
94 - }
79 + /**
80 + * Retry Add-on update checks.
81 + */
82 + public function retry_extension_update_check() {
83 + $this->check_security( 'mainwp_extension_update_check_retry' );
95 84
96 - /**
97 - * Activate MainWP Extension.
98 - *
99 - * @return void
100 - *
101 - * @uses \MainWP\Dashboard\MainWP_Api_Manager::license_key_activation()
102 - * @uses \MainWP\Dashboard\MainWP_Deprecated_Hooks::maybe_handle_deprecated_hook()
103 - */
104 - public function activate_api_extension() {
105 - $this->check_security( 'mainwp_extension_api_activate' );
106 - MainWP_Deprecated_Hooks::maybe_handle_deprecated_hook();
107 - $api_slug = isset( $_POST['slug'] ) ? dirname( wp_unslash( $_POST['slug'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
108 - $api_key = isset( $_POST['key'] ) ? sanitize_text_field( wp_unslash( $_POST['key'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
109 - $result = MainWP_Api_Manager::instance()->license_key_activation( $api_slug, $api_key );
110 - wp_send_json( $result );
111 - }
85 + if ( ! \mainwp_current_user_can( 'dashboard', 'manage_extensions' ) ) {
86 + wp_send_json( array( 'error' => esc_html__( 'You are not allowed to manage Add-ons.', 'mainwp' ) ) );
87 + }
112 88
89 + if ( MainWP_System_Handler::instance()->retry_extension_update_check() ) {
90 + wp_send_json( array( 'result' => 'SUCCESS' ) );
91 + }
113 92
114 - /**
115 - * Handle MainWP Extension plugin actions.
116 - *
117 - * @return void
118 - */
119 - public function ajax_extension_plugin_action() {
120 - $this->check_security( 'mainwp_extension_plugin_action' );
121 - $plugin_slug = isset( $_POST['slug'] ) ? sanitize_text_field( wp_unslash( $_POST['slug'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
122 - $action = isset( $_POST['what'] ) ? sanitize_text_field( wp_unslash( $_POST['what'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
123 - if ( ! empty( $plugin_slug ) && in_array( $action, array( 'active', 'disable', 'remove' ) ) ) {
124 - if ( 'disable' === $action ) {
125 - if ( is_plugin_active( $plugin_slug ) ) {
126 - deactivate_plugins( $plugin_slug, false );
127 - }
128 - wp_send_json( array( 'result' => 'SUCCESS' ) );
129 - } elseif ( 'active' === $action ) {
130 - if ( ! is_plugin_active( $plugin_slug ) ) {
131 - activate_plugin( $plugin_slug, '', false, false );
132 - }
133 - wp_send_json( array( 'result' => 'SUCCESS' ) );
93 + wp_send_json( array( 'error' => esc_html__( 'The update check is still failing. Make sure your MainWP Dashboard can connect to mainwp.com, then try again.', 'mainwp' ) ) );
94 + }
134 95
135 - } elseif ( 'remove' === $action ) {
136 - $status = $this->delete_extension_plugin( $plugin_slug );
137 - wp_send_json( $status );
138 - }
139 - }
140 - wp_send_json( array( 'error' => esc_html__( 'Invalid data provided.', 'mainwp' ) ) );
141 - }
96 + /**
97 + * Apply plugin settings.
98 + *
99 + * @return mixed success|error.
100 + *
101 + * @uses \MainWP\Dashboard\MainWP_Manage_Sites_Handler::apply_plugin_settings()
102 + */
103 + public function mainwp_ext_applypluginsettings() {
104 + $this->check_security( 'mainwp_ext_applypluginsettings' );
105 + MainWP_Manage_Sites_Handler::apply_plugin_settings();
106 + }
142 107
143 - /**
144 - * Delete MainWP Extension plugin.
145 - *
146 - * @param string $plugin_slug plugin slug.
147 - *
148 - * @return array $status Status result.
149 - */
150 - public function delete_extension_plugin( $plugin_slug ) {
108 + /**
109 + * Ajax add extension menu.
110 + *
111 + * @return void
112 + *
113 + * @uses \MainWP\Dashboard\MainWP_Extensions_Handler::add_extension_menu()
114 + */
115 + public function add_extension_menu() {
116 + $this->check_security( 'mainwp_extension_add_menu' );
117 + $slug = isset( $_POST['slug'] ) ? wp_unslash( $_POST['slug'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
118 + MainWP_Extensions_Handler::add_extension_menu( $slug );
119 + die( wp_json_encode( array( 'result' => 'SUCCESS' ) ) );
120 + }
151 121
152 - $status = array();
153 - if ( ! empty( $plugin_slug ) ) {
154 - // Check filesystem credentials.
155 - $url = wp_nonce_url( 'plugins.php?action=delete-selected&verify-delete=1&checked[]=' . $plugin_slug, 'bulk-plugins' );
156 - ob_start();
157 - $credentials = request_filesystem_credentials( $url );
158 - ob_end_clean();
122 + /**
123 + * Activate MainWP Extension.
124 + *
125 + * @return void
126 + *
127 + * @uses \MainWP\Dashboard\MainWP_Api_Manager::license_key_activation()
128 + * @uses \MainWP\Dashboard\MainWP_Deprecated_Hooks::maybe_handle_deprecated_hook()
129 + */
130 + public function activate_api_extension() {
131 + $this->check_security( 'mainwp_extension_api_activate' );
132 + MainWP_Deprecated_Hooks::maybe_handle_deprecated_hook();
133 + $api_slug = isset( $_POST['slug'] ) ? dirname( wp_unslash( $_POST['slug'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
134 + $api_key = isset( $_POST['key'] ) ? sanitize_text_field( wp_unslash( $_POST['key'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
135 + $result = MainWP_Api_Manager::instance()->license_key_activation( $api_slug, $api_key );
136 + wp_send_json( $result );
137 + }
159 138
160 - if ( false === $credentials || ! WP_Filesystem( $credentials ) ) {
161 - global $wp_filesystem;
162 - $status['error'] = esc_html__( 'Unable to connect to the filesystem. Please confirm your credentials.', 'mainwp' );
163 - // Pass through the error from WP_Filesystem if one was raised.
164 - if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
165 - $status['error'] = esc_html( $wp_filesystem->errors->get_error_message() );
166 - }
167 - return $status;
168 - }
169 139
170 - $result = delete_plugins( array( $plugin_slug ) );
140 + /**
141 + * Handle MainWP Extension plugin actions.
142 + *
143 + * @return void
144 + */
145 + public function ajax_extension_plugin_action() {
146 + $this->check_security( 'mainwp_extension_plugin_action' );
147 + $plugin_slug = isset( $_POST['slug'] ) ? sanitize_text_field( wp_unslash( $_POST['slug'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
148 + $action = isset( $_POST['what'] ) ? sanitize_text_field( wp_unslash( $_POST['what'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
149 + if ( ! empty( $plugin_slug ) && in_array( $action, array( 'active', 'disable', 'remove' ) ) ) {
150 + $this->invalidate_warm_cache();
151 + if ( 'disable' === $action ) {
152 + if ( is_plugin_active( $plugin_slug ) ) {
153 + deactivate_plugins( $plugin_slug, false );
154 + }
155 + wp_send_json( array( 'result' => 'SUCCESS' ) );
156 + } elseif ( 'active' === $action ) {
157 + if ( ! is_plugin_active( $plugin_slug ) ) {
158 + activate_plugin( $plugin_slug, '', false, false );
159 + }
160 + wp_send_json( array( 'result' => 'SUCCESS' ) );
171 161
172 - if ( is_wp_error( $result ) ) {
173 - $status['error'] = $result->get_error_message();
174 - return $status;
175 - } elseif ( false === $result ) {
176 - $status['error'] = esc_html__( 'Plugin could not be deleted.', 'mainwp' );
177 - return $status;
178 - }
179 - $status['result'] = 'SUCCESS';
162 + } elseif ( 'remove' === $action ) {
163 + $status = $this->delete_extension_plugin( $plugin_slug );
164 + wp_send_json( $status );
165 + }
166 + }
167 + wp_send_json( array( 'error' => esc_html__( 'Invalid data provided.', 'mainwp' ) ) );
168 + }
180 169
181 - return $status;
182 - }
183 - return $status;
184 - }
185 170
171 + /**
172 + * Method invalidate_warm_cache()
173 + */
174 + public function invalidate_warm_cache() {
175 + MainWP_Cache_Warm_Helper::invalidate_manage_pages( array( 'Extensions' ) );
176 + }
186 177
187 - /**
188 - * Deactivate MainWP Extension.
189 - *
190 - * @return void
191 - *
192 - * @uses \MainWP\Dashboard\MainWP_Api_Manager::license_key_deactivation()
193 - * @uses \MainWP\Dashboard\MainWP_Deprecated_Hooks::maybe_handle_deprecated_hook()
194 - */
195 - public function deactivate_extension() {
196 - $this->check_security( 'mainwp_extension_deactivate' );
197 - $api_slug = isset( $_POST['slug'] ) ? dirname( wp_unslash( $_POST['slug'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
198 - $api_key = isset( $_POST['api_key'] ) ? wp_unslash( $_POST['api_key'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
199 - $result = MainWP_Api_Manager::instance()->license_key_deactivation( $api_slug, $api_key );
200 - wp_send_json( $result );
201 - }
178 + /**
179 + * Delete MainWP Extension plugin.
180 + *
181 + * @param string $plugin_slug plugin slug.
182 + *
183 + * @return array $status Status result.
184 + */
185 + public function delete_extension_plugin( $plugin_slug ) {
202 186
203 - /**
204 - * Grab MainWP Extension API Key.
205 - *
206 - * @return void
207 - *
208 - * @uses \MainWP\Dashboard\MainWP_Api_Manager::grab_license_key()
209 - */
210 - public function grab_extension_api_key() {
211 - $this->check_security( 'mainwp_extension_grabapikey' );
212 - $api_slug = isset( $_POST['slug'] ) ? dirname( wp_unslash( $_POST['slug'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
213 - $master_api_key = isset( $_POST['master_api_key'] ) ? wp_unslash( $_POST['master_api_key'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
214 - $result = MainWP_Api_Manager::instance()->grab_license_key( $api_slug, $master_api_key );
215 - wp_send_json( $result );
216 - }
187 + $status = array();
188 + if ( ! empty( $plugin_slug ) ) {
189 + // Check filesystem credentials.
190 + $url = wp_nonce_url( 'plugins.php?action=delete-selected&verify-delete=1&checked[]=' . $plugin_slug, 'bulk-plugins' );
191 + ob_start();
192 + $credentials = request_filesystem_credentials( $url );
193 + ob_end_clean();
217 194
218 - /**
219 - * Save MainWP Extensions API Login details for future logins.
220 - *
221 - * @return void
222 - *
223 - * @uses \MainWP\Dashboard\MainWP_Api_Manager::verify_mainwp_api()
224 - * @uses \MainWP\Dashboard\MainWP_Api_Manager_Password_Management::encrypt_string()
225 - * @uses \MainWP\Dashboard\MainWP_Utility::update_option()
226 - */
227 - public function save_extensions_api_login() { // phpcs:ignore -- Current complexity is the only way to achieve desired results, pull request solutions appreciated.
228 - $this->check_security( 'mainwp_extension_saveextensionapilogin' );
229 - $api_login_history = isset( $_SESSION['api_login_history'] ) ? $_SESSION['api_login_history'] : array(); //phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- ok.
195 + if ( false === $credentials || ! WP_Filesystem( $credentials ) ) {
196 + global $wp_filesystem;
197 + $status['error'] = esc_html__( 'Unable to connect to the filesystem. Please confirm your credentials.', 'mainwp' );
198 + // Pass through the error from WP_Filesystem if one was raised.
199 + if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
200 + $status['error'] = esc_html( $wp_filesystem->errors->get_error_message() );
201 + }
202 + return $status;
203 + }
230 204
231 - $new_api_login_history = array();
232 - $requests = 0;
205 + $result = delete_plugins( array( $plugin_slug ) );
233 206
234 - foreach ( $api_login_history as $api_login ) {
235 - if ( $api_login['time'] > ( time() - 1 * 60 ) ) {
236 - $new_api_login_history[] = $api_login;
237 - ++$requests;
238 - }
239 - }
207 + if ( is_wp_error( $result ) ) {
208 + $status['error'] = $result->get_error_message();
209 + return $status;
210 + } elseif ( false === $result ) {
211 + $status['error'] = esc_html__( 'Plugin could not be deleted.', 'mainwp' );
212 + return $status;
213 + }
214 + $status['result'] = 'SUCCESS';
240 215
241 - if ( 4 < $requests ) {
242 - $_SESSION['api_login_history'] = $new_api_login_history;
243 - die( wp_json_encode( array( 'error' => esc_html__( 'Too many requests', 'mainwp' ) ) ) );
244 - } else {
245 - $new_api_login_history[] = array( 'time' => time() );
246 - $_SESSION['api_login_history'] = $new_api_login_history;
247 - }
216 + return $status;
217 + }
218 + return $status;
219 + }
248 220
249 - $api_key = isset( $_POST['api_key'] ) ? trim( wp_unslash( $_POST['api_key'] ) ) : false; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
250 221
251 - if ( '' === $api_key && false !== $api_key ) {
252 - MainWP_Keys_Manager::instance()->update_key_value( 'mainwp_extensions_master_api_key', false );
253 - }
222 + /**
223 + * Deactivate MainWP Extension.
224 + *
225 + * @return void
226 + *
227 + * @uses \MainWP\Dashboard\MainWP_Api_Manager::license_key_deactivation()
228 + * @uses \MainWP\Dashboard\MainWP_Deprecated_Hooks::maybe_handle_deprecated_hook()
229 + */
230 + public function deactivate_extension() {
231 + $this->check_security( 'mainwp_extension_deactivate' );
232 + $api_slug = isset( $_POST['slug'] ) ? dirname( wp_unslash( $_POST['slug'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
233 + $api_key = isset( $_POST['api_key'] ) ? wp_unslash( $_POST['api_key'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
234 + // MWP-1546: the hidden input on the Extensions card now renders the
235 + // sentinel placeholder instead of the plaintext per-extension license
236 + // key. Resolve the sentinel server-side using the slug so the
237 + // deactivation request gets the real key from the (now-encrypted)
238 + // per-slug option rather than forwarding the placeholder.
239 + //
240 + // CR follow-up: if the stored key cannot be recovered (option
241 + // missing, decrypt failed, etc.), bail with an error rather
242 + // than forwarding an empty key. license_key_deactivation()
243 + // takes its local-clear fast path on empty $api_key and
244 + // returns SUCCESS without ever calling the licensing API,
245 + // which would silently leave the activation slot allocated
246 + // upstream while telling the operator everything is fine.
247 + if ( MainWP_Credential_Render::is_sentinel( $api_key ) ) {
248 + $info = MainWP_Api_Manager::instance()->get_activation_info( $api_slug );
249 + if ( ! is_array( $info ) || empty( $info['api_key'] ) || ! is_string( $info['api_key'] ) ) {
250 + wp_send_json(
251 + array(
252 + 'error' => esc_html__( 'The stored license key could not be recovered. Please re-enter it before deactivating.', 'mainwp' ),
253 + )
254 + );
255 + return;
256 + }
257 + $api_key = $info['api_key'];
258 + }
259 + // MWP-1546 follow-up: reject empty / non-string submissions outside
260 + // the sentinel path. Without this guard a direct API hit with an
261 + // empty api_key would fall through to license_key_deactivation, which
262 + // takes its local-clear fast path and returns SUCCESS without ever
263 + // contacting the licensing API -- silently leaving the activation
264 + // slot allocated upstream. Same failure mode the sentinel-recovery
265 + // branch above closes; close it here too.
266 + if ( ! is_string( $api_key ) || '' === $api_key ) {
267 + wp_send_json(
268 + array(
269 + 'error' => esc_html__( 'A license key is required to deactivate.', 'mainwp' ),
270 + )
271 + );
272 + return;
273 + }
274 + $result = MainWP_Api_Manager::instance()->license_key_deactivation( $api_slug, $api_key );
275 + wp_send_json( $result );
276 + }
254 277
255 - if ( empty( $api_key ) ) {
256 - die( wp_json_encode( array( 'saved' => 1 ) ) );
257 - }
278 + /**
279 + * Grab MainWP Extension API Key.
280 + *
281 + * @return void
282 + *
283 + * @uses \MainWP\Dashboard\MainWP_Api_Manager::grab_license_key()
284 + */
285 + public function grab_extension_api_key() {
286 + $this->check_security( 'mainwp_extension_grabapikey' );
287 + $api_slug = isset( $_POST['slug'] ) ? dirname( wp_unslash( $_POST['slug'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
288 + $master_api_key = isset( $_POST['master_api_key'] ) ? wp_unslash( $_POST['master_api_key'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
289 + // MWP-1547: the Extensions page renders the sentinel placeholder
290 + // when a master key is already stored. The browser-side AJAX call
291 + // reads #mainwp_com_api_key and submits whatever is there, so a
292 + // remembered-license dashboard sends the sentinel here. Resolve to
293 + // the stored decrypted master key server-side instead of forwarding
294 + // the placeholder to mainwp.com.
295 + if ( MainWP_Credential_Render::is_sentinel( $master_api_key ) ) {
296 + $master_api_key = MainWP_Api_Manager_Key::instance()->get_decrypt_master_api_key();
297 + }
298 + $result = MainWP_Api_Manager::instance()->grab_license_key( $api_slug, $master_api_key );
299 + wp_send_json( $result );
300 + }
258 301
259 - $result = array();
260 - try {
261 - $test = MainWP_Api_Manager::instance()->verify_mainwp_api( $api_key );
262 - } catch ( \Exception $e ) {
263 - $return['error'] = $e->getMessage();
264 - die( wp_json_encode( $return ) );
265 - }
302 + /**
303 + * Save MainWP Extensions API Login details for future logins.
304 + *
305 + * @return void
306 + *
307 + * @uses \MainWP\Dashboard\MainWP_Api_Manager::verify_mainwp_api()
308 + * @uses \MainWP\Dashboard\MainWP_Api_Manager_Password_Management::encrypt_string()
309 + * @uses \MainWP\Dashboard\MainWP_Utility::update_option()
310 + */
311 + public function save_extensions_api_login() { // phpcs:ignore -- NOSONAR -Current complexity is the only way to achieve desired results, pull request solutions appreciated.
312 + $this->check_security( 'mainwp_extension_saveextensionapilogin' );
313 + $api_login_history = isset( $_SESSION['api_login_history'] ) ? $_SESSION['api_login_history'] : array(); //phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- ok.
266 314
267 - if ( is_array( $test ) && isset( $test['retry_action'] ) ) {
268 - wp_send_json( $test );
269 - }
315 + $new_api_login_history = array();
316 + $requests = 0;
270 317
271 - $result = json_decode( $test, true );
272 - $save_login = ( isset( $_POST['saveLogin'] ) && ( 1 === (int) $_POST['saveLogin'] ) ) ? true : false; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
273 - $return = array();
274 - if ( is_array( $result ) ) {
275 - if ( isset( $result['success'] ) && $result['success'] ) {
276 - if ( $save_login ) {
277 - if ( empty( $api_key ) && isset( $result['master_api_key'] ) ) {
278 - $api_key = $result['master_api_key'];
279 - }
280 - MainWP_Keys_Manager::instance()->update_key_value( 'mainwp_extensions_master_api_key', $api_key );
281 - MainWP_Utility::update_option( 'mainwp_extensions_api_save_login', true );
282 - $plan_info = isset( $result['plan_info'] ) ? wp_json_encode( $result['plan_info'] ) : '';
283 - MainWP_Utility::update_option( 'mainwp_extensions_plan_info', $plan_info );
284 - }
285 - $return['result'] = 'SUCCESS';
286 - } elseif ( isset( $result['error'] ) ) {
287 - $return['error'] = $result['error'];
288 - }
289 - }
318 + foreach ( $api_login_history as $api_login ) {
319 + if ( $api_login['time'] > ( time() - 1 * 60 ) ) {
320 + $new_api_login_history[] = $api_login;
321 + ++$requests;
322 + }
323 + }
290 324
291 - if ( ! $save_login ) {
292 - MainWP_Utility::update_option( 'mainwp_extensions_api_save_login', '' );
293 - MainWP_Utility::update_option( 'mainwp_extensions_plan_info', '' );
294 - }
325 + if ( 4 < $requests ) {
326 + $_SESSION['api_login_history'] = $new_api_login_history;
327 + die( wp_json_encode( array( 'error' => esc_html__( 'Too many requests', 'mainwp' ) ) ) );
328 + } else {
329 + $new_api_login_history[] = array( 'time' => time() );
330 + $_SESSION['api_login_history'] = $new_api_login_history;
331 + }
295 332
296 - die( wp_json_encode( $return ) );
297 - }
333 + $api_key = isset( $_POST['api_key'] ) ? trim( wp_unslash( $_POST['api_key'] ) ) : false; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
298 334
299 - /**
300 - * Save whenther or not to verify MainWP API SSL certificate.
301 - *
302 - * @return void
303 - *
304 - * @uses \MainWP\Dashboard\MainWP_Utility::update_option()
305 - */
306 - public function save_api_ssl_verify() {
307 - $this->check_security( 'mainwp_extension_apisslverifycertificate' );
308 - MainWP_Utility::update_option( 'mainwp_api_sslVerifyCertificate', isset( $_POST['api_sslverify'] ) ? intval( $_POST['api_sslverify'] ) : 0 ); // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
309 - die( wp_json_encode( array( 'saved' => 1 ) ) );
310 - }
335 + // MWP-1547: the Extensions page now renders the sentinel placeholder
336 + // ('••••••••') in the license-key input when a value is already
337 + // stored. A submission that returns the sentinel unchanged means
338 + // "leave the existing key alone" -- short-circuit before contacting
339 + // mainwp.com for verification (the sentinel is not a real key, so
340 + // the verify call would otherwise produce a misleading error).
341 + //
342 + // CR follow-up: the unchecked-"Remember API Key" flow normally
343 + // reaches the trailing block at the end of this method which clears
344 + // mainwp_extensions_api_save_login + mainwp_extensions_plan_info.
345 + // The sentinel short-circuit must mirror that behaviour so a user
346 + // who unchecks the box (without changing the input) still has the
347 + // flag cleared. The trailing block deliberately does NOT clear the
348 + // master key itself, so the sentinel branch matches that semantic
349 + // -- the only way to delete the stored master key is to submit an
350 + // empty input, which the next branch handles.
351 + if ( MainWP_Credential_Render::is_sentinel( $api_key ) ) {
352 + $save_login = ( isset( $_POST['saveLogin'] ) && ( 1 === (int) $_POST['saveLogin'] ) ) ? true : false; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
353 + if ( ! $save_login ) {
354 + MainWP_Utility::update_option( 'mainwp_extensions_api_save_login', '' );
355 + MainWP_Utility::update_option( 'mainwp_extensions_plan_info', '' );
356 + }
357 + die(
358 + wp_json_encode(
359 + array(
360 + 'saved' => 1,
361 + 'result' => 'SUCCESS',
362 + )
363 + )
364 + );
365 + }
311 366
312 - /**
313 - * Test Extension page MainWP.com login details.
314 - *
315 - * @return void
316 - *
317 - * @uses \MainWP\Dashboard\MainWP_Api_Manager::verify_mainwp_api()
318 - * @uses \MainWP\Dashboard\MainWP_Utility::update_option()
319 - */
320 - public function test_extensions_api_login() {
321 - $this->check_security( 'mainwp_extension_testextensionapilogin' );
322 - $api_key = MainWP_Api_Manager_Key::instance()->get_decrypt_master_api_key();
323 - $result = array();
324 - try {
325 - $test = MainWP_Api_Manager::instance()->verify_mainwp_api( $api_key );
326 - } catch ( \Exception $e ) {
327 - $return['error'] = $e->getMessage();
328 - die( wp_json_encode( $return ) );
329 - }
367 + if ( '' === $api_key && false !== $api_key ) {
368 + MainWP_Keys_Manager::instance()->update_key_value( 'mainwp_extensions_master_api_key', false );
369 + }
330 370
331 - if ( is_array( $test ) && isset( $test['retry_action'] ) ) {
332 - wp_send_json( $test );
333 - }
371 + if ( empty( $api_key ) ) {
372 + die( wp_json_encode( array( 'saved' => 1 ) ) );
373 + }
334 374
335 - $result = json_decode( $test, true );
336 - $return = array();
337 - if ( is_array( $result ) ) {
338 - if ( isset( $result['success'] ) && $result['success'] ) {
339 - $return['result'] = 'SUCCESS';
340 - } elseif ( isset( $result['error'] ) ) {
341 - $return['error'] = $result['error'];
342 - }
343 - } else {
344 - $apisslverify = get_option( 'mainwp_api_sslVerifyCertificate' );
345 - if ( 1 === (int) $apisslverify ) {
346 - MainWP_Utility::update_option( 'mainwp_api_sslVerifyCertificate', 0 );
347 - $return['retry_action'] = 1;
348 - }
349 - }
350 - wp_send_json( $return );
351 - }
375 + $result = array();
376 + try {
377 + $test = MainWP_Api_Manager::instance()->verify_mainwp_api( $api_key );
378 + } catch ( \Exception $e ) {
379 + $return['error'] = $e->getMessage();
380 + die( wp_json_encode( $return ) );
381 + }
352 382
353 - /**
354 - * Download & Install MainWP Extension.
355 - *
356 - * @return void
357 - *
358 - * @uses \MainWP\Dashboard\MainWP_Extensions_Handler::install_plugin()
359 - */
360 - public function download_and_install() {
361 - $this->check_security( 'mainwp_extension_downloadandinstall' );
362 - // phpcs:ignore -- custom setting to install plugin.
363 - ini_set( 'zlib.output_compression', 'Off' );
364 - $download_link = isset( $_POST['download_link'] ) ? wp_unslash( $_POST['download_link'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
365 - $plugin_slug = isset( $_POST['plugin_slug'] ) ? wp_unslash( $_POST['plugin_slug'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
383 + if ( is_array( $test ) && isset( $test['retry_action'] ) ) {
384 + wp_send_json( $test );
385 + }
366 386
367 - $return = array( 'error' => esc_html__( 'Empty or Invalid request data, please try again.', 'mainwp' ) );
387 + $result = json_decode( $test, true );
388 + $save_login = ( isset( $_POST['saveLogin'] ) && ( 1 === (int) $_POST['saveLogin'] ) ) ? true : false; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
389 + $return = array();
390 + if ( is_array( $result ) ) {
391 + if ( isset( $result['success'] ) && $result['success'] ) {
392 + if ( $save_login ) {
393 + if ( empty( $api_key ) && isset( $result['master_api_key'] ) ) {
394 + $api_key = $result['master_api_key'];
395 + }
396 + MainWP_Keys_Manager::instance()->update_key_value( 'mainwp_extensions_master_api_key', $api_key );
397 + MainWP_Utility::update_option( 'mainwp_extensions_api_save_login', true );
398 + $plan_info = isset( $result['plan_info'] ) ? wp_json_encode( $result['plan_info'] ) : '';
399 + MainWP_Utility::update_option( 'mainwp_extensions_plan_info', $plan_info );
400 + }
401 + $return['result'] = 'SUCCESS';
402 + } elseif ( isset( $result['error'] ) ) {
403 + $return['error'] = $result['error'];
404 + }
405 + }
368 406
369 - if ( ! empty( $download_link ) ) {
370 - $return = MainWP_Extensions_Handler::install_plugin( $download_link );
371 - } elseif ( ! empty( $plugin_slug ) ) {
372 - include_once ABSPATH . '/wp-admin/includes/plugin-install.php';
373 - $api = MainWP_System_Utility::get_plugin_theme_info(
374 - 'plugin',
375 - array(
376 - 'slug' => dirname( $plugin_slug ),
377 - 'fields' => array( 'sections' => false ),
378 - 'timeout' => 60,
379 - )
380 - );
407 + if ( ! $save_login ) {
408 + MainWP_Utility::update_option( 'mainwp_extensions_api_save_login', '' );
409 + MainWP_Utility::update_option( 'mainwp_extensions_plan_info', '' );
410 + }
381 411
382 - if ( is_object( $api ) && property_exists( $api, 'download_link' ) ) {
383 - $download_link = $api->download_link;
384 - $return = MainWP_Extensions_Handler::install_plugin( $download_link );
385 - } else {
386 - $return = array( 'error' => esc_html__( 'No response from the WordPress update server.', 'mainwp' ) );
387 - }
388 - }
412 + die( wp_json_encode( $return ) );
413 + }
389 414
390 - die( '<mainwp>' . wp_json_encode( $return ) . '</mainwp>' );
391 - }
415 + /**
416 + * Save whenther or not to verify MainWP API SSL certificate.
417 + *
418 + * @return void
419 + *
420 + * @uses \MainWP\Dashboard\MainWP_Utility::update_option()
421 + */
422 + public function save_api_ssl_verify() {
423 + $this->check_security( 'mainwp_extension_apisslverifycertificate' );
424 + MainWP_Utility::update_option( 'mainwp_api_sslVerifyCertificate', isset( $_POST['api_sslverify'] ) ? intval( $_POST['api_sslverify'] ) : 0 ); // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
425 + die( wp_json_encode( array( 'saved' => 1 ) ) );
426 + }
392 427
393 - /**
394 - * MainWP Extension Bulck Activation.
395 - *
396 - * @return void
397 - */
398 - public function bulk_activate() {
399 - $this->check_security( 'mainwp_extension_bulk_activate' );
400 - $plugins = isset( $_POST['plugins'] ) ? wp_unslash( $_POST['plugins'] ) : false; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
401 - if ( is_array( $plugins ) && 0 < count( $plugins ) ) {
402 - if ( current_user_can( 'activate_plugins' ) ) {
403 - activate_plugins( $plugins );
404 - die( 'SUCCESS' );
405 - }
406 - }
407 - die( 'FAILED' );
408 - }
428 + /**
429 + * Test Extension page MainWP.com login details.
430 + *
431 + * @return void
432 + *
433 + * @uses \MainWP\Dashboard\MainWP_Api_Manager::verify_mainwp_api()
434 + * @uses \MainWP\Dashboard\MainWP_Utility::update_option()
435 + */
436 + public function test_extensions_api_login() {
437 + $this->check_security( 'mainwp_extension_testextensionapilogin' );
438 + $api_key = MainWP_Api_Manager_Key::instance()->get_decrypt_master_api_key();
439 + $result = array();
440 + try {
441 + $test = MainWP_Api_Manager::instance()->verify_mainwp_api( $api_key );
442 + } catch ( \Exception $e ) {
443 + $return['error'] = $e->getMessage();
444 + die( wp_json_encode( $return ) );
445 + }
409 446
410 - /**
411 - * Remove Extensions menu from MainWP Menu.
412 - *
413 - * @return void
414 - *
415 - * @uses \MainWP\Dashboard\MainWP_Utility::update_option()
416 - */
417 - public function remove_extension_menu_from_mainwp_menu() {
418 - $this->check_security( 'mainwp_extension_remove_menu' );
419 - $snMenuExtensions = get_option( 'mainwp_extmenu' );
420 - if ( ! is_array( $snMenuExtensions ) ) {
421 - $snMenuExtensions = array();
422 - }
447 + if ( is_array( $test ) && isset( $test['retry_action'] ) ) {
448 + wp_send_json( $test );
449 + }
423 450
424 - $key = isset( $_POST['slug'] ) ? sanitize_text_field( wp_unslash( $_POST['slug'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
451 + $result = json_decode( $test, true );
452 + $return = array();
453 + if ( is_array( $result ) ) {
454 + if ( isset( $result['success'] ) && $result['success'] ) {
455 + $return['result'] = 'SUCCESS';
456 + } elseif ( isset( $result['error'] ) ) {
457 + $return['error'] = $result['error'];
458 + }
459 + } else {
460 + $apisslverify = get_option( 'mainwp_api_sslVerifyCertificate' );
461 + if ( 1 === (int) $apisslverify ) {
462 + MainWP_Utility::update_option( 'mainwp_api_sslVerifyCertificate', 0 );
463 + $return['retry_action'] = 1;
464 + }
465 + }
466 + wp_send_json( $return );
467 + }
425 468
426 - if ( ! empty( $key ) && isset( $snMenuExtensions[ $key ] ) ) {
427 - unset( $snMenuExtensions[ $key ] );
428 - MainWP_Utility::update_option( 'mainwp_extmenu', $snMenuExtensions );
429 - do_action( 'mainwp_removed_extension_menu', $key );
430 - die( wp_json_encode( array( 'result' => 'SUCCESS' ) ) );
431 - }
469 + /**
470 + * Download & Install MainWP Extension.
471 + *
472 + * @return void
473 + *
474 + * @uses \MainWP\Dashboard\MainWP_Extensions_Handler::install_plugin()
475 + */
476 + public function download_and_install() {
477 + $this->check_security( 'mainwp_extension_downloadandinstall' );
478 + // phpcs:ignore -- custom setting to install plugin.
479 + ini_set( 'zlib.output_compression', 'Off' );
480 + $download_link = isset( $_POST['download_link'] ) ? wp_unslash( $_POST['download_link'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
481 + $plugin_slug = isset( $_POST['plugin_slug'] ) ? wp_unslash( $_POST['plugin_slug'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
432 482
433 - die( - 1 );
434 - }
483 + $return = array( 'error' => esc_html__( 'Empty or Invalid request data, please try again.', 'mainwp' ) );
484 +
485 + if ( ! empty( $download_link ) ) {
486 + $return = MainWP_Extensions_Handler::install_plugin( $download_link );
487 + } elseif ( ! empty( $plugin_slug ) ) {
488 + include_once ABSPATH . '/wp-admin/includes/plugin-install.php'; // NOSONAR - WP compatible.
489 + $api = MainWP_System_Utility::get_plugin_theme_info(
490 + 'plugin',
491 + array(
492 + 'slug' => dirname( $plugin_slug ),
493 + 'fields' => array( 'sections' => false ),
494 + 'timeout' => 60,
495 + )
496 + );
497 +
498 + if ( is_object( $api ) && property_exists( $api, 'download_link' ) ) {
499 + $download_link = $api->download_link;
500 + $return = MainWP_Extensions_Handler::install_plugin( $download_link );
501 + } else {
502 + $return = array( 'error' => esc_html__( 'No response from the WordPress update server.', 'mainwp' ) );
503 + }
504 + }
505 +
506 + die( '<mainwp>' . wp_json_encode( $return ) . '</mainwp>' );
507 + }
508 +
509 + /**
510 + * MainWP Extension Bulck Activation.
511 + *
512 + * @return void
513 + */
514 + public function bulk_activate() {
515 + $this->check_security( 'mainwp_extension_bulk_activate' );
516 + $plugins = isset( $_POST['plugins'] ) ? wp_unslash( $_POST['plugins'] ) : false; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
517 + if ( is_array( $plugins ) && ! empty( $plugins ) && current_user_can( 'activate_plugins' ) ) {
518 + activate_plugins( $plugins );
519 + die( 'SUCCESS' );
520 + }
521 + die( 'FAILED' );
522 + }
523 +
524 + /**
525 + * Remove Extensions menu from MainWP Menu.
526 + *
527 + * @return void
528 + *
529 + * @uses \MainWP\Dashboard\MainWP_Utility::update_option()
530 + */
531 + public function remove_extension_menu_from_mainwp_menu() {
532 + $this->check_security( 'mainwp_extension_remove_menu' );
533 + $snMenuExtensions = get_option( 'mainwp_extmenu' );
534 + if ( ! is_array( $snMenuExtensions ) ) {
535 + $snMenuExtensions = array();
536 + }
537 +
538 + $key = isset( $_POST['slug'] ) ? sanitize_text_field( wp_unslash( $_POST['slug'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
539 +
540 + if ( ! empty( $key ) && isset( $snMenuExtensions[ $key ] ) ) {
541 + unset( $snMenuExtensions[ $key ] );
542 + MainWP_Utility::update_option( 'mainwp_extmenu', $snMenuExtensions );
543 + do_action( 'mainwp_removed_extension_menu', $key );
544 + die( wp_json_encode( array( 'result' => 'SUCCESS' ) ) );
545 + }
546 +
547 + die( - 1 );
548 + }
435 549 }