Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
84.24% covered (warning)
84.24%
171 / 203
35.29% covered (danger)
35.29%
6 / 17
CRAP
0.00% covered (danger)
0.00%
0 / 1
Settings
84.24% covered (warning)
84.24%
171 / 203
35.29% covered (danger)
35.29%
6 / 17
54.29
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 migrate_old_option
95.24% covered (success)
95.24%
20 / 21
0.00% covered (danger)
0.00%
0 / 1
7
 register_settings
100.00% covered (success)
100.00%
115 / 115
100.00% covered (success)
100.00%
1 / 1
1
 sanitize_message_template
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 get_image_generator_settings
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 get_utm_settings
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_message_template
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 get_social_notes_config
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 should_show_pricing_page
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 is_social_notes_enabled
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_settings
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
3.01
 get_initial_state
n/a
0 / 0
n/a
0 / 0
1
 update_settings
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
72
 update_social_image_generator_settings
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
3.07
 is_sig_available
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 sig_get_default_template
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 sig_get_default_image_id
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
4.05
 sig_get_default_font
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2/**
3 * Settings class.
4 *
5 * @package automattic/jetpack-publicize
6 */
7
8namespace Automattic\Jetpack\Publicize\Jetpack_Social_Settings;
9
10use Automattic\Jetpack\Modules;
11use Automattic\Jetpack\Publicize\Social_Image_Generator\Templates;
12
13/**
14 * This class is used to get and update Jetpack_Social_Settings.
15 * Currently supported features:
16 *      - Social Image Generator
17 *      - UTM Settings
18 *      - Social Notes
19 *
20 * @phan-constructor-used-for-side-effects
21 */
22class Settings {
23    /**
24     * Name of the database option.
25     *
26     * @var string
27     */
28    const OPTION_PREFIX            = 'jetpack_social_';
29    const IMAGE_GENERATOR_SETTINGS = 'image_generator_settings';
30
31    const DEFAULT_IMAGE_GENERATOR_SETTINGS = array(
32        'enabled'  => false,
33        'template' => Templates::DEFAULT_TEMPLATE,
34    );
35
36    const UTM_SETTINGS = 'utm_settings';
37
38    const DEFAULT_UTM_SETTINGS = array(
39        'enabled' => false,
40    );
41
42    const NOTES_CONFIG = 'notes_config';
43
44    const DEFAULT_NOTES_CONFIG = array(
45        'append_link' => true,
46    );
47
48    const MESSAGE_TEMPLATE = 'message_template';
49
50    /**
51     * Default global message template.
52     */
53    const DEFAULT_MESSAGE_TEMPLATE = "{title}\n\n{excerpt}\n\n{url}";
54
55    /**
56     * Storage cap for message templates, in characters. Real-world templates are a few hundred characters at most.
57     */
58    const MESSAGE_TEMPLATE_MAX_LENGTH = 8000;
59
60    // Legacy named options.
61    const JETPACK_SOCIAL_NOTE_CPT_ENABLED   = 'jetpack-social-note';
62    const JETPACK_SOCIAL_SHOW_PRICING_PAGE  = 'jetpack-social_show_pricing_page';
63    const NOTES_FLUSH_REWRITE_RULES_FLUSHED = 'jetpack_social_rewrite_rules_flushed';
64
65    /**
66     * Whether the actions have been hooked into.
67     *
68     * @var bool
69     */
70    protected static $actions_hooked_in = false;
71
72    /**
73     * Constructor.
74     */
75    public function __construct() {
76
77        if ( ! self::$actions_hooked_in ) {
78            add_action( 'rest_api_init', array( $this, 'register_settings' ) );
79            add_action( 'admin_init', array( $this, 'register_settings' ) );
80
81            self::$actions_hooked_in = true;
82        }
83    }
84
85    /**
86     * Migrate old options to the new settings. Previously SIG settings were stored in the
87     * jetpack_social_image_generator_settings option. Now they are stored in the jetpack_social_settings.
88     *
89     * TODO: Work out if this is possible on plugin upgrade
90     *
91     * @return void
92     */
93    private function migrate_old_option() {
94        // Delete the old options if they exist.
95        if ( get_option( 'jetpack_social_settings' ) ) {
96            delete_option( 'jetpack_social_settings' );
97        }
98        if ( get_option( 'jetpack_social_autoconvert_images' ) ) {
99            delete_option( 'jetpack_social_autoconvert_images' );
100        }
101
102        $sig_settings = get_option( 'jetpack_social_image_generator_settings' );
103        // If the option is not set, we don't need to migrate.
104        if ( false === $sig_settings ) {
105            return;
106        }
107
108        $enabled  = false;
109        $template = Templates::DEFAULT_TEMPLATE;
110
111        if ( isset( $sig_settings['defaults']['template'] ) ) {
112            $template = $sig_settings['defaults']['template'];
113        }
114
115        if ( isset( $sig_settings['enabled'] ) ) {
116            $enabled = $sig_settings['enabled'];
117        }
118
119        if ( ! isset( $sig_settings['template'] ) ) {
120            update_option(
121                self::OPTION_PREFIX . self::IMAGE_GENERATOR_SETTINGS,
122                array(
123                    'enabled'  => $enabled,
124                    'template' => $template,
125                )
126            );
127        }
128    }
129
130    /**
131     * Register the settings.
132     *
133     * @return void
134     */
135    public function register_settings() {
136
137        register_setting(
138            'jetpack_social',
139            self::OPTION_PREFIX . self::IMAGE_GENERATOR_SETTINGS,
140            array(
141                'type'         => 'object',
142                'default'      => array(
143                    'enabled'  => false,
144                    'template' => Templates::DEFAULT_TEMPLATE,
145                ),
146                'show_in_rest' => array(
147                    'schema' => array(
148                        'type'       => 'object',
149                        'properties' => array(
150                            'enabled'          => array(
151                                'type' => 'boolean',
152                            ),
153                            'template'         => array(
154                                'type' => 'string',
155                            ),
156                            'font'             => array(
157                                'type' => 'string',
158                            ),
159                            'default_image_id' => array(
160                                'type' => 'number',
161                            ),
162                        ),
163                    ),
164                ),
165            )
166        );
167
168        register_setting(
169            'jetpack_social',
170            self::OPTION_PREFIX . self::UTM_SETTINGS,
171            array(
172                'type'         => 'boolean',
173                'default'      => array(
174                    'enabled' => false,
175                ),
176                'show_in_rest' => array(
177                    'schema' => array(
178                        'type'       => 'object',
179                        'properties' => array(
180                            'enabled' => array(
181                                'type' => 'boolean',
182                            ),
183                        ),
184                    ),
185                ),
186            )
187        );
188
189        register_setting(
190            'jetpack_social',
191            self::JETPACK_SOCIAL_SHOW_PRICING_PAGE,
192            array(
193                'type'         => 'boolean',
194                'default'      => true,
195                'show_in_rest' => array(
196                    'schema' => array(
197                        'type' => 'boolean',
198                    ),
199                ),
200            )
201        );
202
203        register_setting(
204            'jetpack_social',
205            self::JETPACK_SOCIAL_NOTE_CPT_ENABLED,
206            array(
207                'type'         => 'boolean',
208                'default'      => false,
209                'show_in_rest' => array(
210                    'schema' => array(
211                        'type' => 'boolean',
212                    ),
213                ),
214            )
215        );
216
217        register_setting(
218            'jetpack_social',
219            self::OPTION_PREFIX . self::NOTES_CONFIG,
220            array(
221                'type'         => 'object',
222                'default'      => self::DEFAULT_NOTES_CONFIG,
223                'show_in_rest' => array(
224                    'schema' => array(
225                        'type'       => 'object',
226                        'context'    => array( 'view', 'edit' ),
227                        'properties' => array(
228                            'append_link' => array(
229                                'type' => 'boolean',
230                            ),
231                            'link_format' => array(
232                                'type' => 'string',
233                                'enum' => array( 'full_url', 'shortlink', 'permashortcitation' ),
234                            ),
235                        ),
236                    ),
237                ),
238            )
239        );
240
241        register_setting(
242            'jetpack_social',
243            self::OPTION_PREFIX . self::MESSAGE_TEMPLATE,
244            array(
245                'type'              => 'string',
246                'default'           => self::DEFAULT_MESSAGE_TEMPLATE,
247                'sanitize_callback' => array( __CLASS__, 'sanitize_message_template' ),
248                'show_in_rest'      => array(
249                    'schema' => array(
250                        'type'    => 'string',
251                        'context' => array( 'view', 'edit' ),
252                    ),
253                ),
254            )
255        );
256
257        add_filter( 'rest_pre_update_setting', array( $this, 'update_settings' ), 10, 3 );
258    }
259
260    /**
261     * Sanitize a user-authored message template string.
262     *
263     * @param mixed $value The raw setting input. Non-string values coerce to ''.
264     * @return string The sanitised template.
265     */
266    public static function sanitize_message_template( $value ) {
267        if ( ! is_string( $value ) ) {
268            return '';
269        }
270
271        $value = sanitize_textarea_field( $value );
272
273        if ( mb_strlen( $value, 'UTF-8' ) > self::MESSAGE_TEMPLATE_MAX_LENGTH ) {
274            $value = mb_substr( $value, 0, self::MESSAGE_TEMPLATE_MAX_LENGTH, 'UTF-8' );
275        }
276
277        return $value;
278    }
279
280    /**
281     * Get the image generator settings.
282     *
283     * @return array
284     */
285    public function get_image_generator_settings() {
286        return get_option( self::OPTION_PREFIX . self::IMAGE_GENERATOR_SETTINGS, self::DEFAULT_IMAGE_GENERATOR_SETTINGS );
287    }
288
289    /**
290     * Get if the UTM params is enabled.
291     *
292     * @return array
293     */
294    public function get_utm_settings() {
295        return get_option( self::OPTION_PREFIX . self::UTM_SETTINGS, self::DEFAULT_UTM_SETTINGS );
296    }
297
298    /**
299     * Get the global message template.
300     *
301     * @return string
302     */
303    public function get_message_template() {
304        return (string) get_option( self::OPTION_PREFIX . self::MESSAGE_TEMPLATE, self::DEFAULT_MESSAGE_TEMPLATE );
305    }
306
307    /**
308     * Get the social notes config.
309     *
310     * @return array The social notes config.
311     */
312    public function get_social_notes_config() {
313        return get_option( self::OPTION_PREFIX . self::NOTES_CONFIG, self::DEFAULT_NOTES_CONFIG );
314    }
315
316    /**
317     * Check if the pricing page should be displayed.
318     *
319     * @return bool
320     */
321    public static function should_show_pricing_page() {
322        return (bool) get_option( self::JETPACK_SOCIAL_SHOW_PRICING_PAGE, true );
323    }
324
325    /**
326     * Get if the social notes feature is enabled.
327     *
328     * @return bool
329     */
330    public function is_social_notes_enabled() {
331        return (bool) get_option( self::JETPACK_SOCIAL_NOTE_CPT_ENABLED, false );
332    }
333
334    /**
335     * Get the current settings.
336     *
337     * @param bool $with_available Whether to include the available status of the features.
338     *
339     * @return array
340     */
341    public function get_settings( $with_available = false ) {
342        $this->migrate_old_option();
343
344        $settings = array(
345            'socialImageGeneratorSettings' => $this->get_image_generator_settings(),
346        );
347
348        // The feature cannot be enabled without Publicize.
349        if ( ! ( new Modules() )->is_active( 'publicize' ) ) {
350            $settings['socialImageGeneratorSettings']['enabled'] = false;
351        }
352
353        if ( $with_available ) {
354            $settings['socialImageGeneratorSettings']['available'] = $this->is_sig_available();
355        }
356
357        return $settings;
358    }
359
360    /**
361     * Get the initial state.
362     * Deprecated method, stub left here to avoid fatal.
363     *
364     * @deprecated 0.62.0
365     */
366    public function get_initial_state() {
367        return array();
368    }
369
370    /**
371     * Update the settings.
372     *
373     * @param bool   $updated The updated settings.
374     * @param string $name    The name of the setting.
375     * @param mixed  $value   The value of the setting.
376     *
377     * @return bool
378     */
379    public function update_settings( $updated, $name, $value ) {
380
381        // Social Image Generator.
382        if ( self::OPTION_PREFIX . self::IMAGE_GENERATOR_SETTINGS === $name ) {
383            return $this->update_social_image_generator_settings( $value );
384        }
385
386        // UTM Settings.
387        if ( self::OPTION_PREFIX . self::UTM_SETTINGS === $name ) {
388            $current_utm_settings = $this->get_utm_settings();
389
390            if ( empty( $current_utm_settings ) || ! is_array( $current_utm_settings ) ) {
391                $current_utm_settings = self::DEFAULT_UTM_SETTINGS;
392            }
393
394            return update_option( self::OPTION_PREFIX . self::UTM_SETTINGS, array_replace_recursive( $current_utm_settings, $value ) );
395        }
396
397        // Social Notes.
398        if ( self::JETPACK_SOCIAL_NOTE_CPT_ENABLED === $name ) {
399            // Delete this option, so the rules get flushed in maybe_flush_rewrite_rules when the CPT is registered.
400            delete_option( self::NOTES_FLUSH_REWRITE_RULES_FLUSHED );
401            return update_option( self::JETPACK_SOCIAL_NOTE_CPT_ENABLED, (bool) $value );
402        }
403        if ( self::OPTION_PREFIX . self::NOTES_CONFIG === $name ) {
404            $old_config = $this->get_social_notes_config();
405            $new_config = array_merge( $old_config, $value );
406            return update_option( self::OPTION_PREFIX . self::NOTES_CONFIG, $new_config );
407        }
408
409        if ( self::JETPACK_SOCIAL_SHOW_PRICING_PAGE === $name ) {
410            return update_option( self::JETPACK_SOCIAL_SHOW_PRICING_PAGE, (int) $value );
411        }
412
413        return $updated;
414    }
415
416    /**
417     * Update the social image generator settings.
418     *
419     * @param array $new_setting The new settings.
420     *
421     * @return bool
422     */
423    public function update_social_image_generator_settings( $new_setting ) {
424        $this->migrate_old_option();
425        $sig_settings = get_option( self::OPTION_PREFIX . self::IMAGE_GENERATOR_SETTINGS );
426
427        if ( empty( $sig_settings ) || ! is_array( $sig_settings ) ) {
428            $sig_settings = self::DEFAULT_IMAGE_GENERATOR_SETTINGS;
429        }
430
431        return update_option( self::OPTION_PREFIX . self::IMAGE_GENERATOR_SETTINGS, array_replace_recursive( $sig_settings, $new_setting ) );
432    }
433
434    /**
435     * Check if SIG is available.
436     *
437     * @return bool True if SIG is available, false otherwise.
438     */
439    public function is_sig_available() {
440        global $publicize;
441
442        if ( ! $publicize ) {
443            return false;
444        }
445
446        return $publicize->has_social_image_generator_feature();
447    }
448
449    /**
450     * Get the default template.
451     *
452     * @return string
453     */
454    public function sig_get_default_template() {
455        $this->migrate_old_option();
456        $sig_settings = get_option( self::OPTION_PREFIX . self::IMAGE_GENERATOR_SETTINGS );
457        if ( empty( $sig_settings ) || ! is_array( $sig_settings ) ) {
458            $sig_settings = self::DEFAULT_IMAGE_GENERATOR_SETTINGS;
459        }
460        return $sig_settings['template'];
461    }
462
463    /**
464     * Get the default image ID.
465     *
466     * @return int
467     */
468    public function sig_get_default_image_id() {
469        $this->migrate_old_option();
470        $sig_settings = get_option( self::OPTION_PREFIX . self::IMAGE_GENERATOR_SETTINGS );
471        if ( empty( $sig_settings ) || ! is_array( $sig_settings ) ) {
472            return 0;
473        }
474
475        if ( isset( $sig_settings['default_image_id'] ) ) {
476            return $sig_settings['default_image_id'];
477        }
478
479        return 0;
480    }
481
482    /**
483     * Get the default font.
484     *
485     * @return string
486     */
487    public function sig_get_default_font() {
488        $this->migrate_old_option();
489        $sig_settings = get_option( self::OPTION_PREFIX . self::IMAGE_GENERATOR_SETTINGS );
490        if ( empty( $sig_settings ) || ! is_array( $sig_settings ) ) {
491            return '';
492        }
493
494        return $sig_settings['font'] ?? '';
495    }
496}