PluginProbe
Embed Privacy / 1.9.1
Embed Privacy v1.9.1
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 / class-fields.php

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

592 lines 17.1 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;
3
4 use WP_Error;
5
6 /**
7 * Custom fields for Embed Privacy.
8 *
9 * @since 1.2.0
10 *
11 * @author Epiphyt
12 * @license GPL2
13 * @package epiphyt\Embed_Privacy
14 */
15 class Fields {
16 /**
17 * @var array Fields to output
18 */
19 public $fields = [];
20
21 /**
22 * @var \epiphyt\Embed_Privacy\Fields
23 */
24 private static $instance;
25
26 /**
27 * Post Type constructor.
28 */
29 public function __construct() {
30 self::$instance = $this;
31 }
32
33 /**
34 * Initialize functions.
35 */
36 public function init() {
37 \add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_assets' ] );
38 \add_action( 'add_meta_boxes', [ $this, 'add_meta_boxes' ] );
39 \add_action( 'do_meta_boxes', [ $this, 'remove_default_fields' ] );
40 \add_action( 'init', [ $this, 'register_default_fields' ] );
41 \add_action( 'save_post', [ $this, 'save_fields' ], 10, 2 );
42 }
43
44 /**
45 * Add meta boxes.
46 */
47 public function add_meta_boxes() {
48 \add_meta_box( 'embed-privacy-custom-fields', \__( 'Embed Fields', 'embed-privacy' ), [ $this, 'get_the_fields_html' ], 'epi_embed', 'normal', 'high' );
49 }
50
51 /**
52 * Enqueue admin assets.
53 *
54 * @param string $hook The current hook
55 */
56 public function enqueue_admin_assets( $hook ) {
57 // we need it just on the post page
58 if ( ( $hook !== 'post.php' && $hook !== 'post-new.php' ) || \get_current_screen()->id !== 'epi_embed' ) {
59 return;
60 }
61
62 $suffix = ( \defined( 'WP_DEBUG' ) && WP_DEBUG ? '' : '.min' );
63 $script_path = \plugin_dir_path( Embed_Privacy::get_instance()->plugin_file ) . 'assets/js/admin/image-upload' . $suffix . '.js';
64 $script_url = \plugin_dir_url( Embed_Privacy::get_instance()->plugin_file ) . 'assets/js/admin/image-upload' . $suffix . '.js';
65
66 \wp_enqueue_script( 'embed-privacy-admin-image-upload', $script_url, [ 'jquery' ], \filemtime( $script_path ), true );
67
68 $style_path = \plugin_dir_path( Embed_Privacy::get_instance()->plugin_file ) . 'assets/style/embed-privacy-admin' . $suffix . '.css';
69 $style_url = \plugin_dir_url( Embed_Privacy::get_instance()->plugin_file ) . 'assets/style/embed-privacy-admin' . $suffix . '.css';
70
71 \wp_enqueue_style( 'embed-privacy-admin-style', $style_url, [], \filemtime( $style_path ) );
72 }
73
74 /**
75 * Get a unique instance of the class.
76 *
77 * @return \epiphyt\Embed_Privacy\Fields The single instance of this class
78 */
79 public static function get_instance() {
80 if ( self::$instance === null ) {
81 self::$instance = new self();
82 }
83
84 return self::$instance;
85 }
86
87 /**
88 * Get the post meta fields HTML.
89 */
90 public function get_the_fields_html() {
91 global $post;
92
93 foreach ( $this->fields as $field ) {
94 if ( $field['field_type'] !== 'input' || empty( $field['type'] ) || $field['type'] !== 'hidden' ) {
95 continue;
96 }
97
98 $field['value'] = (string) \get_post_meta( $post->ID, $field['name'], true );
99 ?>
100 <input type="hidden" name="<?php echo \esc_attr( $field['name'] ); ?>" value="<?php echo \esc_attr( $field['value'] ); ?>">
101 <?php
102 }
103 ?>
104 <table class="form-table" role="presentation">
105 <tbody>
106 <?php
107 foreach ( $this->fields as $field ) {
108 // set default field type if no one is available
109 if ( empty( $field['field_type'] ) ) {
110 $field['field_type'] = 'input';
111 }
112
113 switch ( $field['field_type'] ) {
114 case 'image':
115 $this->get_the_image_field_html( $post->ID, $field );
116 break;
117 case 'input':
118 default:
119 $this->get_the_input_field_html( $post->ID, $field );
120 break;
121 }
122 }
123
124 /**
125 * Output additional fields.
126 *
127 * @param int $post_id The current post ID
128 */
129 $fields = \apply_filters( 'embed_privacy_editor_fields', $post->ID );
130
131 if ( $fields !== $post->ID ) {
132 echo $fields; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
133 }
134 ?>
135 </tbody>
136 </table>
137 <?php
138 }
139
140 /**
141 * Output an image field.
142 *
143 * @param int $post_id The current post ID
144 * @param array $attributes An array with attributes
145 */
146 public function get_the_image_field_html( $post_id, array $attributes ) {
147 $attributes = \wp_parse_args( $attributes, [
148 'classes' => '',
149 'description' => '',
150 'name' => '',
151 'single' => true,
152 'title' => '',
153 ] );
154
155 if ( empty( $attributes['name'] ) || empty( $attributes['title'] ) ) {
156 return;
157 }
158
159 $attributes['value'] = (string) \get_post_meta( $post_id, $attributes['name'], $attributes['single'] );
160 $image = \wp_get_attachment_image( (int) $attributes['value'] );
161 ?>
162 <tr>
163 <th scope="row">
164 <label for="<?php echo \esc_attr( $attributes['name'] ); ?>"><?php echo \esc_html( $attributes['title'] ); ?></label>
165 </th>
166 <td class="embed-privacy-image-item">
167 <input type="hidden" name="<?php echo \esc_attr( $attributes['name'] ); ?>" value="<?php echo \esc_attr( $attributes['value'] ); ?>" class="embed-privacy-image-input">
168
169 <div class="embed-privacy-image-input-container<?php echo ( ! empty( $attributes['value'] ) ? ' embed-privacy-hidden' : '' ); ?>">
170 <button type="button" class="button button-secondary embed-privacy-image-upload"><?php \esc_html_e( 'Upload or choose file', 'embed-privacy' ); ?></button>
171 </div>
172
173 <div class="embed-privacy-image-container<?php echo ( empty( $attributes['value'] ) ? ' embed-privacy-hidden' : '' ); ?>">
174 <?php echo $image; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
175 <span class="dashicons dashicons-no embed-privacy-icon embed-privacy-remove-image"></span>
176 </div>
177
178 <?php if ( ! empty( $attributes['description'] ) ) : ?>
179 <p><?php echo \esc_html( $attributes['description'] ); ?></p>
180 <?php endif; ?>
181 </td>
182 </tr>
183 <?php
184 }
185
186 /**
187 * Output a single input field depending on given attributes.
188 *
189 * @param int $post_id The current post ID
190 * @param array $attributes An array with attribues
191 */
192 public function get_the_input_field_html( $post_id, array $attributes ) {
193 $attributes = \wp_parse_args( $attributes, [
194 'classes' => 'regular-text',
195 'description' => '',
196 'name' => '',
197 'option_type' => 'meta',
198 'single' => true,
199 'title' => '',
200 'type' => 'text',
201 'validation' => '',
202 ] );
203
204 if ( empty( $attributes['name'] ) || empty( $attributes['title'] ) ) {
205 return;
206 }
207
208 if ( $attributes['option_type'] === 'meta' ) {
209 $current_value = (string) \get_post_meta( $post_id, $attributes['name'], $attributes['single'] );
210 }
211 else {
212 $current_value = (string) \get_option( $attributes['name'] );
213 }
214
215 if ( ! \in_array( $attributes['type'], [ 'checkbox', 'hidden', 'radio' ], true ) ) :
216 \ob_start();
217 ?>
218 <input type="<?php echo \esc_attr( $attributes['type'] ); ?>" name="<?php echo \esc_attr( $attributes['name'] ); ?>" id="<?php echo \esc_attr( $attributes['name'] ); ?>" value="<?php echo esc_attr( $current_value ); ?>" class="<?php echo esc_attr( $attributes['classes'] ); ?>">
219 <?php if ( ! empty( $attributes['description'] ) ) : ?>
220 <p>
221 <?php
222 if ( empty( $attributes['validation'] ) ) {
223 echo \esc_html( $attributes['description'] );
224 }
225 else if ( $attributes['validation'] === 'allow-links' ) {
226 echo \wp_kses( $attributes['description'], [
227 'a' => [
228 'href' => true,
229 'rel' => true,
230 'target' => true,
231 ],
232 ] );
233 }
234 ?>
235 </p>
236 <?php
237 endif;
238 $input = \ob_get_clean();
239
240 if ( $attributes['option_type'] === 'option' ) {
241 echo $input; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
242
243 return;
244 }
245 ?>
246 <tr>
247 <th scope="row">
248 <label for="<?php echo \esc_attr( $attributes['name'] ); ?>"><?php echo \esc_html( $attributes['title'] ); ?></label>
249 </th>
250 <td>
251 <?php echo $input; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
252 </td>
253 </tr>
254 <?php
255 else :
256 // set default value for checkboxes and radio buttons
257 if ( empty( $attributes['value'] ) ) {
258 $attributes['value'] = 'yes';
259 }
260
261 \ob_start();
262 ?>
263 <label for="<?php echo \esc_attr( $attributes['name'] ); ?>"><input type="<?php echo \esc_attr( $attributes['type'] ); ?>" name="<?php echo \esc_attr( $attributes['name'] ); ?>" id="<?php echo \esc_attr( $attributes['name'] ); ?>" value="<?php echo \esc_attr( $attributes['value'] ); ?>" class="<?php echo \esc_attr( $attributes['classes'] ); ?>"<?php \checked( $current_value, $attributes['value'] ); ?>> <?php echo \esc_html( $attributes['title'] ); ?></label>
264 <?php if ( ! empty( $attributes['description'] ) ) : ?>
265 <p>
266 <?php
267 if ( empty( $attributes['validation'] ) ) {
268 echo \esc_html( $attributes['description'] );
269 }
270 else if ( $attributes['validation'] === 'allow-links' ) {
271 echo \wp_kses( $attributes['description'], [
272 'a' => [
273 'href' => true,
274 'rel' => true,
275 'target' => true,
276 ],
277 ] );
278 }
279 ?>
280 </p>
281 <?php
282 endif;
283 $input = \ob_get_clean();
284
285 if ( $attributes['option_type'] === 'option' ) {
286 echo $input; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
287
288 return;
289 }
290 ?>
291 <tr>
292 <th scope="row"></th>
293 <td>
294 <?php echo $input; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
295 </td>
296 </tr>
297 <?php
298 endif;
299 }
300
301 /**
302 * Register fields.
303 *
304 * @param array $fields Fields to register
305 */
306 public function register( array $fields = [] ) {
307 /**
308 * Register additional fields.
309 * Use \epiphyt\Embed_Privacy\Fields::get_instance()->register( $fields )
310 * if possible (be careful, as this needs a call after textdomain has been loaded).
311 *
312 * @param array $fields Additional fields
313 */
314 $additional_fields = \apply_filters( 'embed_privacy_register_fields', [] );
315
316 if ( ! \is_array( $additional_fields ) ) {
317 \wp_die( new WP_Error( 'invalid_fields', \esc_html__( 'Invalid value for additional Embed Privacy fields provided.', 'embed-privacy' ) ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
318 }
319
320 // merge fields
321 $this->fields = \array_merge( $this->fields, $fields, $additional_fields );
322
323 /**
324 * Filter all registered fields.
325 *
326 * @param array $fields Registered fields
327 */
328 $this->fields = \apply_filters( 'embed_privacy_fields', $this->fields );
329 }
330
331 /**
332 * Register default fields.
333 */
334 public function register_default_fields() {
335 $this->register( [
336 'privacy_policy_url' => [
337 'description' => \__( 'Link to the embed provider’s privacy policy URL.', 'embed-privacy' ),
338 'field_type' => 'input',
339 'name' => 'privacy_policy_url',
340 'title' => \__( 'Privacy Policy URL', 'embed-privacy' ),
341 'type' => 'url',
342 ],
343 'background_image' => [
344 'field_type' => 'image',
345 'name' => 'background_image',
346 'title' => \__( 'Background Image', 'embed-privacy' ),
347 ],
348 'regex_default' => [
349 'description' => \sprintf(
350 /* translators: link to documentation */
351 __( 'Regular expression that will be searched for in the content. See the %s for more information.', 'embed-privacy' ),
352 '<a href="' . \esc_url(
353 \sprintf(
354 /* translators: plugin version */
355 __( 'https://epiph.yt/en/embed-privacy/documentation/?version=%s#regex-pattern', 'embed-privacy' ),
356 \EMBED_PRIVACY_VERSION
357 )
358 ) . '" target="_blank" rel="noopener noreferrer">' . \esc_html__( 'documentation', 'embed-privacy' ) . '</a>'
359 ),
360 'field_type' => 'input',
361 'name' => 'regex_default',
362 'title' => \__( 'Regex Pattern', 'embed-privacy' ),
363 'validation' => 'allow-links',
364 ],
365 'is_disabled' => [
366 'field_type' => 'input',
367 'name' => 'is_disabled',
368 'title' => \__( 'Disable embed provider', 'embed-privacy' ),
369 'type' => 'checkbox',
370 ],
371 'is_system' => [
372 'field_type' => 'input',
373 'name' => 'is_system',
374 'title' => '',
375 'type' => 'hidden',
376 ],
377 ] );
378 }
379
380 /**
381 * Remove default meta box "Custom Fields”.
382 */
383 public function remove_default_fields() {
384 foreach ( [ 'normal', 'advanced', 'side' ] as $context ) {
385 \remove_meta_box( 'postcustom', 'epi_embed', $context );
386 }
387 }
388
389 /**
390 * Sanitize an array recursively.
391 *
392 * @param array $array The array to sanitize
393 * @return array The sanitized array
394 */
395 private function sanitize_array( array $array ) {
396 foreach ( $array as &$value ) {
397 if ( \is_array( $value ) ) {
398 $value = $this->sanitize_array( $value );
399 }
400 else {
401 $value = \trim( \sanitize_text_field( \wp_unslash( $value ) ) );
402 }
403 }
404
405 return $array;
406 }
407
408 /**
409 * Save the fields as post meta.
410 *
411 * @param int $post_id The ID of the post
412 * @param \WP_Post $post The post object
413 */
414 public function save_fields( $post_id, $post ) {
415 // ignore other post types
416 if ( $post->post_type !== 'epi_embed' ) {
417 return;
418 }
419
420 if (
421 (
422 // plugin update
423 (
424 ! isset( $_GET['activate'] )
425 || $_GET['activate'] !== 'true'
426 )
427 // manual post update
428 || (
429 ! \get_current_screen()
430 || empty( \get_current_screen()->action )
431 || (
432 \get_current_screen()->action !== 'add'
433 && ! \check_admin_referer( 'update-post_' . $post_id )
434 )
435 )
436 )
437 && \current_action() !== 'save_post'
438 ) {
439 return;
440 }
441
442 // ignore actions to trash the post
443 if ( ! empty( $_GET['action'] ) && \in_array( \sanitize_text_field( \wp_unslash( $_GET['action'] ) ), [ 'trash', 'untrash' ], true ) ) {
444 return;
445 }
446
447 // ignore inline saves
448 if ( ! empty( $_POST['action'] ) && \sanitize_text_field( \wp_unslash( $_POST['action'] ) ) === 'inline-save' ) {
449 return;
450 }
451
452 // verify capability
453 if (
454 ! \defined( 'WP_CLI' ) && ! \current_user_can( 'edit_posts', $post_id )
455 || \defined( 'WP_CLI' ) && ! \WP_CLI
456 ) {
457 \wp_die( new WP_Error( 403, \esc_html__( 'You are not allowed to edit an embed.', 'embed-privacy' ) ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
458 }
459
460 if ( \defined( 'WP_CLI' ) && \WP_CLI ) {
461 return;
462 }
463
464 foreach ( $this->fields as $field ) {
465 if ( empty( $_POST[ $field['name'] ] ) ) {
466 \delete_post_meta( $post_id, $field['name'] );
467
468 continue;
469 }
470
471 // sanitizing
472 // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
473 if ( \is_array( $_POST[ $field['name'] ] ) ) {
474 $value = $this->sanitize_array( \wp_unslash( $_POST[ $field['name'] ] ) );
475 }
476 else if ( \strpos( $field['name'], 'regex' ) === false ) {
477 $value = \sanitize_text_field( \wp_unslash( $_POST[ $field['name'] ] ) );
478 }
479 else {
480 $value = (string) \wp_unslash( $_POST[ $field['name'] ] );
481 }
482 // phpcs:enable
483
484 \update_post_meta( $post_id, $field['name'], $value );
485 }
486
487 $files = $this->validate_files();
488
489 foreach ( $files as $field_name => $file ) {
490 // upload file directly into library
491 $attachment_id = $this->upload_file( $file );
492
493 if ( $attachment_id ) {
494 $attachment_id_list[ $field_name ] = $attachment_id;
495 }
496 }
497
498 // store or remove attachment IDs in the database
499 if ( ! empty( $attachment_id_list ) ) {
500 foreach ( $attachment_id_list as $field_name => $attachment_ids ) {
501 // add uploaded files to POST data to prevent deleting data on
502 // second execution of save_post
503 $_POST[ $field_name ] = $attachment_ids;
504
505 \update_post_meta( $post_id, $field_name, $attachment_ids );
506 }
507 }
508 }
509
510 /**
511 * Upload a file as attachment.
512 *
513 * @param array $file The file to upload
514 * @return int The attachment ID
515 */
516 public function upload_file( array $file ) {
517 // store file in the uploads folder
518 $upload_file = \wp_upload_bits( $file['name'], null, $file['content'] );
519
520 if ( isset( $upload_file['error'] ) && $upload_file['error'] ) {
521 return 0;
522 }
523
524 // get attachment data
525 $attachment = [
526 'post_mime_type' => $upload_file['type'],
527 'post_title' => \sanitize_title( $file['name'] ),
528 'post_content' => '',
529 'post_status' => 'inherit',
530 ];
531 // save the file as attachment
532 $attachment_id = \wp_insert_attachment( $attachment, $upload_file['file'] );
533
534 if ( is_wp_error( $attachment_id ) ) {
535 return 0;
536 }
537
538 // make wp_generate_attachment_metadata() available
539 // see https://wordpress.stackexchange.com/a/261262
540 include_once ABSPATH . 'wp-admin/includes/image.php';
541 // generate meta data
542 \wp_update_attachment_metadata( $attachment_id, \wp_generate_attachment_metadata( $attachment_id, $upload_file['file'] ) );
543
544 return $attachment_id;
545 }
546
547 /**
548 * Validate all files.
549 *
550 * @return array The updated form fields
551 */
552 private function validate_files() {
553 global $wp_filesystem;
554
555 // initialize the WP filesystem if not exists
556 if ( empty( $wp_filesystem ) ) {
557 require_once ABSPATH . 'wp-admin/includes/file.php';
558 \WP_Filesystem();
559 }
560
561 /**
562 * Set the option names to look for files.
563 *
564 * @param array The default name list
565 */
566 $valid_files = \apply_filters( 'embed_privacy_valid_files', [ 'background_image' ] );
567 $validated = [];
568
569 if ( empty( $_FILES ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
570 return $validated;
571 }
572
573 foreach ( $_FILES as $key => $files ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
574 // check valid files
575 if ( ! \in_array( $key, $valid_files, true ) ) {
576 continue;
577 }
578
579 $validated[ $key ] = [
580 'name' => $files['name'],
581 'content' => $wp_filesystem->get_contents( $files['tmp_name'] ),
582 'tmp_name' => $files['tmp_name'],
583 ];
584 }
585
586 // remove files once processed
587 unset( $_FILES );
588
589 return $validated;
590 }
591 }
592