PluginProbe
Sharing Image / 2.0.0
Sharing Image v2.0.0
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-settings.php

class-settings.php in Sharing Image 2.0.0, at classes/class-settings.php

1,507 lines 35.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Settings page class
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 * Settings page class
17 *
18 * @class Settings
19 */
20 class Settings {
21 /**
22 * Admin screen id.
23 *
24 * @var string
25 */
26 const SCREEN_ID = 'settings_page_sharing-image';
27
28 /**
29 * Sharing Image templates options name.
30 *
31 * @var string
32 */
33 const OPTION_TEMPLATES = 'sharing_image_templates';
34
35 /**
36 * Sharing Image config options name.
37 *
38 * @var string
39 */
40 const OPTION_CONFIG = 'sharing_image_config';
41
42 /**
43 * Sharing Image license options name.
44 *
45 * @var string
46 */
47 const OPTION_LICENSE = 'sharing_image_license';
48
49 /**
50 * Plugin admin menu slug.
51 *
52 * @var string
53 */
54 const SETTINGS_SLUG = 'sharing-image';
55
56 /**
57 * Remote licenses API url.
58 *
59 * @var string
60 */
61 const REMOTE_LICENSES = 'https://wpget.org/sharing-image/verify/';
62
63 /**
64 * Premium verification event name.
65 *
66 * @var string
67 */
68 const EVENT_PREMIUM = 'sharing_image_event_premium';
69
70 /**
71 * List of settings tabs.
72 *
73 * @var array
74 */
75 private $tabs = array();
76
77 /**
78 * Settings constructor.
79 */
80 public function __construct() {
81 $this->init_tabs();
82 }
83
84 /**
85 * Init class actions and filters.
86 */
87 public function init() {
88 add_action( 'admin_menu', array( $this, 'add_menu' ) );
89
90 // Handle settings POST requests.
91 add_action( 'admin_init', array( $this, 'handle_post_requests' ) );
92
93 // Handle settings AJAX requests.
94 add_action( 'admin_init', array( $this, 'handle_ajax_requests' ) );
95
96 // Allow uploading custom fonts for templates editor.
97 add_action( 'admin_init', array( $this, 'allow_custom_fonts' ) );
98
99 // Add settings link to plugins list.
100 add_filter( 'plugin_action_links', array( $this, 'add_settings_link' ), 10, 2 );
101
102 // Update admin title for different tabs.
103 add_action( 'admin_title', array( $this, 'update_settings_title' ) );
104
105 // Schedule Premium license verification.
106 add_action( self::EVENT_PREMIUM, array( $this, 'launch_verification_event' ), 10, 1 );
107 }
108
109 /**
110 * Add plugin settings page in WordPress menu.
111 */
112 public function add_menu() {
113 /**
114 * Easy way to hide settings page.
115 *
116 * @param bool $hide_settings Set true to hide settings page.
117 */
118 $hide_settings = apply_filters( 'sharing_image_hide_settings', false );
119
120 if ( $hide_settings ) {
121 return;
122 }
123
124 add_options_page(
125 esc_html__( 'Sharing Image settings', 'sharing-image' ),
126 esc_html__( 'Sharing Image', 'sharing-image' ),
127 'manage_options',
128 self::SETTINGS_SLUG,
129 array( $this, 'display_settings' )
130 );
131
132 // Add required assets and objects.
133 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_styles' ) );
134 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
135 }
136
137 /**
138 * Handle settings POST requests.
139 */
140 public function handle_post_requests() {
141 $actions = array(
142 'config' => 'save_settings_config',
143 'editor' => 'save_settings_template',
144 'delete' => 'delete_settings_template',
145 );
146
147 foreach ( $actions as $key => $method ) {
148 $action = 'sharing_image_' . $key;
149
150 if ( method_exists( $this, $method ) ) {
151 add_action( 'admin_post_' . $action, array( $this, $method ) );
152 }
153 }
154 }
155
156 /**
157 * Handle settings AJAX requests.
158 */
159 public function handle_ajax_requests() {
160 $actions = array(
161 'show' => 'show_template_preview',
162 'save' => 'save_template_preview',
163 'verify' => 'verify_premium_key',
164 'revoke' => 'revoke_premium_access',
165 );
166
167 foreach ( $actions as $key => $method ) {
168 $action = 'sharing_image_' . $key;
169
170 if ( method_exists( $this, $method ) ) {
171 add_action( 'wp_ajax_' . $action, array( $this, $method ) );
172 }
173 }
174 }
175
176 /**
177 * Allow uploading custom fonts for templates editor.
178 * This function may affect the security of the site.
179 * Disable font uploading if you are not going to use it.
180 */
181 public function allow_custom_fonts() {
182 /**
183 * Easy way to disable custom font uploading.
184 *
185 * @param bool $disable_ttf Set true to disable fonts uploading.
186 */
187 $disable_fonts = apply_filters( 'sharing_image_allow_custom_fonts', false );
188
189 if ( $disable_fonts ) {
190 return;
191 }
192
193 // Allow True Type fonts uploading.
194 add_filter( 'wp_check_filetype_and_ext', array( $this, 'fix_ttf_mime_type' ), 10, 3 );
195
196 // Add new .ttf font mime type.
197 add_filter( 'upload_mimes', array( $this, 'add_ttf_mime_type' ) );
198 }
199
200 /**
201 * Add settings link to plugins list.
202 *
203 * @param array $actions An array of plugin action links.
204 * @param string $plugin_file Path to the plugin file relative to the plugins directory.
205
206 * @return array Array of settings actions.
207 */
208 public function add_settings_link( $actions, $plugin_file ) {
209 $actions = (array) $actions;
210
211 if ( plugin_basename( SHARING_IMAGE_FILE ) === $plugin_file ) {
212 $actions[] = sprintf(
213 '<a href="%s">%s</a>',
214 admin_url( 'options-general.php?page=' . self::SETTINGS_SLUG ),
215 __( 'Settings', 'sharing-image' )
216 );
217 }
218
219 return $actions;
220 }
221
222 /**
223 * Save settings config fields.
224 */
225 public function save_settings_config() {
226 check_admin_referer( basename( __FILE__ ), 'sharing_image_nonce' );
227
228 if ( ! current_user_can( 'manage_options' ) ) {
229 wp_die( esc_html__( 'Sorry, you are not allowed to manage options for this site.', 'sharing-image' ) );
230 }
231
232 $return = $this->get_tab_link( 'config' );
233
234 if ( null === $return ) {
235 $return = admin_url( 'options-general.php?page=' . self::SETTINGS_SLUG );
236 }
237
238 if ( ! isset( $_POST['sharing_image_config'] ) ) {
239 $this->redirect_with_message( $return, 5 );
240 }
241
242 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
243 $config = $this->sanitize_config( wp_unslash( $_POST['sharing_image_config'] ) );
244
245 $this->update_config( $config );
246
247 // Redirect with success message.
248 $this->redirect_with_message( $return, 1 );
249 }
250
251 /**
252 * Save template editor fields.
253 */
254 public function save_settings_template() {
255 check_admin_referer( basename( __FILE__ ), 'sharing_image_nonce' );
256
257 if ( ! current_user_can( 'manage_options' ) ) {
258 wp_die( esc_html__( 'Sorry, you are not allowed to manage options for this site.', 'sharing-image' ) );
259 }
260
261 $return = admin_url( 'options-general.php?page=' . self::SETTINGS_SLUG );
262
263 if ( ! isset( $_POST['sharing_image_index'] ) ) {
264 $this->redirect_with_message( $return, 2 );
265 }
266
267 $index = absint( wp_unslash( $_POST['sharing_image_index'] ) );
268
269 if ( ! isset( $_POST['sharing_image_editor'] ) ) {
270 $this->redirect_with_message( $return, 2 );
271 }
272
273 // Skip 2nd+ templates if the Premium is not active.
274 if ( $index > 0 && ! $this->is_premium_features() ) {
275 $this->redirect_with_message( $return, 2 );
276 }
277
278 $return = add_query_arg( array( 'template' => $index + 1 ), $return );
279
280 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
281 $editor = $this->sanitize_editor( wp_unslash( $_POST['sharing_image_editor'] ) );
282
283 $this->update_templates( $index, $editor );
284
285 // Redirect with success message.
286 $this->redirect_with_message( $return, 1 );
287 }
288
289 /**
290 * Action to delete template from editor page.
291 */
292 public function delete_settings_template() {
293 check_admin_referer( basename( __FILE__ ), 'nonce' );
294
295 if ( ! current_user_can( 'manage_options' ) ) {
296 return;
297 }
298
299 $return = admin_url( 'options-general.php?page=' . self::SETTINGS_SLUG );
300
301 if ( ! isset( $_REQUEST['template'] ) ) {
302 $this->redirect_with_message( $return, 4 );
303 }
304
305 // Get index from template ID.
306 $index = absint( $_REQUEST['template'] ) - 1;
307
308 if ( ! $this->update_templates( $index ) ) {
309 $this->redirect_with_message( $return, 4 );
310 }
311
312 $this->redirect_with_message( $return, 3 );
313 }
314
315 /**
316 * Show generated template from AJAX request.
317 */
318 public function show_template_preview() {
319 $check = check_ajax_referer( basename( __FILE__ ), 'sharing_image_nonce', false );
320
321 if ( false === $check ) {
322 wp_send_json_error( __( 'Invalid security token. Reload the page and retry.', 'sharing-image' ), 403 );
323 }
324
325 if ( ! isset( $_POST['sharing_image_index'] ) ) {
326 wp_send_json_error( __( 'Poster index undefined.', 'sharing-image' ), 400 );
327 }
328
329 $index = absint( wp_unslash( $_POST['sharing_image_index'] ) );
330
331 if ( ! isset( $_POST['sharing_image_editor'] ) ) {
332 wp_send_json_error( __( 'Editor settings are not set.', 'sharing-image' ), 400 );
333 }
334
335 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
336 $editor = $this->sanitize_editor( wp_unslash( $_POST['sharing_image_editor'] ) );
337
338 // Show poster using generator class.
339 $poster = ( new Generator() )->show( $editor, $index );
340
341 if ( is_wp_error( $poster ) ) {
342 wp_send_json_error( $poster->get_error_message(), 400 );
343 }
344 }
345
346 /**
347 * Show generated template from AJAX request.
348 */
349 public function save_template_preview() {
350 $check = check_ajax_referer( basename( __FILE__ ), 'sharing_image_nonce', false );
351
352 if ( false === $check ) {
353 wp_send_json_error( __( 'Invalid security token. Reload the page and retry.', 'sharing-image' ), 403 );
354 }
355
356 if ( ! isset( $_POST['sharing_image_index'] ) ) {
357 wp_send_json_error( __( 'Poster index undefined.', 'sharing-image' ), 400 );
358 }
359
360 $index = absint( wp_unslash( $_POST['sharing_image_index'] ) );
361
362 if ( ! isset( $_POST['sharing_image_editor'] ) ) {
363 wp_send_json_error( __( 'Editor settings are not set.', 'sharing-image' ), 400 );
364 }
365
366 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
367 $editor = $this->sanitize_editor( wp_unslash( $_POST['sharing_image_editor'] ) );
368
369 // Save poster using generator class.
370 $poster = ( new Generator() )->save( $editor, $index );
371
372 if ( is_wp_error( $poster ) ) {
373 wp_send_json_error( $poster->get_error_message(), 400 );
374 }
375
376 wp_send_json_success( $poster );
377 }
378
379 /**
380 * Verify Premium key from AJAX request.
381 */
382 public function verify_premium_key() {
383 $check = check_ajax_referer( basename( __FILE__ ), 'sharing_image_nonce', false );
384
385 if ( false === $check ) {
386 wp_send_json_error( __( 'Invalid security token. Reload the page and retry.', 'sharing-image' ), 403 );
387 }
388
389 if ( empty( $_POST['sharing_image_key'] ) ) {
390 wp_send_json_error( __( 'Premium key undefined.', 'sharing-image' ), 400 );
391 }
392
393 $key = sanitize_text_field( wp_unslash( $_POST['sharing_image_key'] ) );
394
395 $args = array(
396 'body' => array(
397 'key' => $key,
398 'domain' => wp_parse_url( site_url(), PHP_URL_HOST ),
399 ),
400 );
401
402 $response = wp_remote_post( self::REMOTE_LICENSES, $args );
403
404 if ( is_wp_error( $response ) ) {
405 wp_send_json_error( __( 'Unable to get a response from the verification server.', 'sharing-image' ), 400 );
406 }
407
408 $answer = json_decode( $response['body'], true );
409
410 if ( ! isset( $answer['success'] ) ) {
411 wp_send_json_error( __( 'Unable to get a response from the verification server.', 'sharing-image' ), 400 );
412 }
413
414 // Remove license verification event.
415 wp_unschedule_hook( self::EVENT_PREMIUM );
416
417 if ( true === $answer['success'] ) {
418 $license = $this->update_license( true, $key );
419
420 // Schedule license verification twice daily.
421 $this->schedule_verification( array( $key ) );
422
423 wp_send_json_success( $license );
424 }
425
426 $error = array(
427 'success' => false,
428 'data' => __( 'Verification failed.', 'sharing-image' ),
429 );
430
431 if ( isset( $answer['result'] ) ) {
432 $error['code'] = $answer['result'];
433 }
434
435 $this->update_license( false, $key );
436
437 wp_send_json( $error, 403 );
438 }
439
440 /**
441 * Revoke Premium access from AJAX request.
442 */
443 public function revoke_premium_access() {
444 $check = check_ajax_referer( basename( __FILE__ ), 'sharing_image_nonce', false );
445
446 if ( false === $check ) {
447 wp_send_json_error( __( 'Invalid security token. Reload the page and retry.', 'sharing-image' ), 403 );
448 }
449
450 // Remove license verification event.
451 wp_unschedule_hook( self::EVENT_PREMIUM );
452
453 // Disable Premium license.
454 $license = $this->update_license( false );
455
456 wp_send_json_success( $license );
457 }
458
459 /**
460 * Show plugin settings.
461 */
462 public function display_settings() {
463 if ( ! $this->is_settings_screen() ) {
464 return;
465 }
466
467 include_once SHARING_IMAGE_DIR . 'templates/settings.php';
468
469 /**
470 * Fires on settings template including.
471 */
472 do_action( 'sharing_image_settings' );
473 }
474
475 /**
476 * Fix .ttf files mime.
477 *
478 * @param array $types Values for the extension, mime type, and corrected filename.
479 * @param string $file Full path to the file.
480 * @param string $filename The name of the file (may differ from $file due to.
481 *
482 * @return array List of file types.
483 */
484 public function fix_ttf_mime_type( $types, $file, $filename ) {
485 $extension = pathinfo( $filename, PATHINFO_EXTENSION );
486
487 if ( 'ttf' === $extension ) {
488 $types['ext'] = false;
489
490 if ( current_user_can( 'manage_options' ) ) {
491 $types['ext'] = 'ttf';
492 $types['type'] = 'application/x-font-ttf';
493 }
494 }
495
496 return $types;
497 }
498
499 /**
500 * Add new .ttf font mime type.
501 *
502 * @param array $types Allowed file types to upload.
503 *
504 * @return array Allowed file types to upload.
505 */
506 public function add_ttf_mime_type( $types ) {
507 $types['ttf'] = 'application/x-font-ttf';
508
509 return $types;
510 }
511
512 /**
513 * Enqueue settings styles.
514 */
515 public function enqueue_styles() {
516 if ( ! $this->is_settings_screen() ) {
517 return;
518 }
519
520 wp_enqueue_style(
521 'sharing-image-settings',
522 SHARING_IMAGE_URL . 'assets/styles/settings.css',
523 array(),
524 SHARING_IMAGE_VERSION,
525 'all'
526 );
527 }
528
529 /**
530 * Enqueue settings scripts.
531 */
532 public function enqueue_scripts() {
533 if ( ! $this->is_settings_screen() ) {
534 return;
535 }
536
537 wp_enqueue_script(
538 'sharing-image-settings',
539 SHARING_IMAGE_URL . 'assets/scripts/settings.js',
540 array( 'wp-i18n', 'wp-polyfill-url', 'wp-polyfill-formdata' ),
541 SHARING_IMAGE_VERSION,
542 true
543 );
544
545 wp_enqueue_media();
546
547 // Translations availible only for WP 5.0+.
548 wp_set_script_translations( 'sharing-image-settings', 'sharing-image' );
549
550 $object = $this->create_script_object();
551
552 // Add settings script object.
553 wp_localize_script( 'sharing-image-settings', 'sharingImageSettings', $object );
554 }
555
556 /**
557 * Get templates list from options.
558 *
559 * @return array List of templates.
560 */
561 public function get_templates() {
562 $templates = get_option( self::OPTION_TEMPLATES, array() );
563
564 if ( ! $this->is_premium_features() ) {
565 $templates = array_slice( $templates, 0, 1 );
566 }
567
568 /**
569 * Filters list of templates.
570 *
571 * @param array $templates List of templates.
572 */
573 return apply_filters( 'sharing_image_get_templates', $templates );
574 }
575
576 /**
577 * Update templates using index.
578 *
579 * @param int $index Template index to update.
580 * @param array $editor New template data.
581 */
582 public function update_templates( $index, $editor = null ) {
583 // Method get_templates() is not used to save old templates during Premium switching.
584 $templates = get_option( self::OPTION_TEMPLATES, array() );
585
586 $templates[ $index ] = $editor;
587
588 if ( null === $editor ) {
589 unset( $templates[ $index ] );
590 }
591
592 /**
593 * Filters list of templates before update in database.
594 *
595 * @param array $templates List of reindexed templates.
596 */
597 $templates = apply_filters( 'sharing_image_update_templates', array_values( $templates ) );
598
599 return update_option( self::OPTION_TEMPLATES, $templates );
600 }
601
602 /**
603 * Get plugin config settings.
604 *
605 * @return array List of plugin config settings.
606 */
607 public function get_config() {
608 $config = get_option( self::OPTION_CONFIG, array() );
609
610 /**
611 * Filters settigns config.
612 *
613 * @param array List of plugin config settings.
614 */
615 return apply_filters( 'sharing_image_get_config', $config );
616 }
617
618 /**
619 * Update config settings.
620 *
621 * @param array $config License settings config data.
622 */
623 public function update_config( $config ) {
624 /**
625 * Filters config options before their update in database.
626 *
627 * @param array $config Settings config data.
628 */
629 $config = apply_filters( 'sharing_image_update_config', $config );
630
631 update_option( self::OPTION_CONFIG, $config );
632 }
633
634 /**
635 * Get plugin license settings.
636 *
637 * @return array List of plugin license settings.
638 */
639 public function get_license() {
640 $license = get_option( self::OPTION_LICENSE, array() );
641
642 /**
643 * Check if the plugin in development mode.
644 *
645 * @param bool Current development state. Disabled by default.
646 */
647 $develop = apply_filters( 'sharing_image_develop', false );
648
649 if ( $develop ) {
650 $license['develop'] = true;
651 }
652
653 /**
654 * Filters license settings.
655 *
656 * @param array List of plugin license settings.
657 */
658 return apply_filters( 'sharing_image_get_license', $license );
659 }
660
661 /**
662 * Set license options.
663 *
664 * @param bool $premium Premium status.
665 * @param string $key License key.
666 * @param string $error Verification error code.
667
668 * @return array License options.
669 */
670 public function update_license( $premium, $key = '', $error = '' ) {
671 $license = get_option( self::OPTION_LICENSE, array() );
672
673 $license['premium'] = $premium;
674
675 if ( ! empty( $key ) ) {
676 $license['key'] = $key;
677 }
678
679 unset( $license['error'] );
680
681 if ( ! empty( $error ) ) {
682 $license['error'] = $error;
683 }
684
685 // Save updated license settings in database.
686 update_option( self::OPTION_LICENSE, $license );
687
688 return $license;
689 }
690
691 /**
692 * Get directory to uploaded posters.
693 *
694 * @return array Path and url to upload directory.
695 */
696 public function get_upload_dir() {
697 $config = $this->get_config();
698
699 if ( ! isset( $config['uploads'] ) ) {
700 $config['uploads'] = 'default';
701 }
702
703 // Create custom upload directory.
704 if ( isset( $config['storage'] ) && 'custom' === $config['uploads'] ) {
705 return $this->create_upload_dir( $config['storage'] );
706 }
707
708 $uploads = wp_upload_dir();
709
710 /**
711 * Filters upload directory.
712 *
713 * @param array $dir Path and url to upload directory.
714 */
715 return apply_filters( 'sharing_image_upload_dir', array( $uploads['path'], $uploads['url'] ) );
716 }
717
718 /**
719 * Get generated image file format.
720 *
721 * @param string $format Optional. Default image format.
722
723 * @return string Image file format.
724 */
725 public function get_file_format( $format = 'jpg' ) {
726 $config = $this->get_config();
727
728 if ( isset( $config['format'] ) ) {
729 $format = $config['format'];
730 }
731
732 /**
733 * Filters Image file format.
734 *
735 * @param string $format Image file format.
736 */
737 return apply_filters( 'sharing_image_file_format', $format );
738 }
739
740 /**
741 * Get quality of generated poster.
742 *
743 * @param int $quality Optional. Default image quality.
744 *
745 * @return int Image quality.
746 */
747 public function get_quality( $quality = 90 ) {
748 $config = $this->get_config();
749
750 if ( isset( $config['quality'] ) ) {
751 $quality = $config['quality'];
752 }
753
754 /**
755 * Filters poster image quality.
756 *
757 * @param string $quality Image quality.
758 */
759 return apply_filters( 'sharing_image_poster_quality', $quality );
760 }
761
762 /**
763 * Try to get default poster image data.
764 * Returns array with image url, width and height.
765 *
766 * @see https://developer.wordpress.org/reference/functions/wp_get_attachment_image_src/
767 *
768 * @return array|false Array of image data, or boolean false if no image is available.
769 */
770 public function get_default_poster_src() {
771 $config = $this->get_config();
772
773 if ( empty( $config['default'] ) ) {
774 return false;
775 }
776
777 $poster = wp_get_attachment_image_src( $config['default'], 'full' );
778
779 if ( is_array( $poster ) ) {
780 $poster = array_slice( $poster, 0, 3 );
781 }
782
783 return apply_filters( 'sharing_image_default_poster_src', $poster );
784 }
785
786 /**
787 * Update settings page title.
788 *
789 * @param string $title Plugin settings page title.
790 *
791 * @return string Plugin settings title
792 */
793 public function update_settings_title( $title ) {
794 if ( ! $this->is_settings_screen() ) {
795 return $title;
796 }
797
798 $tab = $this->get_current_tab();
799
800 if ( null === $tab ) {
801 return $title;
802 }
803
804 if ( empty( $this->tabs[ $tab ]['label'] ) ) {
805 return $title;
806 }
807
808 $label = esc_html( $this->tabs[ $tab ]['label'] );
809
810 return sprintf( '%s &ndash; %s', $label, $title );
811 }
812
813 /**
814 * Launch scheduled license verification event.
815 * Do not disable Premium if the verification server does not respond.
816 *
817 * @param string $key License key.
818 */
819 public function launch_verification_event( $key ) {
820 $args = array(
821 'body' => array(
822 'key' => $key,
823 'domain' => wp_parse_url( site_url(), PHP_URL_HOST ),
824 ),
825 );
826
827 $response = wp_remote_post( self::REMOTE_LICENSES, $args );
828
829 if ( is_wp_error( $response ) ) {
830 return;
831 }
832
833 $answer = json_decode( $response['body'], true );
834
835 if ( ! isset( $answer['success'] ) ) {
836 return;
837 }
838
839 if ( true === $answer['success'] ) {
840 return $this->update_license( true, $key );
841 }
842
843 if ( ! isset( $answer['result'] ) ) {
844 return $this->update_license( false, $key );
845 }
846
847 $this->update_license( false, $key, $answer['result'] );
848 }
849
850 /**
851 * Check if Premium features availible.
852 *
853 * @return bool Whether premium featured enabled.
854 */
855 public function is_premium_features() {
856 $license = $this->get_license();
857
858 if ( ! empty( $license['premium'] ) || ! empty( $license['develop'] ) ) {
859 return true;
860 }
861
862 return false;
863 }
864
865 /**
866 * Schedule license verification.
867 *
868 * @param array $args List of event arguments. License key by default.
869 */
870 public function schedule_verification( $args = array() ) {
871 if ( wp_next_scheduled( self::EVENT_PREMIUM, $args ) ) {
872 return;
873 }
874
875 wp_schedule_event( time() + DAY_IN_SECONDS / 2, 'twicedaily', self::EVENT_PREMIUM, $args );
876 }
877
878 /**
879 * Create script object to inject with settings.
880 *
881 * @return array Filtered script settings object.
882 */
883 private function create_script_object() {
884 $uploads = wp_get_upload_dir();
885
886 // Get uploads directory path from WordPress root.
887 $basedir = str_replace( ABSPATH, '', $uploads['basedir'] );
888
889 $object = array(
890 'nonce' => wp_create_nonce( basename( __FILE__ ) ),
891 'links' => array(
892 'uploads' => esc_url( admin_url( 'upload.php' ) ),
893 'action' => esc_url( admin_url( 'admin-post.php' ) ),
894 'premium' => esc_url_raw( $this->get_tab_link( 'premium' ) ),
895 'storage' => path_join( $basedir, 'sharing-image' ),
896 ),
897 'templates' => $this->get_templates(),
898 'config' => $this->get_config(),
899 'license' => $this->get_license(),
900 'fonts' => $this->get_fonts(),
901 );
902
903 /**
904 * Filters settings script object.
905 *
906 * @param array $object Array of settings script object.
907 */
908 return apply_filters( 'sharing_image_settings_object', $object );
909 }
910
911 /**
912 * Sanitize editor template settings.
913 *
914 * @param array $editor Template editor settings.
915 *
916 * @return array
917 */
918 private function sanitize_editor( $editor ) {
919 $sanitized = array();
920
921 if ( ! empty( $editor['preview'] ) ) {
922 $sanitized['preview'] = sanitize_text_field( $editor['preview'] );
923 }
924
925 if ( ! empty( $editor['title'] ) ) {
926 $sanitized['title'] = sanitize_text_field( $editor['title'] );
927 }
928
929 if ( ! empty( $editor['attachment'] ) ) {
930 $sanitized['attachment'] = absint( $editor['attachment'] );
931 }
932
933 if ( ! empty( $editor['suspend'] ) ) {
934 $sanitized['suspend'] = 'suspend';
935 }
936
937 $sanitized['fill'] = '#000000';
938
939 if ( ! empty( $editor['fill'] ) ) {
940 $sanitized['fill'] = sanitize_hex_color( $editor['fill'] );
941 }
942
943 $sanitized['background'] = 'blank';
944
945 if ( isset( $editor['background'] ) ) {
946 $background = array( 'dynamic', 'blank', 'permanent' );
947
948 // Set default background for permanent option without attachment.
949 if ( empty( $sanitized['attachment'] ) ) {
950 $background = array_diff( $background, array( 'permanent' ) );
951 }
952
953 if ( in_array( $editor['background'], $background, true ) ) {
954 $sanitized['background'] = $editor['background'];
955 }
956 }
957
958 $sanitized['width'] = 1200;
959
960 if ( ! empty( $editor['width'] ) ) {
961 $sanitized['width'] = absint( $editor['width'] );
962 }
963
964 $sanitized['height'] = 630;
965
966 if ( ! empty( $editor['height'] ) ) {
967 $sanitized['height'] = absint( $editor['height'] );
968 }
969
970 if ( isset( $editor['layers'] ) && is_array( $editor['layers'] ) ) {
971 $layers = array();
972
973 foreach ( $editor['layers'] as $layer ) {
974 if ( empty( $layer['type'] ) ) {
975 continue;
976 }
977
978 switch ( $layer['type'] ) {
979 case 'text':
980 $layers[] = $this->sanitize_text_layer( $layer );
981 break;
982
983 case 'image':
984 $layers[] = $this->sanitize_image_layer( $layer );
985 break;
986
987 case 'filter':
988 $layers[] = $this->sanitize_filter_layer( $layer );
989 break;
990
991 case 'rectangle':
992 $layers[] = $this->sanitize_rectangle_layer( $layer );
993 break;
994 }
995 }
996
997 $sanitized['layers'] = $layers;
998 }
999
1000 /**
1001 * Filters template editor sanitized fields.
1002 *
1003 * @param array $sanitized List of sanitized editor fields.
1004 * @param array $editor List of editor fields before sanitization.
1005 */
1006 return apply_filters( 'sharing_image_sanitize_editor', $sanitized, $editor );
1007 }
1008
1009 /**
1010 * Sanitize template editor text layer.
1011 *
1012 * @param array $layer Layer settings.
1013 *
1014 * @return array Sanitized layer settings.
1015 */
1016 private function sanitize_text_layer( $layer ) {
1017 $sanitized = array();
1018
1019 // No need to sanitize after switch.
1020 $sanitized['type'] = $layer['type'];
1021
1022 if ( ! empty( $layer['dynamic'] ) ) {
1023 $sanitized['dynamic'] = 'dynamic';
1024 }
1025
1026 if ( isset( $layer['title'] ) ) {
1027 $sanitized['title'] = sanitize_text_field( $layer['title'] );
1028 }
1029
1030 if ( isset( $layer['content'] ) ) {
1031 $sanitized['content'] = sanitize_textarea_field( $layer['content'] );
1032 }
1033
1034 if ( isset( $layer['sample'] ) ) {
1035 $sanitized['sample'] = sanitize_textarea_field( $layer['sample'] );
1036 }
1037
1038 $sanitized['preset'] = 'none';
1039
1040 if ( isset( $layer['preset'] ) ) {
1041 $preset = array( 'title', 'excerpt' );
1042
1043 if ( in_array( $layer['preset'], $preset, true ) ) {
1044 $sanitized['preset'] = $layer['preset'];
1045 }
1046 }
1047
1048 $sanitized['color'] = '#ffffff';
1049
1050 if ( ! empty( $layer['color'] ) ) {
1051 $sanitized['color'] = sanitize_hex_color( $layer['color'] );
1052 }
1053
1054 $sanitized['horizontal'] = 'left';
1055
1056 if ( isset( $layer['horizontal'] ) ) {
1057 $horizontal = array( 'center', 'right' );
1058
1059 if ( in_array( $layer['horizontal'], $horizontal, true ) ) {
1060 $sanitized['horizontal'] = $layer['horizontal'];
1061 }
1062 }
1063
1064 $sanitized['vertical'] = 'top';
1065
1066 if ( isset( $layer['vertical'] ) ) {
1067 $vertical = array( 'center', 'bottom' );
1068
1069 if ( in_array( $layer['vertical'], $vertical, true ) ) {
1070 $sanitized['vertical'] = $layer['vertical'];
1071 }
1072 }
1073
1074 if ( isset( $layer['fontsize'] ) ) {
1075 $sanitized['fontsize'] = absint( $layer['fontsize'] );
1076 }
1077
1078 if ( isset( $layer['lineheight'] ) ) {
1079 $sanitized['lineheight'] = (float) $layer['lineheight'];
1080 }
1081
1082 if ( isset( $layer['fontname'] ) ) {
1083 $sanitized['fontname'] = sanitize_text_field( $layer['fontname'] );
1084 }
1085
1086 if ( ! empty( $layer['fontfile'] ) ) {
1087 $sanitized['fontfile'] = absint( $layer['fontfile'] );
1088 }
1089
1090 $sizes = array( 'x', 'y', 'width', 'height' );
1091
1092 foreach ( $sizes as $size ) {
1093 if ( ! isset( $layer[ $size ] ) || '' === $layer[ $size ] ) {
1094 continue;
1095 }
1096
1097 $sanitized[ $size ] = absint( $layer[ $size ] );
1098 }
1099
1100 return $sanitized;
1101 }
1102
1103 /**
1104 * Sanitize template editor image layer.
1105 *
1106 * @param array $layer Layer settings.
1107 *
1108 * @return array Sanitized image layer settings.
1109 */
1110 private function sanitize_image_layer( $layer ) {
1111 $sanitized = array();
1112
1113 // No need to sanitize after switch.
1114 $sanitized['type'] = $layer['type'];
1115
1116 if ( ! empty( $layer['attachment'] ) ) {
1117 $sanitized['attachment'] = absint( $layer['attachment'] );
1118 }
1119
1120 $sizes = array( 'x', 'y', 'width', 'height' );
1121
1122 foreach ( $sizes as $size ) {
1123 if ( ! isset( $layer[ $size ] ) || '' === $layer[ $size ] ) {
1124 continue;
1125 }
1126
1127 $sanitized[ $size ] = absint( $layer[ $size ] );
1128 }
1129
1130 return $sanitized;
1131 }
1132
1133 /**
1134 * Sanitize template editor filter layer.
1135 *
1136 * @param array $layer Layer settings.
1137 *
1138 * @return array Sanitized filter layer settings.
1139 */
1140 private function sanitize_filter_layer( $layer ) {
1141 $sanitized = array();
1142
1143 // No need to sanitize after switch.
1144 $sanitized['type'] = $layer['type'];
1145
1146 if ( ! empty( $layer['grayscale'] ) ) {
1147 $sanitized['grayscale'] = 'grayscale';
1148 }
1149
1150 if ( ! empty( $layer['blur'] ) ) {
1151 $sanitized['blur'] = 'blur';
1152 }
1153
1154 $sanitized['brightness'] = 0;
1155
1156 if ( isset( $layer['brightness'] ) ) {
1157 $brightness = (int) $layer['brightness'];
1158
1159 if ( $brightness >= -100 && $brightness <= 100 ) {
1160 $sanitized['brightness'] = $brightness;
1161 }
1162 }
1163
1164 $sanitized['contrast'] = 0;
1165
1166 if ( isset( $layer['contrast'] ) ) {
1167 $contrast = (int) $layer['contrast'];
1168
1169 if ( $contrast >= -100 && $contrast <= 100 ) {
1170 $sanitized['contrast'] = $contrast;
1171 }
1172 }
1173
1174 $sanitized['blackout'] = 0;
1175
1176 if ( isset( $layer['blackout'] ) ) {
1177 $blackout = (int) $layer['blackout'];
1178
1179 if ( $blackout >= 0 && $blackout <= 100 ) {
1180 $sanitized['blackout'] = $blackout;
1181 }
1182 }
1183
1184 return $sanitized;
1185 }
1186
1187 /**
1188 * Sanitize template editor rectagle layer.
1189 *
1190 * @param array $layer Layer settings.
1191 *
1192 * @return array Sanitized rectangle layer settings.
1193 */
1194 private function sanitize_rectangle_layer( $layer ) {
1195 $sanitized = array();
1196
1197 // No need to sanitize after switch.
1198 $sanitized['type'] = $layer['type'];
1199
1200 if ( ! empty( $layer['outline'] ) ) {
1201 $sanitized['outline'] = 'outline';
1202 }
1203
1204 $sanitized['color'] = '#ffffff';
1205
1206 if ( ! empty( $layer['color'] ) ) {
1207 $sanitized['color'] = sanitize_hex_color( $layer['color'] );
1208 }
1209
1210 $sanitized['opacity'] = 0;
1211
1212 if ( isset( $layer['opacity'] ) ) {
1213 $opacity = (float) $layer['opacity'];
1214
1215 if ( $opacity >= 0 && $opacity <= 100 ) {
1216 $sanitized['opacity'] = $opacity;
1217 }
1218 }
1219
1220 $sanitized['thickness'] = 0;
1221
1222 if ( isset( $layer['thickness'] ) ) {
1223 $thickness = (int) $layer['thickness'];
1224
1225 if ( $thickness >= 0 && $thickness <= 50 ) {
1226 $sanitized['thickness'] = $thickness;
1227 }
1228 }
1229
1230 $sizes = array( 'x', 'y', 'width', 'height' );
1231
1232 foreach ( $sizes as $size ) {
1233 if ( ! isset( $layer[ $size ] ) || '' === $layer[ $size ] ) {
1234 continue;
1235 }
1236
1237 $sanitized[ $size ] = absint( $layer[ $size ] );
1238 }
1239
1240 return $sanitized;
1241 }
1242
1243 /**
1244 * Sanitize config settings.
1245 *
1246 * @param array $config Config settings.
1247 *
1248 * @return array Sanitized config settings.
1249 */
1250 private function sanitize_config( $config ) {
1251 $sanitized = array();
1252
1253 if ( ! empty( $config['default'] ) ) {
1254 $sanitized['default'] = absint( $config['default'] );
1255 }
1256
1257 $sanitized['format'] = 'jpg';
1258
1259 if ( isset( $config['format'] ) ) {
1260 $format = $config['format'];
1261
1262 if ( in_array( $format, array( 'jpg', 'png' ), true ) ) {
1263 $sanitized['format'] = $config['format'];
1264 }
1265 }
1266
1267 if ( isset( $config['quality'] ) ) {
1268 $quality = (int) $config['quality'];
1269
1270 if ( $quality >= 1 && $quality <= 100 ) {
1271 $sanitized['quality'] = $quality;
1272 }
1273 }
1274
1275 $sanitized['uploads'] = 'default';
1276
1277 if ( isset( $config['uploads'] ) ) {
1278 $uploads = $config['uploads'];
1279
1280 if ( in_array( $uploads, array( 'custom', 'default' ), true ) ) {
1281 $sanitized['uploads'] = $config['uploads'];
1282 }
1283 }
1284
1285 if ( isset( $config['storage'] ) ) {
1286 $sanitized['storage'] = sanitize_text_field( $config['storage'] );
1287 }
1288
1289 /**
1290 * Filters template editor sanitized fields.
1291 *
1292 * @param array $sanitized List of sanitized config fields.
1293 * @param array $config List of config fields before sanitization.
1294 */
1295 return apply_filters( 'sharing_image_sanitize_config', $sanitized, $config );
1296 }
1297
1298 /**
1299 * Show settings tab template.
1300 */
1301 private function show_settings_section() {
1302 $tab = $this->get_current_tab();
1303
1304 if ( null === $tab ) {
1305 return;
1306 }
1307
1308 include_once SHARING_IMAGE_DIR . "templates/{$tab}.php";
1309 }
1310
1311 /**
1312 * Show settings messages and errors after post actions.
1313 */
1314 private function show_settings_message() {
1315 // phpcs:ignore WordPress.Security.NonceVerification
1316 $message = isset( $_GET['message'] ) ? absint( $_GET['message'] ) : 0;
1317
1318 switch ( $message ) {
1319 case 1:
1320 add_settings_error( 'sharing-image', 'sharing-image', __( 'Settings successfully updated.', 'sharing-image' ), 'updated' );
1321 break;
1322
1323 case 2:
1324 add_settings_error( 'sharing-image', 'sharing-image', __( 'Failed to save template settings.', 'sharing-image' ) );
1325 break;
1326
1327 case 3:
1328 add_settings_error( 'sharing-image', 'sharing-image', __( 'Template successfully deleted.', 'sharing-image' ), 'updated' );
1329 break;
1330
1331 case 4:
1332 add_settings_error( 'sharing-image', 'sharing-image', __( 'Failed to delete template.', 'sharing-image' ) );
1333 break;
1334
1335 case 5:
1336 add_settings_error( 'sharing-image', 'sharing-image', __( 'Failed to save configuration settings.', 'sharing-image' ) );
1337 break;
1338 }
1339
1340 settings_errors( 'sharing-image' );
1341 }
1342
1343 /**
1344 * Set list of settings page tabs.
1345 */
1346 private function init_tabs() {
1347 $tabs = array(
1348 'templates' => array(
1349 'label' => __( 'Templates', 'sharing-image' ),
1350 'link' => admin_url( 'options-general.php?page=' . self::SETTINGS_SLUG ),
1351 'default' => true,
1352 ),
1353 'config' => array(
1354 'label' => __( 'Configuration', 'sharing-image' ),
1355 'link' => admin_url( 'options-general.php?page=' . self::SETTINGS_SLUG . '&tab=config' ),
1356 ),
1357 'premium' => array(
1358 'label' => __( 'Premium', 'sharing-image' ),
1359 'link' => admin_url( 'options-general.php?page=' . self::SETTINGS_SLUG . '&tab=premium' ),
1360 ),
1361 );
1362
1363 /**
1364 * Filters tabs in settings page.
1365 *
1366 * @param array $tabs List of settings tabs.
1367 */
1368 $this->tabs = apply_filters( 'sharing_image_settings_tabs', $tabs );
1369 }
1370
1371 /**
1372 * Print menu on settings page.
1373 */
1374 private function show_settings_menu() {
1375 $current = $this->get_current_tab();
1376
1377 foreach ( $this->tabs as $tab => $args ) {
1378 $classes = array(
1379 'sharing-image-tab',
1380 );
1381
1382 if ( $current === $tab ) {
1383 $classes[] = 'active';
1384 }
1385
1386 if ( null === $current && ! empty( $args['default'] ) ) {
1387 $classes[] = 'active';
1388 }
1389
1390 printf(
1391 '<a href="%2$s" class="%1$s">%3$s</a>',
1392 esc_attr( implode( ' ', $classes ) ),
1393 esc_url( $args['link'] ),
1394 esc_html( $args['label'] )
1395 );
1396 }
1397 }
1398
1399 /**
1400 * Get availible fonts.
1401 *
1402 * @return array List of availible poster fonts.
1403 */
1404 private function get_fonts() {
1405 $fonts = array(
1406 'open-sans' => 'Open Sans',
1407 'merriweather' => 'Merriweather',
1408 'roboto-slab' => 'Roboto Slab',
1409 'ubuntu' => 'Ubuntu',
1410 'rubik-bold' => 'Rubik Bold',
1411 'montserrat' => 'Montserrat',
1412 );
1413
1414 /**
1415 * Filters poster fonts.
1416 *
1417 * @param array List of availible poster fonts.
1418 */
1419 return apply_filters( 'sharing_image_poster_fonts', $fonts );
1420 }
1421
1422 /**
1423 * Get tab link by slug.
1424 *
1425 * @param string $tab Tab name.
1426 *
1427 * @return string|null Tab link.
1428 */
1429 private function get_tab_link( $tab ) {
1430 if ( empty( $this->tabs[ $tab ]['link'] ) ) {
1431 return null;
1432 }
1433
1434 return $this->tabs[ $tab ]['link'];
1435 }
1436
1437 /**
1438 * Get current tab.
1439 *
1440 * @return string|null Current tab name.
1441 */
1442 private function get_current_tab() {
1443 // phpcs:disable WordPress.Security.NonceVerification
1444 if ( ! empty( $_GET['tab'] ) ) {
1445 $tab = sanitize_file_name( wp_unslash( $_GET['tab'] ) );
1446
1447 if ( array_key_exists( $tab, $this->tabs ) ) {
1448 return $tab;
1449 }
1450 }
1451 // phpcs:enable WordPress.Security.NonceVerification
1452
1453 return null;
1454 }
1455
1456 /**
1457 * Create upload directory and return its path and url
1458 *
1459 * @param string $storage Relative directory path.
1460 *
1461 * @return array Path and url to upload directory.
1462 */
1463 private function create_upload_dir( $storage ) {
1464 $storage = trim( $storage, '/' );
1465
1466 /**
1467 * Change permissions when creating new folders.
1468 *
1469 * @param int $permissions New directory access permissions. By default 0755.
1470 */
1471 $permissions = apply_filters( 'sharing_image_directory_permissions', 0755 );
1472
1473 // We do not pay attention to the possible error.
1474 mkdir( ABSPATH . $storage, $permissions, true );
1475
1476 return array( ABSPATH . $storage, site_url( $storage ) );
1477 }
1478
1479 /**
1480 * Add message id to the back link and redirect
1481 *
1482 * @param string $return Redirect link.
1483 * @param int $message Settings error message id.
1484 */
1485 private function redirect_with_message( $return, $message ) {
1486 $return = add_query_arg( array( 'message' => $message ), $return );
1487
1488 wp_safe_redirect( $return );
1489 exit;
1490 }
1491
1492 /**
1493 * Is current admin screen the plugin options screen.
1494 *
1495 * @return bool Whether the settings screen of this plugin is displayed or not.
1496 */
1497 private function is_settings_screen() {
1498 $current_screen = get_current_screen();
1499
1500 if ( $current_screen && self::SCREEN_ID === $current_screen->id ) {
1501 return true;
1502 }
1503
1504 return false;
1505 }
1506 }
1507