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 +105 -3 6.0.11 → 6.2.2 View file →
@@ -58,8 +58,12 @@
58 58 $this->add_action( 'mainwp_extension_testextensionapilogin', array( &$this, 'test_extensions_api_login' ) );
59 59
60 60 $this->add_action( 'mainwp_extension_plugin_action', array( &$this, 'ajax_extension_plugin_action' ) );
61 61
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 +
62 66 if ( \mainwp_current_user_can( 'dashboard', 'bulk_install_and_activate_extensions' ) ) {
63 67 $this->add_action( 'mainwp_extension_grabapikey', array( &$this, 'grab_extension_api_key' ) );
64 68 $this->add_action( 'mainwp_extension_saveextensionapilogin', array( &$this, 'save_extensions_api_login' ) );
65 69 $this->add_action( 'mainwp_extension_getpurchased', array( MainWP_Extensions::get_class_name(), 'ajax_get_purchased_extensions' ) );
@@ -72,8 +76,25 @@
72 76 $this->add_action( 'mainwp_ext_applypluginsettings', array( &$this, 'mainwp_ext_applypluginsettings' ) );
73 77 }
74 78
75 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 + /**
76 97 * Apply plugin settings.
77 98 *
78 99 * @return mixed success|error.
79 100 *
@@ -150,9 +171,9 @@
150 171 /**
151 172 * Method invalidate_warm_cache()
152 173 */
153 174 public function invalidate_warm_cache() {
154 - MainWP_Cache_Warm_Helper::invalidate_manage_pages( array( 'Extensions' ) );
175 + MainWP_Cache_Warm_Helper::invalidate_manage_pages( array( 'Extensions' ) );
155 176 }
156 177
157 178 /**
158 179 * Delete MainWP Extension plugin.
@@ -209,9 +230,49 @@
209 230 public function deactivate_extension() {
210 231 $this->check_security( 'mainwp_extension_deactivate' );
211 232 $api_slug = isset( $_POST['slug'] ) ? dirname( wp_unslash( $_POST['slug'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
212 233 $api_key = isset( $_POST['api_key'] ) ? wp_unslash( $_POST['api_key'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
213 - $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 );
214 275 wp_send_json( $result );
215 276 }
216 277
217 278 /**
@@ -224,9 +285,18 @@
224 285 public function grab_extension_api_key() {
225 286 $this->check_security( 'mainwp_extension_grabapikey' );
226 287 $api_slug = isset( $_POST['slug'] ) ? dirname( wp_unslash( $_POST['slug'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
227 288 $master_api_key = isset( $_POST['master_api_key'] ) ? wp_unslash( $_POST['master_api_key'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
228 - $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 );
229 299 wp_send_json( $result );
230 300 }
231 301
232 302 /**
@@ -260,8 +330,40 @@
260 330 $_SESSION['api_login_history'] = $new_api_login_history;
261 331 }
262 332
263 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 + }
264 366
265 367 if ( '' === $api_key && false !== $api_key ) {
266 368 MainWP_Keys_Manager::instance()->update_key_value( 'mainwp_extensions_master_api_key', false );
267 369 }