PluginProbe
Sharing Image / trunk
Sharing Image vtrunk
3.10 trunk 2.0 2.0.0 2.0.1 2.0.10 2.0.11 2.0.12 2.0.13 2.0.14 2.0.15 2.0.16 2.0.17 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 3.0 3.1 3.2 3.3 All 29 releases
sharing-image / classes / class-widget.php

class-widget.php in Sharing Image trunk, at classes/class-widget.php

1,125 lines 28.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Display widgets and sidebars on post and term editors screens.
4 *
5 * @package sharing-image
6 * @author Anton Lukin
7 */
8
9 namespace Sharing_Image;
10
11 use WP_Error;
12 use Exception;
13
14 if ( ! defined( 'ABSPATH' ) ) {
15 die;
16 }
17
18 /**
19 * Widget class.
20 *
21 * @class Widget
22 */
23 class Widget {
24 /**
25 * Post meta key to store poster image.
26 *
27 * @var string
28 */
29 const META_SOURCE = '_sharing_image';
30
31 /**
32 * Post meta key to store poster image fielset.
33 *
34 * @var string
35 */
36 const META_FIELDSET = '_sharing_image_fieldset';
37
38 /**
39 * Common nonce for settings tabs.
40 *
41 * @var string
42 */
43 const WIDGET_NONCE = 'sharing-image-widget';
44
45 /**
46 * Init class actions and filters.
47 */
48 public static function init() {
49 add_action( 'init', array( __CLASS__, 'init_post_widget' ) );
50 add_action( 'init', array( __CLASS__, 'init_post_sidebar' ) );
51 add_action( 'init', array( __CLASS__, 'init_taxonomy_widget' ) );
52
53 // Generate poster handlers.
54 add_action( 'wp_ajax_sharing_image_generate', array( __CLASS__, 'handle_ajax_generator' ) );
55 add_action( 'rest_api_init', array( __CLASS__, 'add_generate_endpoint' ) );
56
57 // Try to autogenerate poster if it is needed.
58 add_action( 'wp_after_insert_post', array( __CLASS__, 'prepare_autogenerated_poster' ), 20, 2 );
59 }
60
61 /**
62 * Init post sidebar for gutenberg enabled dashboard.
63 */
64 public static function init_post_sidebar() {
65 if ( Config::is_hidden_post_widget() ) {
66 return;
67 }
68
69 $schema = array(
70 'type' => 'object',
71 'properties' => array(
72 'poster' => array(
73 'type' => 'string',
74 ),
75 'width' => array(
76 'type' => 'integer',
77 ),
78 'height' => array(
79 'type' => 'integer',
80 ),
81 'template' => array(
82 'type' => 'string',
83 ),
84 'mode' => array(
85 'type' => 'string',
86 ),
87 ),
88 );
89
90 register_meta(
91 'post',
92 self::META_SOURCE,
93 array(
94 'type' => 'object',
95 'show_in_rest' => array(
96 'schema' => $schema,
97 ),
98 'single' => true,
99 'auth_callback' => function ( $allowed, $meta_key, $object_id ) {
100 return current_user_can( 'edit_post', $object_id );
101 },
102 'sanitize_callback' => array( __CLASS__, 'sanitize_source' ),
103 )
104 );
105
106 register_meta(
107 'post',
108 self::META_FIELDSET,
109 array(
110 'type' => 'object',
111 'show_in_rest' => array(
112 'schema' => array(
113 'type' => 'object',
114 'properties' => array(),
115 'additionalProperties' => array(
116 'type' => array( 'string', 'integer' ),
117 ),
118 ),
119 ),
120 'single' => true,
121 'auth_callback' => function ( $allowed, $meta_key, $object_id ) {
122 return current_user_can( 'edit_post', $object_id );
123 },
124 'sanitize_callback' => array( __CLASS__, 'sanitize_fieldset' ),
125 )
126 );
127
128 // Add required assets and objects.
129 add_action( 'enqueue_block_editor_assets', array( __CLASS__, 'enqueue_sidebar_assets' ) );
130 }
131
132 /**
133 * Init widget on post editing page.
134 */
135 public static function init_post_widget() {
136 if ( Config::is_hidden_post_widget() ) {
137 return;
138 }
139
140 add_action( 'add_meta_boxes', array( __CLASS__, 'add_metabox' ) );
141
142 // Handle actions on post save.
143 add_action( 'save_post', array( __CLASS__, 'save_post_widget' ), 10 );
144
145 // Add required assets and objects.
146 add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_metabox_assets' ) );
147 }
148
149 /**
150 * Init widget on taxonomy term editing page.
151 */
152 public static function init_taxonomy_widget() {
153 // Skip if the Premium is not active.
154 if ( ! Premium::is_premium_features() ) {
155 return;
156 }
157
158 $taxonomies = self::get_widget_taxonomies();
159
160 foreach ( $taxonomies as $taxonomy ) {
161 // Display taxonomy term widget.
162 add_action( $taxonomy . '_edit_form', array( __CLASS__, 'display_widget' ), 10, 2 );
163
164 // Save taxonomy term meta.
165 add_action( 'edited_' . $taxonomy, array( __CLASS__, 'save_taxonomy_widget' ) );
166
167 // Enqueue term assets.
168 add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_taxonomy_assets' ) );
169 }
170 }
171
172 /**
173 * Add metabox to post editing page.
174 */
175 public static function add_metabox() {
176 add_meta_box(
177 'sharing-image-metabox',
178 esc_html__( 'Sharing Image', 'sharing-image' ),
179 array( __CLASS__, 'display_widget' ),
180 self::get_metabox_post_types(),
181 'side',
182 'high',
183 array(
184 '__back_compat_meta_box' => true,
185 )
186 );
187 }
188
189 /**
190 * Add metabox scripts and styles to admin page.
191 *
192 * @param string $hook_suffix The current admin page.
193 */
194 public static function enqueue_metabox_assets( $hook_suffix ) {
195 if ( ! in_array( $hook_suffix, array( 'post.php', 'post-new.php' ), true ) ) {
196 return;
197 }
198
199 $screen = get_current_screen();
200
201 if ( ! in_array( $screen->post_type, self::get_metabox_post_types(), true ) ) {
202 return;
203 }
204
205 $post = get_post();
206
207 if ( use_block_editor_for_post( $post ) ) {
208 return;
209 }
210
211 // Get post meta for current post ID.
212 $data = self::create_script_object( 'post', $post->ID );
213
214 $data['meta'] = array(
215 'source' => get_post_meta( $post->ID, self::META_SOURCE, true ),
216 'fieldset' => get_post_meta( $post->ID, self::META_FIELDSET, true ),
217 );
218
219 /**
220 * Filter widget script object.
221 *
222 * @param array $object Array of widget script object.
223 */
224 self::enqueue_widget_scripts( $data );
225 }
226
227 /**
228 * Add widget scripts and styles to admin page.
229 *
230 * @param string $hook_suffix The current admin page.
231 */
232 public static function enqueue_taxonomy_assets( $hook_suffix ) {
233 if ( 'term.php' !== $hook_suffix ) {
234 return;
235 }
236
237 $screen = get_current_screen();
238
239 if ( ! in_array( $screen->taxonomy, self::get_widget_taxonomies(), true ) ) {
240 return;
241 }
242
243 // phpcs:disable WordPress.Security.NonceVerification
244 if ( ! isset( $_REQUEST['tag_ID'] ) ) {
245 return;
246 }
247
248 $term_id = absint( $_REQUEST['tag_ID'] );
249 // phpcs:enable WordPress.Security.NonceVerification
250
251 $data = self::create_script_object( 'term', $term_id );
252
253 $data['meta'] = array(
254 'source' => get_term_meta( $term_id, self::META_SOURCE, true ),
255 'fieldset' => get_term_meta( $term_id, self::META_FIELDSET, true ),
256 );
257
258 /**
259 * Filter widget script object.
260 *
261 * @param array $object Array of widget script object.
262 */
263 self::enqueue_widget_scripts( $data );
264 }
265
266 /**
267 * Add Gutenberg block scripts and sryles.
268 *
269 * @since 3.0
270 */
271 public static function enqueue_sidebar_assets() {
272 if ( ! is_admin() ) {
273 return;
274 }
275
276 $screen = get_current_screen();
277
278 if ( ! in_array( $screen->post_type, self::get_metabox_post_types(), true ) ) {
279 return;
280 }
281
282 $asset = require SHARING_IMAGE_DIR . 'assets/sidebar/index.asset.php';
283
284 wp_enqueue_script(
285 'sharing-image-sidebar',
286 plugins_url( 'assets/sidebar/index.js', SHARING_IMAGE_FILE ),
287 $asset['dependencies'],
288 $asset['version'],
289 true
290 );
291
292 wp_enqueue_style(
293 'sharing-image-sidebar',
294 plugins_url( 'assets/sidebar/index.css', SHARING_IMAGE_FILE ),
295 null,
296 $asset['version'],
297 'all'
298 );
299
300 // Translations availible only for WP 5.0+.
301 wp_set_script_translations( 'sharing-image-sidebar', 'sharing-image' );
302
303 $data = array(
304 'meta' => array(
305 'source' => self::META_SOURCE,
306 'fieldset' => self::META_FIELDSET,
307 ),
308 'autogenerate' => Config::get_autogenerate_index(),
309 'templates' => Templates::get_templates(),
310 );
311
312 // Add widget script object.
313 wp_localize_script( 'sharing-image-sidebar', 'sharingImageSidebar', $data );
314 }
315
316 /**
317 * Save metabox data.
318 *
319 * @param int $post_id Post ID.
320 */
321 public static function save_post_widget( $post_id ) {
322 if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
323 return;
324 }
325
326 if ( wp_is_post_revision( $post_id ) ) {
327 return;
328 }
329
330 if ( ! current_user_can( 'edit_post', $post_id ) ) {
331 return;
332 }
333
334 if ( ! isset( $_POST['sharing_image_nonce'] ) ) {
335 return;
336 }
337
338 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
339 if ( ! wp_verify_nonce( $_POST['sharing_image_nonce'], self::WIDGET_NONCE ) ) {
340 return;
341 }
342
343 if ( isset( $_POST[ self::META_SOURCE ] ) ) {
344 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
345 $meta = self::sanitize_source( wp_unslash( $_POST[ self::META_SOURCE ] ) );
346
347 update_post_meta( $post_id, self::META_SOURCE, $meta );
348 }
349
350 if ( isset( $_POST[ self::META_FIELDSET ] ) ) {
351 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
352 $meta = self::sanitize_fieldset( wp_unslash( $_POST[ self::META_FIELDSET ] ) );
353
354 update_post_meta( $post_id, self::META_FIELDSET, $meta );
355 }
356 }
357
358 /**
359 * Save taxonomy fields.
360 *
361 * @param int $term_id Term ID.
362 */
363 public static function save_taxonomy_widget( $term_id ) {
364 if ( ! current_user_can( 'edit_term', $term_id ) ) {
365 return;
366 }
367
368 if ( ! isset( $_POST['sharing_image_nonce'] ) ) {
369 return;
370 }
371
372 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
373 if ( ! wp_verify_nonce( $_POST['sharing_image_nonce'], self::WIDGET_NONCE ) ) {
374 return;
375 }
376
377 if ( isset( $_POST[ self::META_SOURCE ] ) ) {
378 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
379 $meta = self::sanitize_source( wp_unslash( $_POST[ self::META_SOURCE ] ) );
380
381 update_term_meta( $term_id, self::META_SOURCE, $meta );
382 }
383
384 if ( isset( $_POST[ self::META_FIELDSET ] ) ) {
385 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
386 $meta = self::sanitize_fieldset( wp_unslash( $_POST[ self::META_FIELDSET ] ) );
387
388 update_term_meta( $term_id, self::META_FIELDSET, $meta );
389 }
390 }
391
392 /**
393 * Display widget.
394 */
395 public static function display_widget() {
396 include_once SHARING_IMAGE_DIR . 'templates/widget.php';
397
398 /**
399 * Fires on widget template including.
400 */
401 do_action( 'sharing_image_widget' );
402 }
403
404 /**
405 * Create new endpoint for generate button in Gutenberg sidebar.
406 */
407 public static function add_generate_endpoint() {
408 register_rest_route(
409 'sharing-image/v1',
410 '/poster/(?P<id>\d+)',
411 array(
412 'methods' => 'POST',
413 'callback' => array( __CLASS__, 'handle_rest_generator' ),
414 'permission_callback' => function ( $request ) {
415 return current_user_can( 'edit_post', absint( $request['id'] ) );
416 },
417 )
418 );
419 }
420
421 /**
422 * Sanitize widget source meta.
423 *
424 * @param array $source Source meta data.
425 *
426 * @return array Sanitized source meta fields.
427 */
428 public static function sanitize_source( $source ) {
429 $sanitized = array();
430
431 if ( isset( $source['poster'] ) ) {
432 $sanitized['poster'] = sanitize_text_field( $source['poster'] );
433 }
434
435 if ( ! empty( $source['width'] ) ) {
436 $sanitized['width'] = absint( $source['width'] );
437 }
438
439 if ( ! empty( $source['height'] ) ) {
440 $sanitized['height'] = absint( $source['height'] );
441 }
442
443 if ( ! empty( $source['template'] ) ) {
444 $sanitized['template'] = sanitize_key( $source['template'] );
445 }
446
447 if ( ! empty( $source['mode'] ) ) {
448 $sanitized['mode'] = sanitize_text_field( $source['mode'] );
449 }
450
451 /**
452 * Filters fieldset meta.
453 *
454 * @since 3.0
455 *
456 * @param array $sanitized List of sanitized fields.
457 * @param array $source List of fields before sanitization.
458 */
459 return apply_filters( 'sharing_image_sanitize_source', $sanitized, $source );
460 }
461
462 /**
463 * Sanitize fieldset.
464 *
465 * @param array $fieldset Fieldset list.
466 *
467 * @return array Sanitized fieldset data.
468 */
469 public static function sanitize_fieldset( $fieldset ) {
470 $sanitized = array();
471
472 // Get list of templates.
473 $templates = Templates::get_templates();
474
475 foreach ( $templates as $template ) {
476 if ( empty( $template['layers'] ) ) {
477 continue;
478 }
479
480 foreach ( $template['layers'] as $key => $layer ) {
481 if ( ! array_key_exists( $key, $fieldset ) ) {
482 continue;
483 }
484
485 if ( 'text' === $layer['type'] ) {
486 $sanitized[ $key ] = sanitize_textarea_field(
487 Generator::normalize_text( $fieldset[ $key ] )
488 );
489 }
490
491 if ( 'image' === $layer['type'] ) {
492 $attachment_id = absint( $fieldset[ $key ] );
493
494 if ( $attachment_id && self::can_use_image_attachment( $attachment_id ) ) {
495 $sanitized[ $key ] = $attachment_id;
496 }
497 }
498 }
499 }
500
501 /**
502 * Filters widget fieldset meta.
503 *
504 * @since 3.0
505 *
506 * @param array $sanitized Sanitized fieldset list.
507 * @param array $fieldset Fieldset list before sanitization.
508 */
509 return apply_filters( 'sharing_image_sanitize_fieldset', $sanitized, $fieldset );
510 }
511
512 /**
513 * Handle generate button in Gutenberg sidebar.
514 *
515 * @param WP_REST_Request $request Request params.
516 */
517 public static function handle_rest_generator( $request ) {
518 $post_id = absint( $request->get_param( 'id' ) );
519
520 if ( empty( $post_id ) ) {
521 wp_send_json_error( __( 'Post data is empty.', 'sharing-image' ), 400 );
522 }
523
524 if ( ! current_user_can( 'edit_post', $post_id ) ) {
525 wp_send_json_error( __( 'Sorry, you are not allowed to edit this post.', 'sharing-image' ), 403 );
526 }
527
528 $params = $request->get_json_params();
529
530 if ( ! isset( $params['template'] ) ) {
531 wp_send_json_error( __( 'Incorrect request parameters.', 'sharing-image' ), 400 );
532 }
533
534 $index = sanitize_key( $params['template'] );
535
536 if ( empty( $params['fieldset'] ) ) {
537 $params['fieldset'] = array();
538 }
539
540 $fieldset = self::sanitize_fieldset( $params['fieldset'] );
541
542 // Invoke poster generation.
543 $result = self::generate_poster( $fieldset, $index, $post_id, 'post' );
544
545 if ( is_wp_error( $result ) ) {
546 wp_send_json_error( $result->get_error_messages(), $result->get_error_data() );
547 }
548
549 wp_send_json_success( $result );
550 }
551
552 /**
553 * Handle generate button on Classic Editor and taxonomy widget.
554 */
555 public static function handle_ajax_generator() {
556 $check = check_ajax_referer( self::WIDGET_NONCE, 'sharing_image_nonce', false );
557
558 if ( false === $check ) {
559 wp_send_json_error( __( 'Invalid security token. Please reload the page and try again.', 'sharing-image' ), 403 );
560 }
561
562 if ( empty( $_POST[ self::META_SOURCE ]['template'] ) ) {
563 wp_send_json_error( __( 'Template ID cannot be empty.', 'sharing-image' ), 400 );
564 }
565
566 $index = sanitize_key( $_POST[ self::META_SOURCE ]['template'] );
567
568 $screen_id = 0;
569
570 if ( ! empty( $_POST['sharing_image_screen'] ) ) {
571 $screen_id = absint( wp_unslash( $_POST['sharing_image_screen'] ) );
572 }
573
574 $context = 'post';
575
576 if ( ! empty( $_POST['sharing_image_context'] ) ) {
577 $context = sanitize_key( wp_unslash( $_POST['sharing_image_context'] ) );
578 }
579
580 $permission_error = self::check_generate_permission( $screen_id, $context );
581
582 if ( is_wp_error( $permission_error ) ) {
583 wp_send_json_error( $permission_error->get_error_message(), $permission_error->get_error_data() );
584 }
585
586 $fieldset = array();
587
588 if ( ! empty( $_POST[ self::META_FIELDSET ] ) ) {
589 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
590 $fieldset = self::sanitize_fieldset( wp_unslash( $_POST[ self::META_FIELDSET ] ) );
591 }
592
593 // Invoke poster generation.
594 $result = self::generate_poster( $fieldset, $index, $screen_id, $context );
595
596 if ( is_wp_error( $result ) ) {
597 wp_send_json_error( $result->get_error_messages(), $result->get_error_data() );
598 }
599
600 wp_send_json_success( $result );
601 }
602
603 /**
604 * Prepare poster for autogeneration on post insert or update.
605 *
606 * @param int $post_id Updated post_id.
607 * @param object $post Updated post object.
608 */
609 public static function prepare_autogenerated_poster( $post_id, $post ) {
610 if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
611 return;
612 }
613
614 if ( wp_is_post_revision( $post_id ) ) {
615 return;
616 }
617
618 if ( ( ! defined( 'DOING_CRON' ) || ! DOING_CRON ) && ! current_user_can( 'edit_post', $post_id ) ) {
619 return;
620 }
621
622 if ( 'trash' === $post->post_status || 'auto-draft' === $post->post_status ) {
623 return;
624 }
625
626 if ( ! in_array( $post->post_type, self::get_metabox_post_types(), true ) ) {
627 return;
628 }
629
630 $meta = get_post_meta( $post_id, self::META_SOURCE, true );
631
632 if ( ! empty( $meta['mode'] ) && 'manual' === $meta['mode'] ) {
633 return;
634 }
635
636 self::autogenerate_poster( $post_id );
637 }
638
639 /**
640 * Autogenerate poster for post_id and custom index if defined.
641 * Use this public method to autogenerate poster manually.
642 *
643 * @param int $post_id Post ID.
644 * @param string|null $index Template index.
645 */
646 public static function autogenerate_poster( $post_id, $index = null ) {
647 if ( is_null( $index ) ) {
648 $index = Config::get_autogenerate_index();
649 }
650
651 if ( ! Templates::has_template( $index ) ) {
652 return;
653 }
654
655 // Compose fieldset by template index.
656 $fieldset = self::compose_fields( $index, $post_id );
657
658 /**
659 * Filters fieldset before autogeneration.
660 *
661 * @since 3.0
662 *
663 * @param array $fieldset Prepared fieldset data.
664 * @param string $index Template index.
665 * @param int $post_id Post ID.
666 */
667 $fieldset = apply_filters( 'sharing_image_autogenerated_fieldset', $fieldset, $index, $post_id );
668
669 if ( empty( $fieldset ) ) {
670 $fieldset = array();
671 }
672
673 // Invoke poster generation.
674 $result = self::generate_poster( $fieldset, $index, $post_id, 'post', true );
675
676 if ( is_wp_error( $result ) ) {
677 return;
678 }
679
680 $result['template'] = $index;
681
682 /**
683 * Filters autogenerated poster data.
684 *
685 * @since 2.0.11
686 *
687 * @param array|WP_Erorr $result Poster image, width and height data or WP_Error if undefined.
688 * @param int $post_id Post ID.
689 */
690 $result = apply_filters( 'sharing_image_autogenerated_poster', $result, $post_id );
691
692 update_post_meta( $post_id, self::META_SOURCE, $result );
693 update_post_meta( $post_id, self::META_FIELDSET, $fieldset );
694 }
695
696 /**
697 * Generate poster.
698 * Used in widget, sidebar and for auto-generation.
699 *
700 * @param array $fieldset Fieldset data from widget.
701 * @param int $index Template index from editor.
702 * @param int $screen_id Post or term ID from admin screen.
703 * @param string $context Screen ID context field. Can be settings, post or term.
704 * @param boolean $auto Whether auto-generated poster.
705 */
706 private static function generate_poster( $fieldset, $index, $screen_id, $context, $auto = false ) {
707 $templates = Templates::get_templates();
708
709 if ( ! isset( $templates[ $index ] ) ) {
710 return new WP_Error( 'generate', esc_html__( 'Incorrect template ID.', 'sharing-image' ), 400 );
711 }
712
713 // Prepare template editor.
714 $editor = Generator::prepare_template( $templates[ $index ], $fieldset, $index, $screen_id, $context );
715
716 if ( ! Generator::check_required( $editor ) ) {
717 return new WP_Error( 'generate', esc_html__( 'Incorrect template settings.', 'sharing-image' ), 400 );
718 }
719
720 list( $path, $url ) = Generator::get_upload_file();
721
722 // Generate image and save it to given path.
723 $poster = Generator::create_poster( $editor, $path );
724
725 if ( is_wp_error( $poster ) ) {
726 return new WP_Error( 'generate', $poster->get_error_message(), 400 );
727 }
728
729 $attachment = self::save_attachment( $path, $screen_id, $context );
730
731 if ( is_wp_error( $attachment ) ) {
732 return new WP_Error( 'attachment', $attachment->get_error_message(), 400 );
733 }
734
735 $source = array(
736 'poster' => $url,
737 'width' => $editor['width'],
738 'height' => $editor['height'],
739 'mode' => 'manual',
740 );
741
742 if ( ! empty( $auto ) ) {
743 $source['mode'] = 'auto';
744 }
745
746 return $source;
747 }
748
749 /**
750 * Create script object to inject with widget.
751 *
752 * @param string $context Widget context. For example: metabox or taxonomy.
753 * @param int $screen_id Post or taxonomy screen ID.
754 *
755 * @return array Filtered widget script object.
756 */
757 private static function create_script_object( $context, $screen_id ) {
758 $object = array(
759 'nonce' => wp_create_nonce( self::WIDGET_NONCE ),
760 'context' => $context,
761 'screen' => $screen_id,
762
763 'name' => array(
764 'source' => self::META_SOURCE,
765 'fieldset' => self::META_FIELDSET,
766 ),
767 'links' => array(
768 'uploads' => esc_url( admin_url( 'upload.php' ) ),
769 ),
770 'templates' => Templates::get_templates(),
771 'autogenerate' => Config::get_autogenerate_index(),
772 );
773
774 /**
775 * Filter widget script object.
776 *
777 * @param array $object Array of widget script object.
778 */
779 return apply_filters( 'sharing_image_widget_object', $object );
780 }
781
782 /**
783 * Enqueue widget scripts.
784 *
785 * @param array $data Widget data object.
786 */
787 private static function enqueue_widget_scripts( $data ) {
788 $asset = require SHARING_IMAGE_DIR . 'assets/widget/index.asset.php';
789
790 wp_enqueue_media();
791
792 wp_enqueue_script(
793 'sharing-image-widget',
794 plugins_url( 'assets/widget/index.js', SHARING_IMAGE_FILE ),
795 $asset['dependencies'],
796 $asset['version'],
797 true
798 );
799
800 wp_enqueue_style(
801 'sharing-image-widget',
802 plugins_url( 'assets/widget/index.css', SHARING_IMAGE_FILE ),
803 $asset['dependencies'],
804 $asset['version'],
805 'all'
806 );
807
808 // Translations availible only for WP 5.0+.
809 wp_set_script_translations( 'sharing-image-widget', 'sharing-image' );
810
811 // Add widget script object.
812 wp_localize_script( 'sharing-image-widget', 'sharingImageWidget', $data );
813 }
814
815 /**
816 * Get an array of post types where the metabox is displayed.
817 *
818 * @return array Array of post types.
819 */
820 private static function get_metabox_post_types() {
821 $post_types = get_post_types(
822 array(
823 'public' => true,
824 )
825 );
826
827 unset( $post_types['attachment'] );
828
829 /**
830 * Filter widget post types.
831 *
832 * @param array $post_types Array of post types for which the metabox is displayed.
833 */
834 return apply_filters( 'sharing_image_metabox_post_types', array_values( $post_types ) );
835 }
836
837 /**
838 * Update template with post data for preset fields.
839 *
840 * @param string $index Template index.
841 * @param int $post_id Post id.
842 * @param array $fieldset Optional. Prepared fieldset to modify.
843 *
844 * @return array List of prepared fields.
845 */
846 private static function compose_fields( $index, $post_id, $fieldset = array() ) {
847 $templates = Templates::get_templates();
848
849 if ( ! isset( $templates[ $index ] ) ) {
850 return $fieldset;
851 }
852
853 $template = $templates[ $index ];
854
855 if ( ! isset( $template['layers'] ) ) {
856 return $fieldset;
857 }
858
859 $layers = $template['layers'];
860
861 foreach ( $layers as $key => $layer ) {
862 $field = null;
863
864 if ( empty( $layer['type'] ) ) {
865 continue;
866 }
867
868 if ( 'text' === $layer['type'] ) {
869 $field = self::compose_text_presets( $layer, $post_id );
870 }
871
872 if ( 'image' === $layer['type'] ) {
873 $field = self::compose_image_presets( $layer, $post_id );
874 }
875
876 if ( ! empty( $field ) ) {
877 $fieldset[ $key ] = $field;
878 }
879 }
880
881 return $fieldset;
882 }
883
884 /**
885 * Compose preset fields for text layers.
886 *
887 * @param string $layer List of layer options.
888 * @param int $post_id Post id.
889 *
890 * @return array|null List of prepared fieldset for text layer.
891 */
892 private static function compose_text_presets( $layer, $post_id ) {
893 if ( empty( $layer['preset'] ) || empty( $layer['dynamic'] ) ) {
894 return null;
895 }
896
897 $separator = ', ';
898
899 if ( ! empty( $layer['separator'] ) ) {
900 $separator = $layer['separator'];
901 }
902
903 if ( 'title' === $layer['preset'] ) {
904 return get_the_title( $post_id );
905 }
906
907 if ( 'excerpt' === $layer['preset'] ) {
908 $post = get_post( $post_id );
909
910 return $post->post_excerpt;
911 }
912
913 if ( 'categories' === $layer['preset'] ) {
914 $categories = get_the_category( $post_id );
915
916 if ( $categories ) {
917 return implode( $separator, wp_list_pluck( $categories, 'name' ) );
918 }
919 }
920
921 if ( 'tags' === $layer['preset'] ) {
922 $tags = get_the_tags( $post_id );
923
924 if ( $tags ) {
925 return implode( $separator, wp_list_pluck( $tags, 'name' ) );
926 }
927 }
928
929 return null;
930 }
931
932 /**
933 * Compose preset fields for image layers.
934 *
935 * @param string $layer List of layer options.
936 * @param int $post_id Post id.
937 *
938 * @return array|null List of prepared fieldset for image layer.
939 */
940 private static function compose_image_presets( $layer, $post_id ) {
941 if ( empty( $layer['preset'] ) || empty( $layer['dynamic'] ) ) {
942 return null;
943 }
944
945 if ( 'featured' === $layer['preset'] ) {
946 return absint( get_post_thumbnail_id( $post_id ) );
947 }
948
949 return null;
950 }
951
952 /**
953 * Get an array of taxonomeis where the widget is displayed.
954 *
955 * @return array Array of taxonomies.
956 */
957 private static function get_widget_taxonomies() {
958 $taxonomies = get_taxonomies(
959 array(
960 'public' => true,
961 'show_ui' => true,
962 )
963 );
964
965 /**
966 * Filter the taxonomies where we need to show the poster settings.
967 *
968 * @param array $taxonomies List of taxonomies to show settings.
969 */
970 return apply_filters( 'sharing_image_widget_taxonomies', array_values( $taxonomies ) );
971 }
972
973 /**
974 * Check whether current user can generate a poster for the requested object.
975 *
976 * @param int $screen_id Post or term ID.
977 * @param string $context Widget context.
978 *
979 * @return true|WP_Error True when allowed, WP_Error otherwise.
980 */
981 private static function check_generate_permission( $screen_id, $context ) {
982 if ( 'term' === $context ) {
983 if ( ! $screen_id || ! current_user_can( 'edit_term', $screen_id ) ) {
984 return new WP_Error( 'permission', __( 'Sorry, you are not allowed to edit this term.', 'sharing-image' ), 403 );
985 }
986
987 return true;
988 }
989
990 if ( 'post' !== $context ) {
991 return new WP_Error( 'permission', __( 'Incorrect request context.', 'sharing-image' ), 400 );
992 }
993
994 if ( $screen_id ) {
995 if ( ! current_user_can( 'edit_post', $screen_id ) ) {
996 return new WP_Error( 'permission', __( 'Sorry, you are not allowed to edit this post.', 'sharing-image' ), 403 );
997 }
998
999 return true;
1000 }
1001
1002 if ( ! current_user_can( 'edit_posts' ) ) {
1003 return new WP_Error( 'permission', __( 'Sorry, you are not allowed to edit posts.', 'sharing-image' ), 403 );
1004 }
1005
1006 return true;
1007 }
1008
1009 /**
1010 * Check whether an attachment can be used as a dynamic image layer.
1011 *
1012 * @param int $attachment_id Attachment ID.
1013 *
1014 * @return bool Whether attachment is readable image.
1015 */
1016 private static function can_use_image_attachment( $attachment_id ) {
1017 if ( ! current_user_can( 'read_post', $attachment_id ) ) {
1018 return false;
1019 }
1020
1021 return wp_attachment_is_image( $attachment_id );
1022 }
1023
1024 /**
1025 * Save attachment to media library.
1026 *
1027 * @param string $path Path to poster image.
1028 * @param int $screen_id Post or taxonomy screen ID.
1029 * @param string $context Widget context. For example: metabox or autogenerate.
1030 *
1031 * @return int|null Attachment ID or null;
1032 */
1033 private static function save_attachment( $path, $screen_id, $context ) {
1034 $config = Config::get_config();
1035
1036 if ( ! isset( $config['attachment'] ) ) {
1037 return null;
1038 }
1039
1040 $id = null;
1041
1042 try {
1043 $name = implode( '-', array( 'sharing-image', $context, $screen_id ) );
1044
1045 // Try to delete current attachment if exists.
1046 self::delete_attachment( $name );
1047
1048 $uploads = wp_upload_dir();
1049
1050 // Get poster filetype.
1051 $filetype = wp_check_filetype( basename( $path ), null );
1052
1053 $attachment = array(
1054 'post' => array(
1055 'guid' => $uploads['url'] . '/' . basename( $path ),
1056 'post_mime_type' => $filetype['type'],
1057 'post_name' => $name,
1058 'post_title' => $name,
1059 'post_content' => '',
1060 'post_status' => 'inherit',
1061 ),
1062 'parent_id' => 0,
1063 );
1064
1065 if ( 'post' === $context ) {
1066 $attachment['parent_id'] = $screen_id;
1067 }
1068
1069 /**
1070 * Filter attachment data before insertion.
1071 *
1072 * @param string $attachment Attachment data.
1073 * @param string $path Path to poster image.
1074 * @param string $screen_id Post or taxonomy screen ID.
1075 * @param string $context Widget context. For example: metabox or autogenerate.
1076 */
1077 $attachment = apply_filters( 'sharing_image_attachment_data', $attachment, $path, $screen_id, $context );
1078
1079 if ( empty( $attachment['post'] ) ) {
1080 return null;
1081 }
1082
1083 if ( ! function_exists( 'wp_crop_image' ) ) {
1084 include ABSPATH . 'wp-admin/includes/image.php';
1085 }
1086
1087 $id = wp_insert_attachment( $attachment['post'], $path, $attachment['parent_id'] );
1088
1089 // Get attachment metadata.
1090 $metadata = wp_generate_attachment_metadata( $id, $path );
1091
1092 wp_update_attachment_metadata( $id, $metadata );
1093 } catch ( Exception $e ) {
1094 return new WP_Error( 'attachment', $e->getMessage() );
1095 }
1096
1097 return $id;
1098 }
1099
1100 /**
1101 * Delete attachment by name if exists.
1102 *
1103 * @param string $name Name of attachment.
1104 *
1105 * @return bool Whether attachment deleted
1106 */
1107 private static function delete_attachment( $name ) {
1108 $posts = get_posts(
1109 array(
1110 'post_type' => 'attachment',
1111 'post_name__in' => array( $name ),
1112 'update_post_term_cache' => false,
1113 'update_post_meta_cache' => false,
1114 'posts_per_page' => 1,
1115 )
1116 );
1117
1118 if ( empty( $posts[0]->ID ) ) {
1119 return false;
1120 }
1121
1122 return wp_delete_attachment( $posts[0]->ID, true );
1123 }
1124 }
1125