PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 16.1-beta
Jetpack – WP Security, Backup, Speed, & Growth v16.1-beta
16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / json-endpoints / jetpack / class.jetpack-json-api-themes-replace-endpoint.php
jetpack / json-endpoints / jetpack Last commit date
class-jetpack-json-api-attachment-ownership-trait.php 3 months ago class-jetpack-json-api-delete-backup-helper-script-endpoint.php 8 months ago class-jetpack-json-api-install-backup-helper-script-endpoint.php 8 months ago class-jetpack-json-api-modules-list-v1-2-endpoint.php 8 months ago class.jetpack-json-api-check-capabilities-endpoint.php 8 months ago class.jetpack-json-api-core-endpoint.php 8 months ago class.jetpack-json-api-core-modify-endpoint.php 2 months ago class.jetpack-json-api-cron-endpoint.php 8 months ago class.jetpack-json-api-endpoint.php 2 months ago class.jetpack-json-api-get-comment-backup-endpoint.php 8 months ago class.jetpack-json-api-get-database-object-backup-endpoint.php 8 months ago class.jetpack-json-api-get-option-backup-endpoint.php 8 months ago class.jetpack-json-api-get-post-backup-endpoint.php 8 months ago class.jetpack-json-api-get-term-backup-endpoint.php 8 months ago class.jetpack-json-api-get-user-backup-endpoint.php 8 months ago class.jetpack-json-api-jps-woocommerce-connect-endpoint.php 8 months ago class.jetpack-json-api-log-endpoint.php 8 months ago class.jetpack-json-api-maybe-auto-update-endpoint.php 8 months ago class.jetpack-json-api-modules-endpoint.php 8 months ago class.jetpack-json-api-modules-get-endpoint.php 8 months ago class.jetpack-json-api-modules-list-endpoint.php 8 months ago class.jetpack-json-api-modules-modify-endpoint.php 8 months ago class.jetpack-json-api-plugins-delete-endpoint.php 8 months ago class.jetpack-json-api-plugins-endpoint.php 2 months ago class.jetpack-json-api-plugins-get-endpoint.php 8 months ago class.jetpack-json-api-plugins-install-endpoint.php 8 months ago class.jetpack-json-api-plugins-list-endpoint.php 1 month ago class.jetpack-json-api-plugins-modify-endpoint.php 8 months ago class.jetpack-json-api-plugins-modify-v1-2-endpoint.php 8 months ago class.jetpack-json-api-plugins-new-endpoint.php 8 months ago class.jetpack-json-api-plugins-replace-endpoint.php 3 months ago class.jetpack-json-api-sync-endpoint.php 2 months ago class.jetpack-json-api-themes-active-endpoint.php 8 months ago class.jetpack-json-api-themes-delete-endpoint.php 8 months ago class.jetpack-json-api-themes-endpoint.php 2 months ago class.jetpack-json-api-themes-get-endpoint.php 8 months ago class.jetpack-json-api-themes-install-endpoint.php 8 months ago class.jetpack-json-api-themes-list-endpoint.php 8 months ago class.jetpack-json-api-themes-modify-endpoint.php 8 months ago class.jetpack-json-api-themes-new-endpoint.php 8 months ago class.jetpack-json-api-themes-replace-endpoint.php 3 months ago class.jetpack-json-api-translations-endpoint.php 8 months ago class.jetpack-json-api-translations-modify-endpoint.php 8 months ago class.jetpack-json-api-updates-status-endpoint.php 2 months ago class.jetpack-json-api-user-connect-endpoint.php 8 months ago class.jetpack-json-api-user-create-endpoint.php 2 months ago class.wpcom-json-api-get-option-endpoint.php 2 months ago class.wpcom-json-api-update-option-endpoint.php 8 months ago json-api-jetpack-endpoints.php 3 months ago
class.jetpack-json-api-themes-replace-endpoint.php
245 lines
1 <?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2
3 use Automattic\Jetpack\Automatic_Install_Skin;
4
5 if ( ! defined( 'ABSPATH' ) ) {
6 exit( 0 );
7 }
8
9 require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
10 require_once ABSPATH . 'wp-admin/includes/file.php';
11
12 /**
13 * Install-or-replace a theme via zip upload. Passes overwrite_package=true to
14 * Theme_Upgrader so an existing theme at the same slug is replaced in place,
15 * mirroring wp-admin's "Replace current with uploaded" confirmation flow.
16 *
17 * POST /sites/%s/themes/replace
18 *
19 * ## Authentication trust model
20 *
21 * Two auth modes are supported:
22 *
23 * 1. User auth — the request is mapped to a WordPress user who must hold
24 * `install_themes` AND `update_themes`. The ownership check verifies the
25 * referenced attachment was authored by that same user, preventing the
26 * endpoint from being used to touch another user's attachments.
27 *
28 * 2. Site-based auth (`allow_jetpack_site_auth => true`) — no user is
29 * identified; capability and ownership checks are skipped. The wpcom
30 * upload forwarder is expected to (a) vouch for the caller, (b) create the
31 * attachment as part of the same request's upload-intercept pipeline, and
32 * (c) pass the resulting ID through unchanged. If that contract is broken
33 * on the wpcom side, any trusted site credential becomes install-anything
34 * on the site.
35 *
36 * ## Cross-user attachment-deletion guard
37 *
38 * `Jetpack_JSON_API_Themes_New_Endpoint::validate_call` deletes the referenced
39 * attachment on capability-check failure without verifying the caller owns it.
40 * This Replace endpoint overrides `validate_call` to run the ownership check
41 * first, so a low-privilege caller cannot use it to hard-delete another user's
42 * attachment as a side-effect of the cap check failing. The parent's behavior
43 * is unchanged for the legitimate case (caller's own attachment is cleaned up
44 * when their cap check fails).
45 *
46 * @phan-constructor-used-for-side-effects
47 */
48 class Jetpack_JSON_API_Themes_Replace_Endpoint extends Jetpack_JSON_API_Themes_New_Endpoint {
49 use Jetpack_JSON_API_Attachment_Ownership_Trait;
50
51 /**
52 * Replace is destructive, so require both install and update caps.
53 *
54 * @var array
55 */
56 protected $needed_capabilities = array( 'install_themes', 'update_themes' );
57
58 /**
59 * Error codes we are willing to surface to the caller. Anything outside
60 * this list is collapsed to 'install_failed' with a generic message to
61 * avoid leaking filesystem paths from Theme_Upgrader internals.
62 *
63 * Kept aligned with codes actually emitted by `WP_Upgrader` /
64 * `Theme_Upgrader` — $this->strings[] message keys are NOT error codes
65 * and do not belong here.
66 *
67 * @var array
68 */
69 protected static $allowed_error_codes = array(
70 'no_package',
71 'bad_request',
72 'files_not_writable',
73 'copy_dir_failed',
74 'remove_old_failed',
75 'source_read_failed',
76 'new_source_read_failed',
77 'mkdir_failed_destination',
78 'folder_exists',
79 'incompatible_archive',
80 'incompatible_archive_empty',
81 'incompatible_archive_theme_no_style',
82 'incompatible_archive_theme_no_name',
83 'incompatible_archive_theme_no_index',
84 'incompatible_php_required_version',
85 'incompatible_wp_required_version',
86 'unable_to_connect_to_filesystem',
87 'fs_unavailable',
88 'fs_error',
89 'fs_no_themes_dir',
90 'fs_no_folder',
91 'fs_no_root_dir',
92 'fs_no_content_dir',
93 'fs_no_temp_backup_dir',
94 'fs_temp_backup_mkdir',
95 'fs_temp_backup_move',
96 );
97
98 /**
99 * Install, replacing any existing theme at the same slug.
100 *
101 * @return bool|WP_Error
102 */
103 public function install() {
104 $args = $this->input();
105
106 if ( ! isset( $args['zip'][0]['id'] ) || ! is_scalar( $args['zip'][0]['id'] ) ) {
107 return new WP_Error( 'no_theme_installed', __( 'No theme zip file was provided.', 'jetpack' ), 400 );
108 }
109
110 $expected_slug = isset( $args['slug'] ) && is_scalar( $args['slug'] )
111 ? strtolower( (string) $args['slug'] )
112 : '';
113 if ( ! preg_match( '/^[a-z0-9][a-z0-9_-]*$/', $expected_slug ) ) {
114 return new WP_Error( 'missing_slug', __( 'A valid theme slug is required; the replace endpoint refuses to overwrite a theme whose slug the caller has not declared.', 'jetpack' ), 400 );
115 }
116
117 $attachment_id = (int) $args['zip'][0]['id'];
118
119 // Re-checked here so direct invocations of install() (notably the test stubs)
120 // can't accidentally bypass the ownership guard that validate_call() runs on
121 // the live request path.
122 $ownership = $this->validate_attachment_ownership( $attachment_id );
123 if ( is_wp_error( $ownership ) ) {
124 return $ownership;
125 }
126
127 $zip_check = $this->validate_attachment_is_zip( $attachment_id );
128 if ( is_wp_error( $zip_check ) ) {
129 wp_delete_attachment( $attachment_id, true );
130 return $zip_check;
131 }
132
133 $local_file = get_attached_file( $attachment_id );
134 if ( ! $local_file ) {
135 wp_delete_attachment( $attachment_id, true );
136 return new WP_Error( 'local-file-does-not-exist', __( 'Uploaded theme zip could not be found on disk.', 'jetpack' ), 400 );
137 }
138
139 $skin = new Automatic_Install_Skin();
140 $upgrader = new Theme_Upgrader( $skin );
141
142 $result = $upgrader->install(
143 $local_file,
144 array( 'overwrite_package' => true )
145 );
146
147 wp_delete_attachment( $attachment_id, true );
148
149 if ( is_wp_error( $result ) ) {
150 return $this->sanitize_upgrader_error( $result );
151 }
152
153 if ( ! $result ) {
154 $error_code = $skin->get_main_error_code();
155 if ( 'download_failed' === $error_code ) {
156 $error_code = 'no_package';
157 }
158 if ( empty( $error_code ) || ! in_array( $error_code, self::$allowed_error_codes, true ) ) {
159 $error_code = 'install_failed';
160 }
161 return new WP_Error( $error_code, __( 'Theme installation failed.', 'jetpack' ), 400 );
162 }
163
164 $theme_info = $upgrader->theme_info();
165 $theme_slug = $theme_info ? $theme_info->get_stylesheet() : '';
166 if ( empty( $theme_slug ) ) {
167 return new WP_Error( 'theme_replace_info_missing', __( 'Theme was installed but its identifier could not be determined.', 'jetpack' ), 500 );
168 }
169
170 // `overwrite_package=true` trusts the zip's own folder name, so a zip whose
171 // top-level folder differs from the declared slug would clobber an unrelated
172 // theme. Verify the post-install identifier matches the caller's contract.
173 if ( strtolower( $theme_slug ) !== $expected_slug ) {
174 return new WP_Error( 'slug_mismatch', __( 'The installed theme does not match the declared slug.', 'jetpack' ), 400 );
175 }
176
177 $this->themes = array( $theme_slug );
178 $this->log[ $theme_slug ] = $upgrader->skin->get_upgrade_messages();
179
180 return true;
181 }
182
183 /**
184 * See class docblock — runs the attachment-ownership check before delegating
185 * to the parent's validate_call(), which deletes the referenced attachment
186 * on capability-check failure without verifying ownership.
187 *
188 * @param int $_blog_id Blog ID.
189 * @param string $capability Capability.
190 * @param bool $check_manage_active Whether to check manage-is-active.
191 * @return bool|WP_Error
192 */
193 protected function validate_call( $_blog_id, $capability, $check_manage_active = true ) {
194 $args = $this->input();
195 if ( isset( $args['zip'][0]['id'] ) && is_scalar( $args['zip'][0]['id'] ) ) {
196 $ownership = $this->validate_attachment_ownership( (int) $args['zip'][0]['id'] );
197 if ( is_wp_error( $ownership ) ) {
198 return $ownership;
199 }
200 }
201 return parent::validate_call( $_blog_id, $capability, $check_manage_active );
202 }
203
204 /**
205 * Collapse unknown error codes and strip potentially path-leaking messages
206 * from WP_Error instances returned by Theme_Upgrader.
207 *
208 * @param WP_Error $error Raw upgrader error.
209 * @return WP_Error
210 */
211 protected function sanitize_upgrader_error( WP_Error $error ) {
212 $code = $error->get_error_code();
213 if ( empty( $code ) || ! in_array( $code, self::$allowed_error_codes, true ) ) {
214 return new WP_Error( 'install_failed', __( 'Theme installation failed.', 'jetpack' ), 400 );
215 }
216 return new WP_Error( $code, __( 'Theme installation failed.', 'jetpack' ), 400 );
217 }
218 }
219
220 // POST /sites/%s/themes/replace
221 new Jetpack_JSON_API_Themes_Replace_Endpoint(
222 array(
223 'description' => 'Install or replace a theme on a Jetpack site by uploading a zip file. If a theme with the same slug is already installed, its destination folder is replaced in place, mirroring wp-admin\'s "Replace current with uploaded" upload flow.',
224 'group' => '__do_not_document',
225 'stat' => 'themes:replace',
226 'method' => 'POST',
227 'path' => '/sites/%s/themes/replace',
228 'path_labels' => array(
229 '$site' => '(int|string) Site ID or domain',
230 ),
231 'request_format' => array(
232 'zip' => '(array) Reference to an uploaded theme package zip file.',
233 'slug' => '(string) The theme slug the uploaded zip must resolve to. Required; the endpoint rejects zips whose top-level folder does not match.',
234 ),
235 'response_format' => Jetpack_JSON_API_Themes_Endpoint::$_response_format,
236 'allow_jetpack_site_auth' => true,
237 'example_request_data' => array(
238 'headers' => array(
239 'authorization' => 'Bearer YOUR_API_TOKEN',
240 ),
241 ),
242 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/example.wordpress.org/themes/replace',
243 )
244 );
245