PluginProbe
Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant / 2.2.8
Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant v2.2.8
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
merchant / admin / classes / class-merchant-plugin-installer.php

class-merchant-plugin-installer.php in Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant 2.2.8, at admin/classes/class-merchant-plugin-installer.php

274 lines 10.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Plugin Installer.
5 * This class is responsible for installing and activating plugins. This could be from wp.org and external sources.
6 *
7 * @package Merchant
8 */
9
10 // Exit if accessed directly.
11 if ( ! defined( 'ABSPATH' ) ) {
12 exit;
13 }
14
15 if ( ! class_exists( 'Merchant_Plugin_Installer' ) ) {
16
17 class Merchant_Plugin_Installer {
18
19 /**
20 * Constructor.
21 *
22 */
23 public function __construct() {
24 add_action( 'admin_init', array( $this, 'init' ) );
25 }
26
27 /**
28 * Initialize the class.
29 *
30 * @return void
31 */
32 public function init() {
33 if ( ! is_admin() ) {
34 return;
35 }
36
37 if ( ! current_user_can( 'install_plugins' ) ) {
38 return;
39 }
40
41 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
42 add_action( 'wp_ajax_merchant_install_plugin', array( $this, 'install_plugin' ) );
43 add_action( 'wp_ajax_merchant_install_external_plugin', array( $this, 'install_external_plugin' ) );
44 }
45
46 /**
47 * Enqueue scripts.
48 *
49 * @return void
50 */
51 public function enqueue_scripts() {
52 wp_enqueue_script( 'merchant-plugin-installer', MERCHANT_URI . '/assets/js/admin/plugin-installer.min.js', array( 'jquery' ), MERCHANT_VERSION, true );
53
54 wp_localize_script( 'merchant-plugin-installer', 'merchantPluginInstallerConfig', array(
55 'ajax_url' => admin_url( 'admin-ajax.php' ),
56 'nonce' => wp_create_nonce( 'merchant_plugin_installer_nonce' ),
57 'i18n' => array(
58 'defaultText' => esc_html__( 'Install and Activate', 'merchant' ),
59 'installingText' => esc_html__( 'Installing...', 'merchant' ),
60 'activatingText' => esc_html__( 'Activating...', 'merchant' ),
61 ),
62 ) );
63 }
64
65 /**
66 * Install plugin.
67 * This method is responsible for installing plugins from the wp.org.
68 *
69 * @return void
70 */
71 public function install_plugin() {
72 // TODO: Implement this method.
73 }
74
75 /**
76 * Install and activate an external plugin.
77 * Handles three states: already active, installed but inactive, not installed.
78 *
79 * @return void
80 */
81 public function install_external_plugin() {
82 list( $url, $plugin_name ) = $this->validate_install_request();
83
84 $state = $this->get_plugin_state( $plugin_name );
85
86 // Already active — nothing to do.
87 if ( 'active' === $state ) {
88 wp_send_json_success( array( 'message' => esc_html__( 'Plugin is already active.', 'merchant' ) ) );
89 }
90
91 // Not installed — download and install first.
92 if ( 'not_installed' === $state ) {
93 $install_result = $this->install_from_url( $url );
94
95 if ( is_wp_error( $install_result ) ) {
96 wp_send_json_error( array( 'message' => $install_result->get_error_message() ) );
97 }
98 }
99
100 // Activate the plugin (covers both 'installed' and freshly installed).
101 $activate_result = $this->activate( $plugin_name );
102
103 if ( is_wp_error( $activate_result ) ) {
104 wp_send_json_error( array( 'message' => $activate_result->get_error_message() ) );
105 }
106
107 wp_send_json_success( array( 'message' => esc_html__( 'Plugin activated successfully.', 'merchant' ) ) );
108 }
109
110 /**
111 * Validate the install request.
112 * Checks nonce, capability, and required parameters.
113 * Sends a JSON error and dies if validation fails.
114 *
115 * @return array{0: string, 1: string} The validated URL and plugin name.
116 */
117 private function validate_install_request() {
118 check_ajax_referer( 'merchant_plugin_installer_nonce', 'nonce' );
119
120 if ( ! current_user_can( 'install_plugins' ) ) {
121 wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to install plugins.', 'merchant' ) ) );
122 }
123
124 if ( empty( $_POST['url'] ) ) {
125 wp_send_json_error( array( 'message' => esc_html__( 'Plugin URL is required.', 'merchant' ) ) );
126 }
127
128 if ( empty( $_POST['plugin_name'] ) ) {
129 wp_send_json_error( array( 'message' => esc_html__( 'Plugin name is required.', 'merchant' ) ) );
130 }
131
132 return array(
133 esc_url_raw( $_POST['url'] ), // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
134 sanitize_text_field( wp_unslash( $_POST['plugin_name'] ) ),
135 );
136 }
137
138 /**
139 * Determine the current state of a plugin.
140 *
141 * @param string $plugin_name The plugin basename (e.g. 'athemes-patcher/athemes-patcher.php').
142 *
143 * @return string One of 'active', 'installed', or 'not_installed'.
144 */
145 private function get_plugin_state( $plugin_name ) {
146 if ( is_plugin_active( $plugin_name ) ) {
147 return 'active';
148 }
149
150 $plugin_file = WP_PLUGIN_DIR . '/' . $plugin_name;
151
152 if ( file_exists( $plugin_file ) ) {
153 return 'installed';
154 }
155
156 return 'not_installed';
157 }
158
159 /**
160 * Download and install a plugin from an external URL.
161 * Uses `overwrite_package` to handle leftover directories from failed installs.
162 *
163 * @param string $url The URL to the plugin ZIP file.
164 *
165 * @return true|WP_Error True on success, WP_Error on failure.
166 */
167 private function install_from_url( $url ) {
168 include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
169 require_once MERCHANT_DIR . 'admin/classes/class-merchant-silent-upgrader-skin.php';
170
171 $skin = new Merchant_Silent_Upgrader_Skin();
172 $upgrader = new Plugin_Upgrader( $skin );
173 $result = $upgrader->install( $url, array( 'overwrite_package' => true ) );
174
175 if ( is_wp_error( $result ) ) {
176 return new WP_Error(
177 'merchant_install_failed',
178 $this->get_user_friendly_error( $result )
179 );
180 }
181
182 if ( ! $result ) {
183 $captured = $skin->get_captured_errors();
184 $error_message = ! empty( $captured ) ? $captured[0] : '';
185
186 $error = new WP_Error( 'download_failed', $error_message );
187
188 return new WP_Error(
189 'merchant_install_failed',
190 $this->get_user_friendly_error( $error )
191 );
192 }
193
194 return true;
195 }
196
197 /**
198 * Get a user-friendly error message based on the error type.
199 *
200 * @param WP_Error $error The WP_Error object.
201 *
202 * @return string User-friendly error message.
203 */
204 private function get_user_friendly_error( WP_Error $error ) {
205 $error_code = $error->get_error_code();
206 $error_message = $error->get_error_message();
207 $error_data = $error->get_error_data();
208
209 // Ensure error_message is a string for strpos() calls
210 if ( ! is_string( $error_message ) ) {
211 $error_message = '';
212 }
213
214 // Extract HTTP status code if available
215 $http_code = null;
216 if ( is_array( $error_data ) && isset( $error_data['status'] ) && is_int( $error_data['status'] ) ) {
217 $http_code = $error_data['status'];
218 }
219
220 // Check for 403 Forbidden (server blocking the download)
221 if ( 403 === $http_code || strpos( $error_message, '403' ) !== false || strpos( $error_message, 'Forbidden' ) !== false ) {
222 return esc_html__( 'The download is currently unavailable. Please try again later or contact support for assistance.', 'merchant' );
223 }
224
225 // Check for server errors (500, 502, 503)
226 if ( in_array( $http_code, array( 500, 502, 503 ), true ) ||
227 strpos( $error_message, '500' ) !== false ||
228 strpos( $error_message, '502' ) !== false ||
229 strpos( $error_message, '503' ) !== false ||
230 strpos( $error_message, 'Internal Server Error' ) !== false ||
231 strpos( $error_message, 'Bad Gateway' ) !== false ||
232 strpos( $error_message, 'Service Unavailable' ) !== false ) {
233 return esc_html__( 'The download server is temporarily unavailable. Please try again in a few minutes.', 'merchant' );
234 }
235
236 // Check for rate limiting (429)
237 if ( 429 === $http_code || strpos( $error_message, '429' ) !== false || strpos( $error_message, 'Too Many Requests' ) !== false ) {
238 return esc_html__( 'Too many download requests. Please wait a moment and try again.', 'merchant' );
239 }
240
241 // Check for network/connectivity errors
242 if ( in_array( $error_code, array( 'http_request_failed', 'http_no_url', 'http_404' ), true ) ) {
243 return esc_html__( 'The server could not connect to the download source. Please try again or contact your hosting provider if the problem persists.', 'merchant' );
244 }
245
246 // Check for download failures
247 if ( strpos( $error_message, 'Download failed' ) !== false ) {
248 return esc_html__( 'The download could not be completed. Please try again or install the plugin manually.', 'merchant' );
249 }
250
251 // Generic fallback
252 return esc_html__( 'Installation failed. Please try again or contact support if the problem persists.', 'merchant' );
253 }
254
255 /**
256 * Activate a plugin.
257 *
258 * @param string $plugin_name The plugin basename (e.g. 'athemes-patcher/athemes-patcher.php').
259 *
260 * @return true|WP_Error True on success, WP_Error on failure.
261 */
262 private function activate( $plugin_name ) {
263 $result = activate_plugin( $plugin_name );
264
265 if ( is_wp_error( $result ) ) {
266 return $result;
267 }
268
269 return true;
270 }
271 }
272
273 new Merchant_Plugin_Installer();
274 }