PluginProbe
Embed Privacy / 1.10.2
Embed Privacy v1.10.2
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 1.10.2, at inc/admin/class-fields.php

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