PluginProbe
Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant / 2.3.2
Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant v2.3.2
2.3.2 2.3.1 2.3.0 2.2.8 2.2.7 trunk 1.10.0 1.10.1 1.10.2 1.10.3 1.10.4 1.10.5 1.11.0 1.11.1 1.11.2 1.6 1.7 1.8 1.8.1 1.8.2 1.8.3 1.9.0 1.9.1 1.9.10 1.9.11 All 60 releases
← All changes | admin/classes/class-merchant-plugin-installer.php +130 -15 2.2.72.3.2 View file →
@@ -54,23 +54,55 @@
54 54 wp_localize_script( 'merchant-plugin-installer', 'merchantPluginInstallerConfig', array(
55 55 'ajax_url' => admin_url( 'admin-ajax.php' ),
56 56 'nonce' => wp_create_nonce( 'merchant_plugin_installer_nonce' ),
57 57 'i18n' => array(
58 - 'defaultText' => esc_html__( 'Install and Activate', 'merchant' ),
59 - 'installingText' => esc_html__( 'Installing...', 'merchant' ),
60 - 'activatingText' => esc_html__( 'Activating...', 'merchant' ),
58 + 'defaultText' => esc_html__( 'Install and Activate', 'merchant' ),
59 + 'installingText' => esc_html__( 'Installing...', 'merchant' ),
60 + 'activatingText' => esc_html__( 'Activating...', 'merchant' ),
61 + 'networkErrorText' => esc_html__( 'Installation failed. Please try again.', 'merchant' ),
61 62 ),
62 63 ) );
63 64 }
64 65
65 66 /**
66 - * Install plugin.
67 - * This method is responsible for installing plugins from the wp.org.
67 + * Install and activate a plugin from the WordPress.org repository.
68 + * Handles three states: already active, installed but inactive, not installed.
68 69 *
69 70 * @return void
70 71 */
71 72 public function install_plugin() {
72 - // TODO: Implement this method.
73 + list( $slug, $plugin_name ) = $this->validate_wporg_install_request();
74 +
75 + $state = $this->get_plugin_state( $plugin_name );
76 +
77 + // Already active — nothing to do.
78 + if ( 'active' === $state ) {
79 + wp_send_json_success( array( 'message' => esc_html__( 'Plugin is already active.', 'merchant' ) ) );
80 + }
81 +
82 + // Not installed — resolve download URL from wp.org and install.
83 + if ( 'not_installed' === $state ) {
84 + $download_url = $this->get_wporg_download_url( $slug );
85 +
86 + if ( is_wp_error( $download_url ) ) {
87 + wp_send_json_error( array( 'message' => $download_url->get_error_message() ) );
88 + }
89 +
90 + $install_result = $this->install_from_url( $download_url );
91 +
92 + if ( is_wp_error( $install_result ) ) {
93 + wp_send_json_error( array( 'message' => $install_result->get_error_message() ) );
94 + }
95 + }
96 +
97 + // Activate the plugin (covers both 'installed' and freshly installed).
98 + $activate_result = $this->activate( $plugin_name );
99 +
100 + if ( is_wp_error( $activate_result ) ) {
101 + wp_send_json_error( array( 'message' => $activate_result->get_error_message() ) );
102 + }
103 +
104 + wp_send_json_success( array( 'message' => esc_html__( 'Plugin activated successfully.', 'merchant' ) ) );
73 105 }
74 106
75 107 /**
76 108 * Install and activate an external plugin.
@@ -129,17 +161,80 @@
129 161 wp_send_json_error( array( 'message' => esc_html__( 'Plugin name is required.', 'merchant' ) ) );
130 162 }
131 163
132 164 return array(
133 - esc_url_raw( $_POST['url'] ), // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
165 + esc_url_raw( wp_unslash( $_POST['url'] ) ), // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
134 166 sanitize_text_field( wp_unslash( $_POST['plugin_name'] ) ),
135 167 );
136 168 }
137 169
138 170 /**
171 + * Validate a wp.org install request.
172 + * Checks nonce, capability, and required parameters (slug and plugin_name).
173 + * Sends a JSON error and dies if validation fails.
174 + *
175 + * @return array{0: string, 1: string} The validated slug and plugin name.
176 + */
177 + private function validate_wporg_install_request() {
178 + check_ajax_referer( 'merchant_plugin_installer_nonce', 'nonce' );
179 +
180 + if ( ! current_user_can( 'install_plugins' ) ) {
181 + wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to install plugins.', 'merchant' ) ) );
182 + }
183 +
184 + if ( empty( $_POST['slug'] ) ) {
185 + wp_send_json_error( array( 'message' => esc_html__( 'Plugin slug is required.', 'merchant' ) ) );
186 + }
187 +
188 + if ( empty( $_POST['plugin_name'] ) ) {
189 + wp_send_json_error( array( 'message' => esc_html__( 'Plugin name is required.', 'merchant' ) ) );
190 + }
191 +
192 + return array(
193 + sanitize_text_field( wp_unslash( $_POST['slug'] ) ),
194 + sanitize_text_field( wp_unslash( $_POST['plugin_name'] ) ),
195 + );
196 + }
197 +
198 + /**
199 + * Resolve the download URL for a plugin from the WordPress.org API.
200 + *
201 + * @param string $slug The plugin slug (e.g. 'woocommerce').
202 + *
203 + * @return string|WP_Error The download URL on success, WP_Error on failure.
204 + */
205 + private function get_wporg_download_url( $slug ) {
206 + require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
207 +
208 + $api = plugins_api(
209 + 'plugin_information',
210 + array(
211 + 'slug' => $slug,
212 + 'fields' => array( 'sections' => false ),
213 + )
214 + );
215 +
216 + if ( is_wp_error( $api ) ) {
217 + return new WP_Error(
218 + 'merchant_plugin_api_failed',
219 + esc_html__( 'Could not retrieve plugin information from WordPress.org. Please try again later.', 'merchant' )
220 + );
221 + }
222 +
223 + if ( empty( $api->download_link ) ) {
224 + return new WP_Error(
225 + 'merchant_no_download_link',
226 + esc_html__( 'No download link found for this plugin.', 'merchant' )
227 + );
228 + }
229 +
230 + return $api->download_link;
231 + }
232 +
233 + /**
139 234 * Determine the current state of a plugin.
140 235 *
141 - * @param string $plugin_name The plugin basename (e.g. 'athemes-patcher/athemes-patcher.php').
236 + * @param string $plugin_name The plugin basename (e.g. 'woocommerce/woocommerce.php').
142 237 *
143 238 * @return string One of 'active', 'installed', or 'not_installed'.
144 239 */
145 240 private function get_plugin_state( $plugin_name ) {
@@ -167,10 +262,10 @@
167 262 private function install_from_url( $url ) {
168 263 include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
169 264 require_once MERCHANT_DIR . 'admin/classes/class-merchant-silent-upgrader-skin.php';
170 265
171 - $skin = new Merchant_Silent_Upgrader_Skin();
172 - $upgrader = new Plugin_Upgrader( $skin );
266 + $skin = $this->make_skin();
267 + $upgrader = $this->make_upgrader( $skin );
173 268 $result = $upgrader->install( $url, array( 'overwrite_package' => true ) );
174 269
175 270 if ( is_wp_error( $result ) ) {
176 271 return new WP_Error(
@@ -194,8 +289,30 @@
194 289 return true;
195 290 }
196 291
197 292 /**
293 + * Factory: create a Plugin_Upgrader instance.
294 + * Extracted as a protected method so tests can override it via a subclass.
295 + *
296 + * @param Merchant_Silent_Upgrader_Skin $skin The upgrader skin.
297 + *
298 + * @return Plugin_Upgrader
299 + */
300 + protected function make_upgrader( Merchant_Silent_Upgrader_Skin $skin ) {
301 + return new Plugin_Upgrader( $skin );
302 + }
303 +
304 + /**
305 + * Factory: create a Merchant_Silent_Upgrader_Skin instance.
306 + * Extracted as a protected method so tests can override it via a subclass.
307 + *
308 + * @return Merchant_Silent_Upgrader_Skin
309 + */
310 + protected function make_skin() {
311 + return new Merchant_Silent_Upgrader_Skin();
312 + }
313 +
314 + /**
198 315 * Get a user-friendly error message based on the error type.
199 316 *
200 317 * @param WP_Error $error The WP_Error object.
201 318 *
@@ -205,12 +322,10 @@
205 322 $error_code = $error->get_error_code();
206 323 $error_message = $error->get_error_message();
207 324 $error_data = $error->get_error_data();
208 325
209 - // Ensure error_message is a string for strpos() calls
210 - if ( ! is_string( $error_message ) ) {
211 - $error_message = '';
212 - }
326 + // Ensure error_message is a string for strpos() calls.
327 + $error_message = (string) $error_message;
213 328
214 329 // Extract HTTP status code if available
215 330 $http_code = null;
216 331 if ( is_array( $error_data ) && isset( $error_data['status'] ) && is_int( $error_data['status'] ) ) {
@@ -254,9 +369,9 @@
254 369
255 370 /**
256 371 * Activate a plugin.
257 372 *
258 - * @param string $plugin_name The plugin basename (e.g. 'athemes-patcher/athemes-patcher.php').
373 + * @param string $plugin_name The plugin basename (e.g. 'woocommerce/woocommerce.php').
259 374 *
260 375 * @return true|WP_Error True on success, WP_Error on failure.
261 376 */
262 377 private function activate( $plugin_name ) {