PluginProbe
Sharing Image / 3.2
Sharing Image v3.2
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-templates.php

class-templates.php in Sharing Image 3.2, at classes/class-templates.php

753 lines 18.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Handle templates settings page.
4 *
5 * @package sharing-image
6 * @author Anton Lukin
7 */
8
9 namespace Sharing_Image;
10
11 if ( ! defined( 'ABSPATH' ) ) {
12 die;
13 }
14
15 /**
16 * Templates class.
17 *
18 * @class Templates
19 */
20 class Templates {
21
22 /**
23 * Sharing Image templates options name.
24 *
25 * @var string
26 */
27 const OPTION_TEMPLATES = 'sharing_image_data';
28
29 /**
30 * Init class actions and filters.
31 */
32 public static function init() {
33 add_action( 'admin_init', array( __CLASS__, 'handle_requests' ) );
34 }
35
36 /**
37 * Handle tools requests from admin-side.
38 */
39 public static function handle_requests() {
40 add_action( 'admin_post_sharing_image_save_editor', array( __CLASS__, 'save_editor' ) );
41 add_action( 'admin_post_sharing_image_delete_template', array( __CLASS__, 'delete_template' ) );
42
43 add_action( 'wp_ajax_sharing_image_show_preview', array( __CLASS__, 'show_preview' ) );
44 add_action( 'wp_ajax_sharing_image_save_preview', array( __CLASS__, 'save_preview' ) );
45 }
46
47 /**
48 * Save template editor fields.
49 */
50 public static function save_editor() {
51 check_admin_referer( Settings::SETTINGS_NONCE, 'sharing_image_nonce' );
52
53 if ( ! current_user_can( 'manage_options' ) ) {
54 wp_die( esc_html__( 'Sorry, you do not have permission to manage options for this site.', 'sharing-image' ) );
55 }
56
57 $redirect = admin_url( 'options-general.php?page=' . Settings::SETTINGS_SLUG );
58
59 if ( ! isset( $_POST['sharing_image_index'] ) ) {
60 Settings::redirect_with_message( $redirect, 2 );
61 }
62
63 $index = sanitize_key( wp_unslash( $_POST['sharing_image_index'] ) );
64
65 if ( ! self::is_valid_unique_index( $index ) ) {
66 Settings::redirect_with_message( $redirect, 2 );
67 }
68
69 if ( ! isset( $_POST['sharing_image_editor'] ) ) {
70 Settings::redirect_with_message( $redirect, 2 );
71 }
72
73 $templates = self::get_templates();
74
75 // Skip 2nd+ templates if the Premium is not active.
76 if ( ! Premium::is_premium_features() && ! empty( $templates ) && empty( $templates[ $index ] ) ) {
77 Settings::redirect_with_message( $redirect, 8 );
78 }
79
80 $redirect = add_query_arg( array( 'template' => $index ), $redirect );
81
82 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
83 $editor = self::sanitize_editor( wp_unslash( $_POST['sharing_image_editor'] ) );
84
85 self::update_templates( $index, $editor );
86
87 // Redirect with success message.
88 Settings::redirect_with_message( $redirect, 1 );
89 }
90
91 /**
92 * Action to delete template from editor page.
93 */
94 public static function delete_template() {
95 check_admin_referer( Settings::SETTINGS_NONCE, 'nonce' );
96
97 if ( ! current_user_can( 'manage_options' ) ) {
98 wp_die( esc_html__( 'Sorry, you do not have permission to manage options for this site.', 'sharing-image' ) );
99 }
100
101 $redirect = admin_url( 'options-general.php?page=' . Settings::SETTINGS_SLUG );
102
103 if ( ! isset( $_REQUEST['template'] ) ) {
104 Settings::redirect_with_message( $redirect, 4 );
105 }
106
107 $index = sanitize_key( $_REQUEST['template'] );
108
109 if ( ! self::update_templates( $index ) ) {
110 Settings::redirect_with_message( $redirect, 4 );
111 }
112
113 Settings::redirect_with_message( $redirect, 3 );
114 }
115
116 /**
117 * Show generated template from AJAX request.
118 */
119 public static function show_preview() {
120 $check = check_ajax_referer( Settings::SETTINGS_NONCE, 'sharing_image_nonce', false );
121
122 if ( false === $check ) {
123 wp_send_json_error( __( 'Invalid security token. Please reload the page and try again.', 'sharing-image' ), 403 );
124 }
125
126 if ( ! isset( $_POST['sharing_image_index'] ) ) {
127 wp_send_json_error( __( 'Poster index is undefined.', 'sharing-image' ), 400 );
128 }
129
130 $index = sanitize_key( wp_unslash( $_POST['sharing_image_index'] ) );
131
132 if ( ! isset( $_POST['sharing_image_editor'] ) ) {
133 wp_send_json_error( __( 'Editor settings are not configured.', 'sharing-image' ), 400 );
134 }
135
136 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
137 $editor = self::sanitize_editor( wp_unslash( $_POST['sharing_image_editor'] ) );
138
139 // Prepare template editor.
140 $editor = Generator::prepare_template( $editor, null, $index );
141
142 if ( ! Generator::check_required( $editor ) ) {
143 wp_send_json_error( __( 'Incorrect template settings.', 'sharing-image' ), 400 );
144 }
145
146 // Generate image and show it immediately.
147 $poster = Generator::create_poster( $editor );
148
149 if ( is_wp_error( $poster ) ) {
150 wp_send_json_error( $poster->get_error_message(), 400 );
151 }
152
153 exit; // It's ok to exit here. Just cause we show an image above.
154 }
155
156 /**
157 * Save generated template from AJAX request.
158 */
159 public static function save_preview() {
160 $check = check_ajax_referer( Settings::SETTINGS_NONCE, 'sharing_image_nonce', false );
161
162 if ( false === $check ) {
163 wp_send_json_error( __( 'Invalid security token. Please reload the page and try again.', 'sharing-image' ), 403 );
164 }
165
166 if ( ! isset( $_POST['sharing_image_index'] ) ) {
167 wp_send_json_error( __( 'Poster index is undefined.', 'sharing-image' ), 400 );
168 }
169
170 $index = sanitize_key( wp_unslash( $_POST['sharing_image_index'] ) );
171
172 if ( ! isset( $_POST['sharing_image_editor'] ) ) {
173 wp_send_json_error( __( 'Editor settings are not configured.', 'sharing-image' ), 400 );
174 }
175
176 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
177 $editor = self::sanitize_editor( wp_unslash( $_POST['sharing_image_editor'] ) );
178
179 // Prepare template editor.
180 $editor = Generator::prepare_template( $editor, null, $index );
181
182 if ( ! Generator::check_required( $editor ) ) {
183 wp_send_json_error( __( 'Incorrect template settings.', 'sharing-image' ), 400 );
184 }
185
186 list( $path, $url ) = Generator::get_upload_file();
187
188 // Generate image and save it.
189 $poster = Generator::create_poster( $editor, $path );
190
191 if ( is_wp_error( $poster ) ) {
192 wp_send_json_error( $poster->get_error_message(), 400 );
193 }
194
195 wp_send_json_success( $url );
196 }
197
198 /**
199 * Get templates list from options.
200 *
201 * @return array List of templates.
202 */
203 public static function get_templates() {
204 $templates = get_option( self::OPTION_TEMPLATES, null );
205
206 if ( empty( $templates ) ) {
207 $templates = array();
208 }
209
210 if ( ! Premium::is_premium_features() ) {
211 $templates = array_slice( $templates, 0, 1 );
212 }
213
214 /**
215 * Filters list of templates.
216 *
217 * @param array $templates List of templates.
218 */
219 return apply_filters( 'sharing_image_get_templates', $templates );
220 }
221
222 /**
223 * Get templates count.
224 *
225 * @return int Last index of templates list.
226 */
227 public static function get_templates_count() {
228 return count( self::get_templates() );
229 }
230
231 /**
232 * Check if template with given index exists.
233 *
234 * @param string $index Template index.
235 *
236 * @return bool Whether template exists.
237 */
238 public static function has_template( $index ) {
239 if ( empty( $index ) ) {
240 return false;
241 }
242
243 $templates = self::get_templates();
244
245 if ( array_key_exists( $index, $templates ) ) {
246 return true;
247 }
248
249 return false;
250 }
251
252 /**
253 * Check unique template index.
254 *
255 * @param string $index Unique template index to check.
256 *
257 * @return bool Whether unique template index is correct.
258 */
259 public static function is_valid_unique_index( $index ) {
260 $valid = preg_match( '~^[a-z0-9]{8}$~', $index );
261
262 /**
263 * Filter whether given unique index matching rule.
264 *
265 * @param bool $valid Valid flag.
266 * @param string $index Template index.
267 */
268 return apply_filters( 'sharing_image_valid_unique_index', $valid, $index );
269 }
270
271 /**
272 * Update templates by index.
273 *
274 * @param string $index Template index to create or update.
275 * @param array|null $editor New template data. Use null to delete template.
276 *
277 * @return bool True if the value was updated, false otherwise.
278 */
279 public static function update_templates( $index, $editor = null ) {
280 // Method get_templates() is not used to save old templates during Premium switching.
281 $templates = get_option( self::OPTION_TEMPLATES, null );
282
283 if ( empty( $templates ) ) {
284 $templates = array();
285 }
286
287 $templates[ $index ] = $editor;
288
289 if ( null === $editor ) {
290 unset( $templates[ $index ] );
291 }
292
293 /**
294 * Filters list of templates before update in database.
295 *
296 * @param array $templates List of reindexed templates.
297 */
298 $templates = apply_filters( 'sharing_image_update_templates', $templates );
299
300 return update_option( self::OPTION_TEMPLATES, $templates );
301 }
302
303 /**
304 * Generate random string for layer key.
305 *
306 * @param int $len Optional. Desiner string length.
307 * @param string $key Optional. Prefix for key.
308 */
309 public static function generate_layer_key( $len = 12, $key = '' ) {
310 $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
311
312 for ( $i = 0; $i < $len; $i++ ) {
313 $key = $key . $characters[ random_int( 0, strlen( $characters ) - 1 ) ];
314 }
315
316 return $key;
317 }
318
319 /**
320 * Create unique template index.
321 *
322 * @return string New template index.
323 */
324 public static function create_unique_index() {
325 $templates = self::get_templates();
326
327 // Generate random index.
328 $index = substr( bin2hex( openssl_random_pseudo_bytes( 20 ) ), -8 );
329
330 /**
331 * Filter template unique index.
332 *
333 * @param array $index Generated template index.
334 * @param array $templates List of templates.
335 */
336 $index = apply_filters( 'sharing_image_unique_index', $index, $templates );
337
338 if ( array_key_exists( $index, $templates ) ) {
339 return self::create_unique_index();
340 }
341
342 return $index;
343 }
344
345 /**
346 * Sanitize editor template settings.
347 *
348 * @param array $editor Template editor settings.
349 *
350 * @return array
351 */
352 public static function sanitize_editor( $editor ) {
353 $sanitized = array();
354
355 if ( ! empty( $editor['debug'] ) ) {
356 $sanitized['debug'] = 'debug';
357 }
358
359 if ( ! empty( $editor['preview'] ) ) {
360 $sanitized['preview'] = sanitize_text_field( $editor['preview'] );
361 }
362
363 if ( ! empty( $editor['title'] ) ) {
364 $sanitized['title'] = sanitize_text_field( $editor['title'] );
365 }
366
367 $sanitized['fill'] = '#000000';
368
369 if ( ! empty( $editor['fill'] ) ) {
370 $sanitized['fill'] = sanitize_hex_color( $editor['fill'] );
371 }
372
373 $sanitized['width'] = 1200;
374
375 if ( ! empty( $editor['width'] ) ) {
376 $sanitized['width'] = absint( $editor['width'] );
377 }
378
379 $sanitized['height'] = 630;
380
381 if ( ! empty( $editor['height'] ) ) {
382 $sanitized['height'] = absint( $editor['height'] );
383 }
384
385 if ( isset( $editor['layers'] ) && is_array( $editor['layers'] ) ) {
386 $layers = array();
387
388 foreach ( $editor['layers'] as $key => $layer ) {
389 if ( empty( $layer['type'] ) ) {
390 continue;
391 }
392
393 switch ( $layer['type'] ) {
394 case 'text':
395 $layers[ $key ] = self::sanitize_text_layer( $layer );
396 break;
397
398 case 'image':
399 $layers[ $key ] = self::sanitize_image_layer( $layer );
400 break;
401
402 case 'filter':
403 $layers[ $key ] = self::sanitize_filter_layer( $layer );
404 break;
405
406 case 'rectangle':
407 $layers[ $key ] = self::sanitize_rectangle_layer( $layer );
408 break;
409 }
410 }
411
412 $sanitized['layers'] = $layers;
413 }
414
415 /**
416 * Filters template editor sanitized fields.
417 *
418 * @param array $sanitized List of sanitized editor fields.
419 * @param array $editor List of editor fields before sanitization.
420 */
421 return apply_filters( 'sharing_image_sanitize_editor', $sanitized, $editor );
422 }
423
424 /**
425 * Sanitize template editor text layer.
426 *
427 * @param array $layer Layer settings.
428 *
429 * @return array Sanitized layer settings.
430 */
431 private static function sanitize_text_layer( $layer ) {
432 $sanitized = array();
433
434 // No need to sanitize after switch.
435 $sanitized['type'] = $layer['type'];
436
437 if ( ! empty( $layer['collapsed'] ) ) {
438 $sanitized['collapsed'] = 1;
439 }
440
441 if ( ! empty( $layer['dynamic'] ) ) {
442 $sanitized['dynamic'] = 'dynamic';
443 }
444
445 if ( isset( $layer['title'] ) ) {
446 $sanitized['title'] = sanitize_text_field( $layer['title'] );
447 }
448
449 if ( isset( $layer['content'] ) ) {
450 $sanitized['content'] = sanitize_textarea_field( $layer['content'] );
451 }
452
453 if ( isset( $layer['sample'] ) ) {
454 $sanitized['sample'] = sanitize_textarea_field( $layer['sample'] );
455 }
456
457 if ( isset( $layer['separator'] ) ) {
458 $sanitized['separator'] = preg_replace( '#[^\s,]#', '', $layer['separator'] );
459 }
460
461 $sanitized['preset'] = 'none';
462
463 if ( isset( $layer['preset'] ) ) {
464 $preset = array( 'title', 'excerpt', 'categories', 'tags' );
465
466 if ( in_array( $layer['preset'], $preset, true ) ) {
467 $sanitized['preset'] = $layer['preset'];
468 }
469 }
470
471 $sanitized['color'] = '#ffffff';
472
473 if ( ! empty( $layer['color'] ) ) {
474 $sanitized['color'] = sanitize_hex_color( $layer['color'] );
475 }
476
477 $sanitized['horizontal'] = 'left';
478
479 if ( isset( $layer['horizontal'] ) ) {
480 $horizontal = array( 'center', 'right' );
481
482 if ( in_array( $layer['horizontal'], $horizontal, true ) ) {
483 $sanitized['horizontal'] = $layer['horizontal'];
484 }
485 }
486
487 $sanitized['vertical'] = 'top';
488
489 if ( isset( $layer['vertical'] ) ) {
490 $vertical = array( 'center', 'bottom' );
491
492 if ( in_array( $layer['vertical'], $vertical, true ) ) {
493 $sanitized['vertical'] = $layer['vertical'];
494 }
495 }
496
497 if ( isset( $layer['fontsize'] ) ) {
498 $sanitized['fontsize'] = absint( $layer['fontsize'] );
499 }
500
501 if ( isset( $layer['lineheight'] ) ) {
502 $sanitized['lineheight'] = (float) $layer['lineheight'];
503 }
504
505 if ( isset( $layer['fontname'] ) ) {
506 $sanitized['fontname'] = sanitize_text_field( $layer['fontname'] );
507 }
508
509 if ( ! empty( $layer['fontfile'] ) ) {
510 $sanitized['fontfile'] = absint( $layer['fontfile'] );
511 }
512
513 $sanitized['opacity'] = 0;
514
515 if ( isset( $layer['opacity'] ) ) {
516 $opacity = absint( $layer['opacity'] );
517
518 if ( $opacity <= 100 ) {
519 $sanitized['opacity'] = $opacity;
520 }
521 }
522
523 $sizes = array( 'x', 'y', 'width', 'height' );
524
525 foreach ( $sizes as $size ) {
526 if ( ! isset( $layer[ $size ] ) || '' === $layer[ $size ] ) {
527 continue;
528 }
529
530 $sanitized[ $size ] = intval( $layer[ $size ] );
531 }
532
533 $sanitized['boundary'] = self::sanitize_boundary( $layer );
534
535 return $sanitized;
536 }
537
538 /**
539 * Sanitize template editor image layer.
540 *
541 * @param array $layer Layer settings.
542 *
543 * @return array Sanitized image layer settings.
544 */
545 private static function sanitize_image_layer( $layer ) {
546 $sanitized = array();
547
548 // No need to sanitize after switch.
549 $sanitized['type'] = $layer['type'];
550
551 if ( ! empty( $layer['collapsed'] ) ) {
552 $sanitized['collapsed'] = 1;
553 }
554
555 if ( isset( $layer['title'] ) ) {
556 $sanitized['title'] = sanitize_text_field( $layer['title'] );
557 }
558
559 if ( ! empty( $layer['dynamic'] ) ) {
560 $sanitized['dynamic'] = 'dynamic';
561 }
562
563 if ( ! empty( $layer['attachment'] ) ) {
564 $sanitized['attachment'] = absint( $layer['attachment'] );
565 }
566
567 $sanitized['opacity'] = 0;
568
569 if ( isset( $layer['opacity'] ) ) {
570 $opacity = absint( $layer['opacity'] );
571
572 if ( $opacity <= 100 ) {
573 $sanitized['opacity'] = $opacity;
574 }
575 }
576
577 $sizes = array( 'x', 'y', 'width', 'height' );
578
579 foreach ( $sizes as $size ) {
580 if ( ! isset( $layer[ $size ] ) || '' === $layer[ $size ] ) {
581 continue;
582 }
583
584 $sanitized[ $size ] = intval( $layer[ $size ] );
585 }
586
587 $sanitized['preset'] = 'none';
588
589 if ( isset( $layer['preset'] ) ) {
590 $preset = array( 'featured' ); // Possible update later.
591
592 if ( in_array( $layer['preset'], $preset, true ) ) {
593 $sanitized['preset'] = $layer['preset'];
594 }
595 }
596
597 $sanitized['resize'] = 'center';
598
599 if ( isset( $layer['resize'] ) ) {
600 $resize = array( 'center', 'top', 'bottom', 'ignore', 'crop' );
601
602 if ( in_array( $layer['resize'], $resize, true ) ) {
603 $sanitized['resize'] = $layer['resize'];
604 }
605 }
606
607 $sanitized['boundary'] = self::sanitize_boundary( $layer );
608
609 return $sanitized;
610 }
611
612 /**
613 * Sanitize template editor filter layer.
614 *
615 * @param array $layer Layer settings.
616 *
617 * @return array Sanitized filter layer settings.
618 */
619 private static function sanitize_filter_layer( $layer ) {
620 $sanitized = array();
621
622 // No need to sanitize after switch.
623 $sanitized['type'] = $layer['type'];
624
625 if ( ! empty( $layer['collapsed'] ) ) {
626 $sanitized['collapsed'] = 1;
627 }
628
629 if ( ! empty( $layer['grayscale'] ) ) {
630 $sanitized['grayscale'] = 'grayscale';
631 }
632
633 if ( ! empty( $layer['blur'] ) ) {
634 $sanitized['blur'] = 'blur';
635 }
636
637 $sanitized['brightness'] = 0;
638
639 if ( isset( $layer['brightness'] ) ) {
640 $brightness = (int) $layer['brightness'];
641
642 if ( $brightness >= -100 && $brightness <= 100 ) {
643 $sanitized['brightness'] = $brightness;
644 }
645 }
646
647 $sanitized['contrast'] = 0;
648
649 if ( isset( $layer['contrast'] ) ) {
650 $contrast = (int) $layer['contrast'];
651
652 if ( $contrast >= -100 && $contrast <= 100 ) {
653 $sanitized['contrast'] = $contrast;
654 }
655 }
656
657 $sanitized['blackout'] = 0;
658
659 if ( isset( $layer['blackout'] ) ) {
660 $blackout = (int) $layer['blackout'];
661
662 if ( $blackout >= 0 && $blackout <= 100 ) {
663 $sanitized['blackout'] = $blackout;
664 }
665 }
666
667 return $sanitized;
668 }
669
670 /**
671 * Sanitize template editor rectagle layer.
672 *
673 * @param array $layer Layer settings.
674 *
675 * @return array Sanitized rectangle layer settings.
676 */
677 private static function sanitize_rectangle_layer( $layer ) {
678 $sanitized = array();
679
680 // No need to sanitize after switch.
681 $sanitized['type'] = $layer['type'];
682
683 if ( ! empty( $layer['collapsed'] ) ) {
684 $sanitized['collapsed'] = 1;
685 }
686
687 if ( ! empty( $layer['outline'] ) ) {
688 $sanitized['outline'] = 'outline';
689 }
690
691 $sanitized['color'] = '#ffffff';
692
693 if ( ! empty( $layer['color'] ) ) {
694 $sanitized['color'] = sanitize_hex_color( $layer['color'] );
695 }
696
697 $sanitized['opacity'] = 0;
698
699 if ( isset( $layer['opacity'] ) ) {
700 $opacity = absint( $layer['opacity'] );
701
702 if ( $opacity <= 100 ) {
703 $sanitized['opacity'] = $opacity;
704 }
705 }
706
707 $sanitized['thickness'] = 0;
708
709 if ( isset( $layer['thickness'] ) ) {
710 $thickness = (int) $layer['thickness'];
711
712 if ( $thickness >= 0 && $thickness <= 50 ) {
713 $sanitized['thickness'] = $thickness;
714 }
715 }
716
717 $sizes = array( 'x', 'y', 'width', 'height' );
718
719 foreach ( $sizes as $size ) {
720 if ( ! isset( $layer[ $size ] ) || '' === $layer[ $size ] ) {
721 continue;
722 }
723
724 $sanitized[ $size ] = intval( $layer[ $size ] );
725 }
726
727 $sanitized['boundary'] = self::sanitize_boundary( $layer );
728
729 return $sanitized;
730 }
731
732 /**
733 * Sanitize boundary layer setting.
734 *
735 * @param array $layer Layer settings.
736 *
737 * @return string Boundary setting.
738 */
739 private static function sanitize_boundary( $layer ) {
740 $boundary = 'absolute';
741
742 if ( isset( $layer['boundary'] ) ) {
743 $valid = array( 'absolute', 'vertically', 'horizontally', 'both' );
744
745 if ( in_array( $layer['boundary'], $valid, true ) ) {
746 $boundary = $layer['boundary'];
747 }
748 }
749
750 return $boundary;
751 }
752 }
753