PluginProbe
GiveWP – Donation Plugin and Fundraising Platform / 4.17.0
GiveWP – Donation Plugin and Fundraising Platform v4.17.0
4.17.0 4.16.9 4.16.8.1 4.16.8 4.16.7.2 4.16.7.1 4.16.7 4.16.6.1 4.16.6 4.16.5.1 4.16.5 4.16.4 4.16.3 4.16.2 4.16.1 4.16.0 4.15.5 4.15.4 4.15.3 4.15.2 4.15.1 4.15.0 2.3.0 2.3.1 2.3.2 All 256 releases
← All changes | includes/admin/add-ons/actions.php +176 -59 4.15.1 → 4.17.0 View file →
@@ -8,8 +8,10 @@
8 8 * @license https://opensource.org/licenses/gpl-license GNU Public License
9 9 * @since 2.5.0
10 10 */
11 11
12 +use Give\VendorOverrides\Harbor\Actions\HarborHasLoaded;
13 +
12 14 // Exit if accessed directly.
13 15 if ( ! defined( 'ABSPATH' ) ) {
14 16 exit;
15 17 }
@@ -18,21 +20,23 @@
18 20 * Ajax addon upload handler
19 21 *
20 22 * Note: only for internal use
21 23 *
24 + * @since 4.16.6 Use Plugin_Upgrader to install/update the add-on, replacing unreliable
25 + * filename-based pre-existing checks and post-install detection.
22 26 * @since 2.5.0
23 27 */
24 28 function give_upload_addon_handler() {
25 - /* @var WP_Filesystem_Direct $wp_filesystem */
26 - global $wp_filesystem;
29 + if ( ! isset( $_FILES['file']['name'] ) ) {
30 + wp_send_json_error( [ 'errorMsg' => __( 'No file was uploaded.', 'give' ) ] );
31 + }
27 32
33 + if ( UPLOAD_ERR_OK !== $_FILES['file']['error'] ) {
34 + wp_send_json_error( [ 'errorMsg' => __( 'The file upload failed. Please try again.', 'give' ) ] );
35 + }
36 +
28 37 check_admin_referer( 'give-upload-addon' );
29 38
30 - // Remove version from file name.
31 - $filename = preg_replace( [ '/\(\d\).zip/', '/(.\d).*[\(\d\)]/' ], '', $_FILES['file']['name'] );
32 - $filename = basename( trim( $filename ), '.zip' );
33 -
34 - // Bailout if user does not has permission.
35 39 if ( ! current_user_can( 'upload_plugins' ) ) {
36 40 wp_send_json_error( [ 'errorMsg' => __( 'The current user does not have permission to upload plugins on this site.', 'give' ) ] );
37 41 }
38 42
@@ -42,9 +46,9 @@
42 46 wp_send_json_error(
43 47 [
44 48 'errorMsg' => sprintf(
45 49 __( 'In order to upload add-ons here, GiveWP needs direct access to the file system. Please <a href="%1$s" target="_blank">visit the main plugin page</a> to manually upload the add-on.', 'give' ),
46 - admin_url( 'plugin-install.php?tab=upload' )
50 + esc_url( admin_url( 'plugin-install.php?tab=upload' ) )
47 51 ),
48 52 ]
49 53 );
50 54 }
@@ -54,80 +58,86 @@
54 58 if ( empty( $file_type['ext'] ) ) {
55 59 wp_send_json_error( [ 'errorMsg' => __( 'Uploaded add-ons must be (zipped) ZIP files. Upload a valid add-on ZIP.', 'give' ) ] );
56 60 }
57 61
58 - $give_addons_list = give_get_plugins();
59 - $is_addon_installed = [];
62 + // Snapshot existing Give add-ons for diff after installation.
63 + $pre_addons_list = give_get_plugins( [ 'only_add_on' => true ] );
60 64
61 - if ( ! empty( $give_addons_list ) ) {
62 - foreach ( $give_addons_list as $addon => $give_addon ) {
63 - if ( false !== stripos( $addon, $filename ) ) {
64 - $is_addon_installed = $give_addon;
65 + // Detect the plugin folder name from the ZIP to check for an existing installation.
66 + $zip_folder = give_get_zip_plugin_folder( $_FILES['file']['tmp_name'] );
67 +
68 + if ( ! empty( $zip_folder ) && ! empty( $pre_addons_list ) ) {
69 + foreach ( $pre_addons_list as $addon_path => $addon_data ) {
70 + if ( strpos( $addon_path, $zip_folder . '/' ) === 0 ) {
71 + wp_send_json_error(
72 + [
73 + 'errorMsg' => __( 'This add-on is already installed.', 'give' ),
74 + 'pluginInfo' => $addon_data,
75 + ]
76 + );
65 77 }
66 78 }
67 79 }
68 80
69 - // Bailout if addon already installed
70 - if ( ! empty( $is_addon_installed ) ) {
71 - wp_send_json_error(
72 - [
73 - 'errorMsg' => __( 'This add-on is already installed.', 'give' ),
74 - 'pluginInfo' => $is_addon_installed,
75 - ]
76 - );
77 - }
81 + // Install the plugin using the WordPress upgrader.
82 + require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
83 + require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader-skin.php';
84 + require_once ABSPATH . 'wp-admin/includes/class-automatic-upgrader-skin.php';
85 + require_once ABSPATH . 'wp-admin/includes/class-plugin-upgrader.php';
86 + require_once ABSPATH . 'wp-admin/includes/plugin.php';
78 87
79 - $upload_status = wp_handle_upload( $_FILES['file'], [ 'test_form' => false ] );
88 + $skin = new Automatic_Upgrader_Skin();
89 + $upgrader = new Plugin_Upgrader( $skin );
80 90
81 - // Bailout if has any upload error
82 - if ( empty( $upload_status['file'] ) ) {
83 - wp_send_json_error( $upload_status );
91 + $buffer_level = ob_get_level();
92 +
93 + $result = $upgrader->install( $_FILES['file']['tmp_name'] );
94 +
95 + while ( ob_get_level() > $buffer_level ) {
96 + // A non-removable buffer, such as zlib, would loop until the request times out.
97 + if ( ! @ob_end_clean() ) {
98 + break;
99 + }
84 100 }
85 101
86 - // @todo: check how WordPress verify plugin files before uploading to plugin directory
102 + if ( is_wp_error( $result ) ) {
103 + wp_send_json_error( [ 'errorMsg' => $result->get_error_message() ] );
104 + }
87 105
88 - /* you can safely run request_filesystem_credentials() without any issues and don't need to worry about passing in a URL */
89 - $creds = request_filesystem_credentials( site_url() . '/wp-admin/', '', false, false, [] );
106 + if ( ! $result ) {
107 + $error_message = is_wp_error( $skin->result )
108 + ? $skin->result->get_error_message()
109 + : __( 'The add-on could not be installed. Please try again or upload it manually.', 'give' );
90 110
91 - /* initialize the API */
92 - if ( ! WP_Filesystem( $creds ) ) {
93 - /* any problems and we exit */
94 - wp_send_json_error(
95 - [
96 - 'errorMsg' => __( 'The file system did not load correctly. This is usually a permissions issue on your server, and not something that GiveWP has control over. Try uploading the ZIP like a regular plugin.', 'give' ),
97 - ]
98 - );
111 + wp_send_json_error( [ 'errorMsg' => $error_message ] );
99 112 }
100 113
101 - $unzip_status = unzip_file( $upload_status['file'], $wp_filesystem->wp_plugins_dir() );
114 + // Refresh the plugin cache and find the newly installed or updated plugin.
115 + wp_clean_plugins_cache( true );
102 116
103 - // Remove file.
104 - @unlink( $upload_status['file'] );
117 + $post_addons_list = give_get_plugins( [ 'only_add_on' => true ] );
118 + $new_plugins = array_diff_key( $post_addons_list, $pre_addons_list );
105 119
106 - // Bailout if not able to unzip file successfully
107 - if ( is_wp_error( $unzip_status ) ) {
120 + $installed_addon = [];
121 +
122 + if ( ! empty( $new_plugins ) ) {
123 + $new_plugin_path = array_key_first( $new_plugins );
124 + $installed_addon = $new_plugins[ $new_plugin_path ];
125 + $installed_addon['path'] = $new_plugin_path;
126 + }
127 +
128 + if ( empty( $installed_addon ) ) {
108 129 wp_send_json_error(
109 130 [
110 - 'errorMsg' => $unzip_status,
131 + 'errorMsg' => sprintf(
132 + /* translators: %1$s: URL to the plugins page */
133 + __( 'The add-on was uploaded but GiveWP could not detect it. Please <a href="%1$s">visit the plugins page</a> to activate it manually.', 'give' ),
134 + esc_url( admin_url( 'plugins.php' ) )
135 + ),
111 136 ]
112 137 );
113 138 }
114 139
115 - // Delete cache and get current installed addon plugin path.
116 - wp_clean_plugins_cache( true );
117 -
118 - $give_addons_list = give_get_plugins();
119 - $installed_addon = [];
120 -
121 - if ( ! empty( $give_addons_list ) ) {
122 - foreach ( $give_addons_list as $addon => $give_addon ) {
123 - if ( false !== stripos( $addon, $filename ) ) {
124 - $installed_addon = $give_addon;
125 - $installed_addon['path'] = $addon;
126 - }
127 - }
128 - }
129 -
130 140 wp_send_json_success(
131 141 [
132 142 'pluginPath' => $installed_addon['path'],
133 143 'pluginName' => $installed_addon['Name'],
@@ -136,8 +146,58 @@
136 146 ]
137 147 );
138 148 }
139 149
150 +/**
151 + * Reads the top-level directory name from a plugin ZIP file.
152 + *
153 + * Returns the single top-level directory name inside the ZIP (e.g. "give-recurring").
154 + * Returns an empty string if the ZIP can't be read or contains multiple top-level items.
155 + *
156 + * @since 4.16.6
157 + *
158 + * @param string $zip_file Absolute path to the ZIP file.
159 + *
160 + * @return string Plugin folder name, or empty string on failure.
161 + */
162 +function give_get_zip_plugin_folder( $zip_file ) {
163 + if ( ! file_exists( $zip_file ) || ! class_exists( 'ZipArchive' ) ) {
164 + return '';
165 + }
166 +
167 + $zip = new ZipArchive();
168 +
169 + if ( true !== $zip->open( $zip_file ) ) {
170 + return '';
171 + }
172 +
173 + $folder = '';
174 +
175 + for ( $i = 0; $i < $zip->numFiles; $i++ ) {
176 + $entry = $zip->getNameIndex( $i );
177 + $parts = explode( '/', $entry );
178 +
179 + // Skip macOS metadata folders.
180 + if ( isset( $parts[0] ) && '__MACOSX' === $parts[0] ) {
181 + continue;
182 + }
183 +
184 + if ( count( $parts ) > 1 && '' !== $parts[0] ) {
185 + if ( '' === $folder ) {
186 + $folder = $parts[0];
187 + } elseif ( $folder !== $parts[0] ) {
188 + // Multiple top-level directories — ambiguous.
189 + $folder = '';
190 + break;
191 + }
192 + }
193 + }
194 +
195 + $zip->close();
196 +
197 + return $folder;
198 +}
199 +
140 200 add_action( 'wp_ajax_give_upload_addon', 'give_upload_addon_handler' );
141 201
142 202 /**
143 203 * Ajax license inquiry handler
@@ -143,8 +203,9 @@
143 203 * Ajax license inquiry handler
144 204 *
145 205 * Note: only for internal use
146 206 *
207 + * @since 4.16.7 Redirect unified license keys (LWSW-) to the Unified License Manager, when it is available.
147 208 * @since 2.5.0
148 209 */
149 210 function give_get_license_info_handler() {
150 211 check_admin_referer( 'give-license-activator-nonce' );
@@ -166,8 +227,26 @@
166 227 'errorMsg' => __( 'You entered an invalid key. Confirm your license key on your GiveWP dashboard and try again.', 'give' ),
167 228 ]
168 229 );
169 230
231 + } elseif ( 0 === stripos( $license_key, 'LWSW-' ) ) {
232 + // The Unified License Manager is only available once a premium add-on is installed and active.
233 + $harborHasLoaded = give( HarborHasLoaded::class )();
234 +
235 + $error_message = $harborHasLoaded
236 + ? sprintf(
237 + /* translators: %s: URL to the Unified License Manager page */
238 + __( 'This is a unified license key. To activate it, enter your license in the <a href="%s" target="_blank">Unified License Manager</a> instead.', 'give' ),
239 + esc_url( lw_harbor_get_license_page_url() )
240 + )
241 + : __( 'This is a unified license key. It can be added from the Unified License Manager, which appears under GiveWP &gt; Licensing once an add-on is installed.', 'give' );
242 +
243 + wp_send_json_error(
244 + [
245 + 'errorMsg' => $error_message,
246 + ]
247 + );
248 +
170 249 } elseif (
171 250 ! $is_reactivating_license
172 251 && array_key_exists( $license_key, $licenses )
173 252 ) {
@@ -314,8 +393,10 @@
314 393 * Activate addon handler
315 394 *
316 395 * Note: only for internal use
317 396 *
397 + * @since 4.16.6 Guard against empty or invalid plugin paths that previously bypassed
398 + * the nonce and capability checks.
318 399 * @since 2.5.0
319 400 */
320 401 function give_activate_addon_handler() {
321 402 $plugin_path = give_clean( $_POST['plugin'] );
@@ -324,8 +405,44 @@
324 405
325 406 // check user permission.
326 407 if ( ! current_user_can( 'manage_give_settings' ) ) {
327 408 give_die();
409 + }
410 +
411 + if ( empty( $plugin_path ) ) {
412 + wp_send_json_error(
413 + [
414 + 'errorMsg' => __( 'No plugin path was provided. The uploaded plugin may not have been detected correctly.', 'give' ),
415 + ]
416 + );
417 + }
418 +
419 + $plugin_file = WP_PLUGIN_DIR . '/' . $plugin_path;
420 +
421 + if ( ! file_exists( $plugin_file ) ) {
422 + wp_send_json_error(
423 + [
424 + 'errorMsg' => sprintf(
425 + /* translators: %1$s: plugin file path */
426 + __( 'The plugin file "%1$s" could not be found. The add-on may not have been extracted correctly.', 'give' ),
427 + esc_html( $plugin_path )
428 + ),
429 + ]
430 + );
431 + }
432 +
433 + $plugin_data = get_plugin_data( $plugin_file );
434 +
435 + if ( empty( $plugin_data['Name'] ) ) {
436 + wp_send_json_error(
437 + [
438 + 'errorMsg' => sprintf(
439 + /* translators: %1$s: plugin file path */
440 + __( 'The plugin "%1$s" does not have a valid plugin header. The add-on may be corrupted or incompatible.', 'give' ),
441 + esc_html( $plugin_path )
442 + ),
443 + ]
444 + );
328 445 }
329 446
330 447 $status = activate_plugin( $plugin_path );
331 448