PluginProbe
Embed Privacy / trunk
Embed Privacy vtrunk
1.14.0 1.13.0 trunk 0.1 1.0.0 1.0.1 1.0.2 1.1.0 1.1.1 1.1.2 1.1.3 1.10.0 1.10.1 1.10.10 1.10.2 1.10.3 1.10.4 1.10.5 1.10.6 1.10.7 1.10.8 1.10.9 1.11.0 1.11.1 1.11.2 All 68 releases
embed-privacy / inc / admin / class-fields.php

class-fields.php in Embed Privacy trunk, at inc/admin/class-fields.php

443 lines 12.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace epiphyt\Embed_Privacy\admin;
3
4 use epiphyt\Embed_Privacy\Embed_Privacy;
5 use WP_Post;
6
7 /**
8 * Admin fields functionality.
9 *
10 * @author Epiphyt
11 * @license GPL2
12 * @package epiphyt\Embed_Privacy
13 * @since 1.10.0
14 */
15 final class Fields {
16 /**
17 * @var array List of fields
18 */
19 public $fields = [];
20
21 /**
22 * Initialize functions.
23 */
24 public function init() {
25 \add_action( 'add_meta_boxes', [ $this, 'add_meta_boxes' ] );
26 \add_action( 'do_meta_boxes', [ self::class, 'remove_default' ] );
27 \add_action( 'init', [ $this, 'register_default' ] );
28 \add_action( 'save_post_epi_embed', [ $this, 'save' ] );
29 \add_filter( 'map_meta_cap', [ self::class, 'disallow_deleting_system_embeds' ], 10, 4 );
30 }
31
32 /**
33 * Add meta boxes.
34 */
35 public function add_meta_boxes() {
36 \add_meta_box( 'embed-privacy-custom-fields', \__( 'Embed Fields', 'embed-privacy' ), [ $this, 'get' ], 'epi_embed', 'normal', 'high' );
37 }
38
39 /**
40 * Disallow deletion of system embeds.
41 *
42 * @param array $caps The current capabilities
43 * @param string $cap The capability to check
44 * @param int $user_id The user ID
45 * @param array $args Additional arguments
46 * @return array The updated capabilities
47 */
48 public static function disallow_deleting_system_embeds( array $caps, $cap, $user_id, array $args ) {
49 if ( $cap !== 'delete_post' ) {
50 return $caps;
51 }
52
53 $post_id = \reset( $args );
54
55 if ( $post_id ) {
56 $post = \get_post( $post_id );
57
58 if (
59 $post instanceof WP_Post
60 && $post->post_type === 'epi_embed'
61 && \get_post_meta( $post->ID, 'is_system', true ) === 'yes'
62 ) {
63 $caps[] = 'do_not_allow';
64
65 return $caps;
66 }
67 }
68
69 return $caps;
70 }
71
72 /**
73 * Get the post meta fields HTML.
74 */
75 public function get() {
76 global $post;
77
78 foreach ( $this->fields as $field ) {
79 if ( $field['field_type'] !== 'input' || empty( $field['type'] ) || $field['type'] !== 'hidden' ) {
80 continue;
81 }
82
83 $field['value'] = (string) \get_post_meta( $post->ID, $field['name'], true );
84 ?>
85 <input type="hidden" name="<?php echo \esc_attr( $field['name'] ); ?>" value="<?php echo \esc_attr( $field['value'] ); ?>">
86 <?php
87 }
88 ?>
89 <table class="form-table" role="presentation">
90 <tbody>
91 <?php
92 foreach ( $this->fields as $field ) {
93 // set default field type if no one is available
94 if ( empty( $field['field_type'] ) ) {
95 $field['field_type'] = 'input';
96 }
97
98 switch ( $field['field_type'] ) {
99 case 'image':
100 Field::get_image( $post->ID, $field );
101 break;
102 case 'input':
103 default:
104 Field::get( $field, $post->ID );
105 break;
106 }
107 }
108
109 /**
110 * Output additional fields.
111 *
112 * @param int $post_id The current post ID
113 */
114 $fields = \apply_filters( 'embed_privacy_editor_fields', $post->ID );
115
116 if ( $fields !== $post->ID && \is_string( $fields ) ) {
117 echo $fields; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
118 }
119 ?>
120 </tbody>
121 </table>
122 <?php
123 }
124
125 /**
126 * Register fields.
127 *
128 * @param array $fields Fields to register
129 */
130 public function register( array $fields = [] ) {
131 /**
132 * Register additional fields.
133 * Use \epiphyt\Embed_Privacy\Fields::get_instance()->register( $fields )
134 * if possible (be careful, as this needs a call after textdomain has been loaded).
135 *
136 * @param array $fields Additional fields
137 */
138 $additional_fields = \apply_filters( 'embed_privacy_register_fields', [] );
139
140 if ( ! \is_array( $additional_fields ) ) {
141 \wp_die(
142 new \WP_Error( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
143 'invalid_fields',
144 \esc_html__( 'Invalid value for additional Embed Privacy fields provided.', 'embed-privacy' )
145 )
146 );
147 }
148
149 // merge fields
150 $this->fields = \array_merge( $this->fields, $fields, $additional_fields );
151
152 /**
153 * Filter all registered fields.
154 *
155 * @param array $fields Registered fields
156 */
157 $this->fields = \apply_filters( 'embed_privacy_fields', $this->fields );
158 }
159
160 /**
161 * Register default fields.
162 */
163 public function register_default() {
164 $this->register( [ // phpcs:ignore SlevomatCodingStandard.Arrays.AlphabeticallySortedByKeys.IncorrectKeyOrder
165 'privacy_policy_url' => [
166 'description' => \__( 'Link to the embed provider’s privacy policy URL.', 'embed-privacy' ),
167 'field_type' => 'input',
168 'name' => 'privacy_policy_url',
169 'title' => \__( 'Privacy Policy URL', 'embed-privacy' ),
170 'type' => 'url',
171 ],
172 'background_image' => [
173 'field_type' => 'image',
174 'name' => 'background_image',
175 'title' => \__( 'Background Image', 'embed-privacy' ),
176 ],
177 'content_item_name' => [
178 'description' => \__( 'Name of a single content item of this provider.', 'embed-privacy' ),
179 'field_type' => 'input',
180 'name' => 'content_item_name',
181 'title' => \__( 'Content Name', 'embed-privacy' ),
182 ],
183 'regex_default' => [
184 'description' => \sprintf(
185 /* translators: link to documentation */
186 \__( 'Regular expression that will be searched for in the content. See the %s for more information.', 'embed-privacy' ),
187 '<a href="' . \esc_url(
188 \sprintf(
189 /* translators: plugin version */
190 \__( 'https://epiph.yt/en/embed-privacy/documentation/?version=%s#regex-pattern', 'embed-privacy' ),
191 \EMBED_PRIVACY_VERSION
192 )
193 ) . '" target="_blank" rel="noopener noreferrer">' . \esc_html__( 'documentation', 'embed-privacy' ) . '</a>'
194 ),
195 'field_type' => 'input',
196 'name' => 'regex_default',
197 'title' => \__( 'Regex Pattern', 'embed-privacy' ),
198 'validation' => 'allow-links',
199 ],
200 'is_disabled' => [
201 'field_type' => 'input',
202 'name' => 'is_disabled',
203 'title' => \__( 'Disable embed provider', 'embed-privacy' ),
204 'type' => 'checkbox',
205 ],
206 'is_system' => [
207 'field_type' => 'input',
208 'name' => 'is_system',
209 'title' => '',
210 'type' => 'hidden',
211 ],
212 ] );
213 }
214
215 /**
216 * Remove default meta box "Custom Fields”.
217 */
218 public static function remove_default() {
219 foreach ( [ 'normal', 'advanced', 'side' ] as $context ) {
220 \remove_meta_box( 'postcustom', 'epi_embed', $context );
221 }
222 }
223
224 /**
225 * Sanitize an array recursively.
226 *
227 * @param array $array The array to sanitize
228 * @return array The sanitized array
229 */
230 private static function sanitize_array( array $array ) {
231 foreach ( $array as &$value ) {
232 if ( \is_array( $value ) ) {
233 $value = self::sanitize_array( $value );
234 }
235 else {
236 $value = \trim( \sanitize_text_field( \wp_unslash( $value ) ) );
237 }
238 }
239
240 return $array;
241 }
242
243 /**
244 * Save the fields as post meta.
245 *
246 * @since 1.12.0 Deprecated second parameter
247 *
248 * @param int $post_id The ID of the post
249 * @param \WP_Post|false $deprecated Deprecated. The post object
250 */
251 public function save( $post_id, $deprecated = false ) {
252 if ( $deprecated !== false ) {
253 \_doing_it_wrong(
254 __METHOD__,
255 \esc_html__( 'The second parameter is deprecated. Please remove it from your method call.', 'embed-privacy' ),
256 '1.12.0'
257 );
258 }
259
260 if (
261 (
262 // plugin update
263 (
264 ! isset( $_GET['activate'] )
265 || $_GET['activate'] !== 'true'
266 )
267 // manual post update
268 || (
269 ! \get_current_screen()
270 || empty( \get_current_screen()->action )
271 || (
272 \get_current_screen()->action !== 'add'
273 && ! \check_admin_referer( 'update-post_' . $post_id )
274 )
275 )
276 )
277 && \current_action() !== 'save_post_epi_embed'
278 ) {
279 return;
280 }
281
282 // ignore actions to trash the post
283 if (
284 ! empty( $_GET['action'] )
285 && \in_array( \sanitize_text_field( \wp_unslash( $_GET['action'] ) ), [ 'trash', 'untrash' ], true )
286 ) {
287 return;
288 }
289
290 // ignore inline saves
291 if ( ! empty( $_POST['action'] ) && \sanitize_text_field( \wp_unslash( $_POST['action'] ) ) === 'inline-save' ) {
292 return;
293 }
294
295 // verify capability
296 if (
297 ! \defined( 'WP_CLI' ) && ! \current_user_can( 'edit_posts', $post_id )
298 || \defined( 'WP_CLI' ) && ! \WP_CLI
299 ) {
300 \wp_die( new \WP_Error( 403, \esc_html__( 'You are not allowed to edit an embed.', 'embed-privacy' ) ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
301 }
302
303 if ( \defined( 'WP_CLI' ) && \WP_CLI ) {
304 return;
305 }
306
307 foreach ( $this->fields as $field ) {
308 if ( empty( $_POST[ $field['name'] ] ) ) {
309 \delete_post_meta( $post_id, $field['name'] );
310
311 continue;
312 }
313
314 // sanitizing
315 // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
316 if ( \is_array( $_POST[ $field['name'] ] ) ) {
317 $value = self::sanitize_array( \wp_unslash( $_POST[ $field['name'] ] ) );
318 }
319 else if ( ! \str_contains( $field['name'], 'regex' ) ) {
320 $value = \sanitize_text_field( \wp_unslash( $_POST[ $field['name'] ] ) );
321 }
322 else {
323 $value = (string) \wp_unslash( $_POST[ $field['name'] ] );
324 }
325 // phpcs:enable
326
327 \update_post_meta( $post_id, $field['name'], $value );
328 }
329
330 $files = self::validate_files();
331
332 foreach ( $files as $field_name => $file ) {
333 // upload file directly into library
334 $attachment_id = self::upload_file( $file );
335
336 if ( $attachment_id ) {
337 $attachment_id_list[ $field_name ] = $attachment_id;
338 }
339 }
340
341 // store or remove attachment IDs in the database
342 if ( ! empty( $attachment_id_list ) ) {
343 foreach ( $attachment_id_list as $field_name => $attachment_ids ) {
344 // add uploaded files to POST data to prevent deleting data on
345 // second execution of save_post
346 $_POST[ $field_name ] = $attachment_ids;
347
348 \update_post_meta( $post_id, $field_name, $attachment_ids );
349 }
350 }
351 }
352
353 /**
354 * Upload a file as attachment.
355 *
356 * @param array $file The file to upload
357 * @return int The attachment ID
358 */
359 public static function upload_file( array $file ) {
360 // store file in the uploads folder
361 $upload_file = \wp_upload_bits( $file['name'], null, $file['content'] );
362
363 if ( isset( $upload_file['error'] ) && $upload_file['error'] ) {
364 return 0;
365 }
366
367 // get attachment data
368 $attachment = [
369 'post_content' => '',
370 'post_mime_type' => $upload_file['type'],
371 'post_status' => 'inherit',
372 'post_title' => \sanitize_title( $file['name'] ),
373 ];
374 // save the file as attachment
375 $attachment_id = \wp_insert_attachment( $attachment, $upload_file['file'] );
376
377 if ( \is_wp_error( $attachment_id ) ) {
378 return 0;
379 }
380
381 // make wp_generate_attachment_metadata() available
382 // see https://wordpress.stackexchange.com/a/261262
383 include_once \ABSPATH . 'wp-admin/includes/image.php';
384 // generate meta data
385 \wp_update_attachment_metadata( $attachment_id, \wp_generate_attachment_metadata( $attachment_id, $upload_file['file'] ) );
386
387 return $attachment_id;
388 }
389
390 /**
391 * Validate all files.
392 *
393 * @return array The updated form fields
394 */
395 private static function validate_files() {
396 $wp_filesystem = Embed_Privacy::get_wp_filesystem();
397
398 /**
399 * Set the option names to look for files.
400 *
401 * @param array $valid_files The default name list
402 */
403 $valid_files = \apply_filters( 'embed_privacy_valid_files', [ 'background_image' ] );
404 $validated = [];
405
406 if ( empty( $_FILES ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
407 return $validated;
408 }
409
410 /**
411 * Filter the allowed mime types for uploaded files.
412 *
413 * @since 1.13.0
414 *
415 * @param string[] $allowed_mime_types List of allowed mime types
416 */
417 $allowed_mime_types = (array) \apply_filters( 'embed_privacy_allowed_file_mime_types', [ 'image/gif', 'image/jpeg', 'image/png', 'image/webp' ] );
418
419 foreach ( $_FILES as $key => $files ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
420 if ( ! \in_array( $key, $valid_files, true ) ) { // check valid files
421 continue;
422 }
423
424 $filetype = \wp_check_filetype( $files['name'] );
425
426 if ( empty( $filetype['type'] ) || ! \in_array( $filetype['type'], $allowed_mime_types, true ) ) {
427 continue;
428 }
429
430 $validated[ $key ] = [
431 'content' => $wp_filesystem->get_contents( $files['tmp_name'] ),
432 'name' => \sanitize_file_name( $files['name'] ),
433 'tmp_name' => $files['tmp_name'],
434 ];
435 }
436
437 // remove files once processed
438 unset( $_FILES );
439
440 return $validated;
441 }
442 }
443