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