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 +119 -3 5.2.1 → 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
@@ -53,9 +58,13 @@
53 58 $this->add_action( 'mainwp_extension_testextensionapilogin', array( &$this, 'test_extensions_api_login' ) );
54 59
55 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' ) ) {
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 +
66 + if ( \mainwp_current_user_can( 'dashboard', 'bulk_install_and_activate_extensions' ) ) {
58 67 $this->add_action( 'mainwp_extension_grabapikey', array( &$this, 'grab_extension_api_key' ) );
59 68 $this->add_action( 'mainwp_extension_saveextensionapilogin', array( &$this, 'save_extensions_api_login' ) );
60 69 $this->add_action( 'mainwp_extension_getpurchased', array( MainWP_Extensions::get_class_name(), 'ajax_get_purchased_extensions' ) );
61 70 $this->add_action( 'mainwp_extension_downloadandinstall', array( &$this, 'download_and_install' ) );
@@ -67,8 +76,25 @@
67 76 $this->add_action( 'mainwp_ext_applypluginsettings', array( &$this, 'mainwp_ext_applypluginsettings' ) );
68 77 }
69 78
70 79 /**
80 + * Retry Add-on update checks.
81 + */
82 + public function retry_extension_update_check() {
83 + $this->check_security( 'mainwp_extension_update_check_retry' );
84 +
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 + }
88 +
89 + if ( MainWP_System_Handler::instance()->retry_extension_update_check() ) {
90 + wp_send_json( array( 'result' => 'SUCCESS' ) );
91 + }
92 +
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 + }
95 +
96 + /**
71 97 * Apply plugin settings.
72 98 *
73 99 * @return mixed success|error.
74 100 *
@@ -120,8 +146,9 @@
120 146 $this->check_security( 'mainwp_extension_plugin_action' );
121 147 $plugin_slug = isset( $_POST['slug'] ) ? sanitize_text_field( wp_unslash( $_POST['slug'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
122 148 $action = isset( $_POST['what'] ) ? sanitize_text_field( wp_unslash( $_POST['what'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
123 149 if ( ! empty( $plugin_slug ) && in_array( $action, array( 'active', 'disable', 'remove' ) ) ) {
150 + $this->invalidate_warm_cache();
124 151 if ( 'disable' === $action ) {
125 152 if ( is_plugin_active( $plugin_slug ) ) {
126 153 deactivate_plugins( $plugin_slug, false );
127 154 }
@@ -139,9 +166,17 @@
139 166 }
140 167 wp_send_json( array( 'error' => esc_html__( 'Invalid data provided.', 'mainwp' ) ) );
141 168 }
142 169
170 +
143 171 /**
172 + * Method invalidate_warm_cache()
173 + */
174 + public function invalidate_warm_cache() {
175 + MainWP_Cache_Warm_Helper::invalidate_manage_pages( array( 'Extensions' ) );
176 + }
177 +
178 + /**
144 179 * Delete MainWP Extension plugin.
145 180 *
146 181 * @param string $plugin_slug plugin slug.
147 182 *
@@ -195,9 +230,49 @@
195 230 public function deactivate_extension() {
196 231 $this->check_security( 'mainwp_extension_deactivate' );
197 232 $api_slug = isset( $_POST['slug'] ) ? dirname( wp_unslash( $_POST['slug'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
198 233 $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 );
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 );
200 275 wp_send_json( $result );
201 276 }
202 277
203 278 /**
@@ -210,9 +285,18 @@
210 285 public function grab_extension_api_key() {
211 286 $this->check_security( 'mainwp_extension_grabapikey' );
212 287 $api_slug = isset( $_POST['slug'] ) ? dirname( wp_unslash( $_POST['slug'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
213 288 $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 );
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 );
215 299 wp_send_json( $result );
216 300 }
217 301
218 302 /**
@@ -246,8 +330,40 @@
246 330 $_SESSION['api_login_history'] = $new_api_login_history;
247 331 }
248 332
249 333 $api_key = isset( $_POST['api_key'] ) ? trim( wp_unslash( $_POST['api_key'] ) ) : false; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
334 +
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 + }
250 366
251 367 if ( '' === $api_key && false !== $api_key ) {
252 368 MainWP_Keys_Manager::instance()->update_key_value( 'mainwp_extensions_master_api_key', false );
253 369 }