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

428 lines 11.3 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 'content_item_name' => [
177 'description' => \__( 'Name of a single content item of this provider.', 'embed-privacy' ),
178 'field_type' => 'input',
179 'name' => 'content_item_name',
180 'title' => \__( 'Content Name', 'embed-privacy' ),
181 ],
182 'regex_default' => [
183 'description' => \sprintf(
184 /* translators: link to documentation */
185 \__( 'Regular expression that will be searched for in the content. See the %s for more information.', 'embed-privacy' ),
186 '<a href="' . \esc_url(
187 \sprintf(
188 /* translators: plugin version */
189 \__( 'https://epiph.yt/en/embed-privacy/documentation/?version=%s#regex-pattern', 'embed-privacy' ),
190 \EMBED_PRIVACY_VERSION
191 )
192 ) . '" target="_blank" rel="noopener noreferrer">' . \esc_html__( 'documentation', 'embed-privacy' ) . '</a>'
193 ),
194 'field_type' => 'input',
195 'name' => 'regex_default',
196 'title' => \__( 'Regex Pattern', 'embed-privacy' ),
197 'validation' => 'allow-links',
198 ],
199 'is_disabled' => [
200 'field_type' => 'input',
201 'name' => 'is_disabled',
202 'title' => \__( 'Disable embed provider', 'embed-privacy' ),
203 'type' => 'checkbox',
204 ],
205 'is_system' => [
206 'field_type' => 'input',
207 'name' => 'is_system',
208 'title' => '',
209 'type' => 'hidden',
210 ],
211 ] );
212 }
213
214 /**
215 * Remove default meta box "Custom Fields”.
216 */
217 public static function remove_default() {
218 foreach ( [ 'normal', 'advanced', 'side' ] as $context ) {
219 \remove_meta_box( 'postcustom', 'epi_embed', $context );
220 }
221 }
222
223 /**
224 * Sanitize an array recursively.
225 *
226 * @param array $array The array to sanitize
227 * @return array The sanitized array
228 */
229 private static function sanitize_array( array $array ) {
230 foreach ( $array as &$value ) {
231 if ( \is_array( $value ) ) {
232 $value = self::sanitize_array( $value );
233 }
234 else {
235 $value = \trim( \sanitize_text_field( \wp_unslash( $value ) ) );
236 }
237 }
238
239 return $array;
240 }
241
242 /**
243 * Save the fields as post meta.
244 *
245 * @param int $post_id The ID of the post
246 * @param \WP_Post $post The post object
247 */
248 public function save( $post_id, $post ) {
249 // ignore other post types
250 if ( $post->post_type !== 'epi_embed' ) {
251 return;
252 }
253
254 if (
255 (
256 // plugin update
257 (
258 ! isset( $_GET['activate'] )
259 || $_GET['activate'] !== 'true'
260 )
261 // manual post update
262 || (
263 ! \get_current_screen()
264 || empty( \get_current_screen()->action )
265 || (
266 \get_current_screen()->action !== 'add'
267 && ! \check_admin_referer( 'update-post_' . $post_id )
268 )
269 )
270 )
271 && \current_action() !== 'save_post'
272 ) {
273 return;
274 }
275
276 // ignore actions to trash the post
277 if (
278 ! empty( $_GET['action'] )
279 && \in_array( \sanitize_text_field( \wp_unslash( $_GET['action'] ) ), [ 'trash', 'untrash' ], true )
280 ) {
281 return;
282 }
283
284 // ignore inline saves
285 if ( ! empty( $_POST['action'] ) && \sanitize_text_field( \wp_unslash( $_POST['action'] ) ) === 'inline-save' ) {
286 return;
287 }
288
289 // verify capability
290 if (
291 ! \defined( 'WP_CLI' ) && ! \current_user_can( 'edit_posts', $post_id )
292 || \defined( 'WP_CLI' ) && ! \WP_CLI
293 ) {
294 \wp_die( new \WP_Error( 403, \esc_html__( 'You are not allowed to edit an embed.', 'embed-privacy' ) ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
295 }
296
297 if ( \defined( 'WP_CLI' ) && \WP_CLI ) {
298 return;
299 }
300
301 foreach ( $this->fields as $field ) {
302 if ( empty( $_POST[ $field['name'] ] ) ) {
303 \delete_post_meta( $post_id, $field['name'] );
304
305 continue;
306 }
307
308 // sanitizing
309 // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
310 if ( \is_array( $_POST[ $field['name'] ] ) ) {
311 $value = self::sanitize_array( \wp_unslash( $_POST[ $field['name'] ] ) );
312 }
313 else if ( ! \str_contains( $field['name'], 'regex' ) ) {
314 $value = \sanitize_text_field( \wp_unslash( $_POST[ $field['name'] ] ) );
315 }
316 else {
317 $value = (string) \wp_unslash( $_POST[ $field['name'] ] );
318 }
319 // phpcs:enable
320
321 \update_post_meta( $post_id, $field['name'], $value );
322 }
323
324 $files = self::validate_files();
325
326 foreach ( $files as $field_name => $file ) {
327 // upload file directly into library
328 $attachment_id = self::upload_file( $file );
329
330 if ( $attachment_id ) {
331 $attachment_id_list[ $field_name ] = $attachment_id;
332 }
333 }
334
335 // store or remove attachment IDs in the database
336 if ( ! empty( $attachment_id_list ) ) {
337 foreach ( $attachment_id_list as $field_name => $attachment_ids ) {
338 // add uploaded files to POST data to prevent deleting data on
339 // second execution of save_post
340 $_POST[ $field_name ] = $attachment_ids;
341
342 \update_post_meta( $post_id, $field_name, $attachment_ids );
343 }
344 }
345 }
346
347 /**
348 * Upload a file as attachment.
349 *
350 * @param array $file The file to upload
351 * @return int The attachment ID
352 */
353 public static function upload_file( array $file ) {
354 // store file in the uploads folder
355 $upload_file = \wp_upload_bits( $file['name'], null, $file['content'] );
356
357 if ( isset( $upload_file['error'] ) && $upload_file['error'] ) {
358 return 0;
359 }
360
361 // get attachment data
362 $attachment = [
363 'post_content' => '',
364 'post_mime_type' => $upload_file['type'],
365 'post_status' => 'inherit',
366 'post_title' => \sanitize_title( $file['name'] ),
367 ];
368 // save the file as attachment
369 $attachment_id = \wp_insert_attachment( $attachment, $upload_file['file'] );
370
371 if ( \is_wp_error( $attachment_id ) ) {
372 return 0;
373 }
374
375 // make wp_generate_attachment_metadata() available
376 // see https://wordpress.stackexchange.com/a/261262
377 include_once \ABSPATH . 'wp-admin/includes/image.php';
378 // generate meta data
379 \wp_update_attachment_metadata( $attachment_id, \wp_generate_attachment_metadata( $attachment_id, $upload_file['file'] ) );
380
381 return $attachment_id;
382 }
383
384 /**
385 * Validate all files.
386 *
387 * @return array The updated form fields
388 */
389 private static function validate_files() {
390 global $wp_filesystem;
391
392 // initialize the WP filesystem if not exists
393 if ( empty( $wp_filesystem ) ) {
394 require_once \ABSPATH . 'wp-admin/includes/file.php';
395 \WP_Filesystem();
396 }
397
398 /**
399 * Set the option names to look for files.
400 *
401 * @param array 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 foreach ( $_FILES as $key => $files ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
411 if ( ! \in_array( $key, $valid_files, true ) ) { // check valid files
412 continue;
413 }
414
415 $validated[ $key ] = [
416 'content' => $wp_filesystem->get_contents( $files['tmp_name'] ),
417 'name' => $files['name'],
418 'tmp_name' => $files['tmp_name'],
419 ];
420 }
421
422 // remove files once processed
423 unset( $_FILES );
424
425 return $validated;
426 }
427 }
428