PluginProbe
JetFormBuilder — Dynamic Blocks Form Builder / 3.6.6
JetFormBuilder — Dynamic Blocks Form Builder v3.6.6
3.6.6.1 3.6.6 3.6.5.4 3.6.5.3 3.6.5.2 3.6.5.1 3.6.5 3.6.4.2 3.6.4.1 3.6.4 3.6.3.1 3.6.3 3.6.2.2 3.6.2.1 3.6.2 3.6.1.1 3.6.1 3.6.0.1 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 All 134 releases
jetformbuilder / includes / classes / resources / uploaded-file.php

uploaded-file.php in JetFormBuilder — Dynamic Blocks Form Builder 3.6.6, at includes/classes/resources/uploaded-file.php

306 lines 7.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3
4 namespace Jet_Form_Builder\Classes\Resources;
5
6 // If this file is called directly, abort.
7 if ( ! defined( 'WPINC' ) ) {
8 die;
9 }
10
11 class Uploaded_File implements Media_Block_Value, Uploaded_File_Path {
12
13 protected $file = '';
14 protected $url = '';
15 protected $type = '';
16 protected $attachment_id = '';
17
18 /**
19 * @param File $file
20 *
21 * @throws Upload_Exception
22 */
23 public function upload( File $file ) {
24 if ( ! function_exists( 'wp_handle_upload' ) ) {
25 include_once ABSPATH . 'wp-admin/includes/file.php';
26 include_once ABSPATH . 'wp-admin/includes/media.php';
27 }
28
29 add_filter( 'upload_dir', array( Upload_Dir::class, 'apply_upload_dir' ) );
30
31 $file_array = $file->to_array();
32 $this->upload_file( $file_array );
33
34 remove_filter( 'upload_dir', array( Upload_Dir::class, 'apply_upload_dir' ) );
35 }
36
37 /**
38 * @param array $file
39 *
40 * @throws Upload_Exception
41 */
42 protected function upload_file( array $file ) {
43 $upload = wp_handle_upload(
44 $file,
45 array(
46 'test_form' => false,
47 )
48 );
49
50 if ( ! empty( $upload['error'] ) ) {
51 throw new Upload_Exception( esc_html( $upload['error'] ) );
52 }
53
54 $this->set_from_array( $upload );
55 }
56
57 /**
58 * @throws Upload_Exception
59 */
60 public function add_attachment() {
61 if ( ! function_exists( 'wp_generate_attachment_metadata' ) ) {
62 require_once ABSPATH . 'wp-admin/includes/image.php';
63 }
64
65 if ( ! function_exists( 'wp_read_video_metadata' ) ) {
66 require_once ABSPATH . 'wp-admin/includes/media.php';
67 }
68
69 $attachment = wp_insert_attachment(
70 array(
71 'guid' => $this->get_url(),
72 'post_mime_type' => $this->get_type(),
73 'post_title' => preg_replace( '/\.[^.]+$/', '', basename( $this->get_file() ) ),
74 'post_content' => '',
75 'post_status' => 'publish',
76 ),
77 $this->get_file(),
78 0,
79 true
80 );
81
82 if ( is_wp_error( $attachment ) ) {
83 throw new Upload_Exception( esc_html( $attachment->get_error_message() ) );
84 }
85
86 /**
87 * Records who actually uploaded this attachment, so a later request
88 * can't take credit for someone else's upload just because it also
89 * carries the `_jfb_uploaded_by_form` marker.
90 *
91 * A target post ID is deliberately NOT recorded here: at upload time
92 * (field/request parsing) no post has been authorized yet - the Insert
93 * Post action's `ID` property (which would map to a real post) is only
94 * resolved and capability-checked later, when that action itself runs.
95 * Binding to a not-yet-authorized `pid` value from the same request
96 * would just persist the attacker-controlled value the field-level
97 * check exists to distrust.
98 *
99 * Written BEFORE `_jfb_uploaded_by_form` (below), not after: the latter
100 * is the trust gate `Media_Cleanup::is_owned_by_current_actor()` reads
101 * to decide an attachment is plugin-owned at all. If this request were
102 * interrupted between the two writes, an attachment left with the
103 * trust gate set but no uploader identity would fall into that check's
104 * unconditional-allow "legacy, predates the marker system" branch -
105 * recreating the same unrestricted-trust primitive this fix exists to
106 * close. Writing the identity first means an interruption instead
107 * leaves the attachment with neither marker: not yet plugin-owned by
108 * anyone, exactly like an ordinary, unrelated attachment.
109 *
110 * Order alone is not enough, though: `update_post_meta()` is not
111 * guaranteed to persist just because it was called - a plugin hooked
112 * on the `update_post_meta`/`add_post_meta` filters (WP core lets any
113 * of them short-circuit the write by returning non-null) could silently
114 * swallow the identity write while the second call still succeeds. The
115 * explicit `metadata_exists()` check below confirms the identity is
116 * actually there before the trust gate is published; if it is not,
117 * this attachment is left exactly as an ordinary, non-plugin-owned one
118 * (no `_jfb_uploaded_by_form` either), rather than risk a trust gate
119 * with nothing behind it.
120 */
121 update_post_meta( $attachment, '_jfb_uploaded_by_user', get_current_user_id() );
122
123 if ( metadata_exists( 'post', $attachment, '_jfb_uploaded_by_user' ) ) {
124 update_post_meta( $attachment, '_jfb_uploaded_by_form', 1 );
125 }
126
127 wp_update_attachment_metadata(
128 $attachment,
129 wp_generate_attachment_metadata( $attachment, $this->get_file() )
130 );
131
132 $this->set_attachment_id( (string) $attachment );
133
134 // Update file url for performance plugins compatibility
135 $this->set_url( wp_get_attachment_url( $attachment ) );
136 }
137
138 public function set_from_array( array $upload ): Uploaded_File {
139 if ( isset( $upload['file'] ) ) {
140 $file = wp_normalize_path( (string) $upload['file'] );
141
142 // Validate the path against uploads, but keep the original non-realpath
143 // value so WordPress can correctly relativize symlinked upload paths.
144 if ( '' !== self::normalize_allowed_upload_file_path( $file ) ) {
145 $this->file = $file;
146 }
147 }
148 if ( isset( $upload['url'] ) ) {
149 $this->url = esc_url_raw( (string) $upload['url'] );
150 }
151 if ( isset( $upload['type'] ) ) {
152 $this->type = sanitize_mime_type( (string) $upload['type'] );
153 }
154 if ( isset( $upload['id'] ) ) {
155
156 $this->set_attachment_id( (string) absint( $upload['id'] ) );
157 }
158
159 return $this;
160 }
161
162 public function set_attachment_id( string $attachment_id ): Uploaded_File {
163 $this->attachment_id = $attachment_id;
164
165 return $this;
166 }
167
168 /**
169 * @return string
170 */
171 public function get_type(): string {
172 return $this->type;
173 }
174
175 /**
176 * @return string
177 */
178 public function get_file(): string {
179 return $this->file;
180 }
181
182 /**
183 * @return string
184 */
185 public function get_url(): string {
186 return $this->url;
187 }
188
189 /*
190 * Realisation of
191 * \Jet_Form_Builder\Classes\Resources\Media_Block_Value
192 */
193
194 /**
195 * @return string
196 */
197 public function get_attachment_id(): string {
198 return $this->attachment_id;
199 }
200
201
202 /**
203 * @return string
204 */
205 public function get_attachment_url(): string {
206 return $this->get_url();
207 }
208
209 /**
210 * @return array
211 */
212 public function get_attachment_both(): array {
213 return array(
214 'id' => $this->get_attachment_id(),
215 'url' => $this->get_attachment_url(),
216 );
217 }
218
219 public function get_attachment_ids(): array {
220 return array( $this->get_attachment_id() );
221 }
222
223 /*
224 * Realisation of
225 * \Jet_Form_Builder\Classes\Resources\Uploaded_File_Path
226 */
227
228 /**
229 * @return string
230 */
231 public function get_attachment_file(): string {
232 $file = $this->get_file();
233
234 if ( $file ) {
235 $file = self::normalize_allowed_upload_file_path( $file );
236 if ( $file ) {
237 return $file;
238 }
239 }
240
241 $id = $this->get_attachment_id();
242 $url = $this->get_attachment_url();
243
244 if ( ( empty( $id ) || ! is_numeric( $id ) ) && ! empty( $url ) ) {
245 $id = attachment_url_to_postid( $url );
246 }
247
248 $file = get_attached_file( $id );
249
250 if ( ! is_string( $file ) ) {
251 return '';
252 }
253
254 return self::normalize_allowed_upload_file_path( $file );
255 }
256
257 /**
258 * @param string $url
259 */
260 public function set_url( string $url ) {
261 $this->url = esc_url_raw( $url );
262 }
263
264 /**
265 * Normalize path and allow only existing files inside wp-content uploads directory.
266 *
267 * @return string Normalized realpath to a file in uploads, or empty string.
268 */
269 public static function normalize_allowed_upload_file_path( string $file ): string {
270 if ( '' === $file ) {
271 return '';
272 }
273
274 $path = wp_normalize_path( $file );
275 $real = realpath( $path );
276
277 if ( false === $real ) {
278 return '';
279 }
280
281 $real = wp_normalize_path( $real );
282 $real = untrailingslashit( $real );
283
284 $uploads = wp_get_upload_dir();
285 $base = (string) ( $uploads['basedir'] ?? '' );
286
287 if ( '' === $base ) {
288 return '';
289 }
290
291 $base_real = realpath( $base );
292 if ( false === $base_real ) {
293 return '';
294 }
295
296 $base = wp_normalize_path( $base_real );
297 $base = untrailingslashit( $base );
298
299 if ( 0 === strpos( $real, $base . '/' ) && is_file( $real ) ) {
300 return $real;
301 }
302
303 return '';
304 }
305 }
306