Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 211
0.00% covered (danger)
0.00%
0 / 16
CRAP
0.00% covered (danger)
0.00%
0 / 1
jetpack_gallery_widget_init
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
42
Jetpack_Gallery_Widget
0.00% covered (danger)
0.00%
0 / 202
0.00% covered (danger)
0.00%
0 / 15
2352
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
30
 widget
0.00% covered (danger)
0.00%
0 / 33
0.00% covered (danger)
0.00%
0 / 1
132
 get_attachments
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
12
 rectangular_widget
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 square_widget
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 circle_widget
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 slideshow_widget
0.00% covered (danger)
0.00%
0 / 35
0.00% covered (danger)
0.00%
0 / 1
30
 tiled_gallery_content_width
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 form
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 update
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 sanitize
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
42
 allowed_values
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
2
 defaults
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 enqueue_frontend_scripts
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
2
 enqueue_admin_scripts
0.00% covered (danger)
0.00%
0 / 28
0.00% covered (danger)
0.00%
0 / 1
12
1<?php // phpcs:ignore eWordPress.Files.FileName.InvalidClassFileName
2/**
3 * Module Name: Gallery widget
4 *
5 * @package automattic/jetpack
6 */
7
8// phpcs:disable Universal.Files.SeparateFunctionsFromOO.Mixed -- TODO: Move classes to appropriately-named class files.
9
10use Automattic\Jetpack\Assets;
11use Automattic\Jetpack\Image_CDN\Image_CDN_Core;
12
13if ( ! defined( 'ABSPATH' ) ) {
14    exit( 0 );
15}
16
17/**
18 * Jetpack_Gallery_Widget main class.
19 */
20class Jetpack_Gallery_Widget extends WP_Widget {
21    const THUMB_SIZE    = 45;
22    const DEFAULT_WIDTH = 265;
23
24    /**
25     * The width of the gallery widget.
26     * May be customized by the 'gallery_widget_content_width' filter.
27     *
28     * @var int
29     */
30    protected $instance_width;
31
32    /**
33     * Jetpack_Gallery_Widget constructor.
34     */
35    public function __construct() {
36        $widget_ops = array(
37            'classname'                   => 'widget-gallery',
38            'description'                 => __( 'Display a photo gallery or slideshow', 'jetpack' ),
39            'customize_selective_refresh' => true,
40        );
41
42        add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_scripts' ) );
43
44        parent::__construct(
45            'gallery',
46            /** This filter is documented in modules/widgets/facebook-likebox.php */
47            apply_filters( 'jetpack_widget_name', __( 'Gallery', 'jetpack' ) ),
48            $widget_ops
49        );
50
51        if ( is_customize_preview() ) {
52            add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_frontend_scripts' ) );
53
54            if ( class_exists( 'Jetpack_Tiled_Gallery' ) ) {
55                add_action( 'wp_enqueue_scripts', array( 'Jetpack_Tiled_Gallery', 'default_scripts_and_styles' ) );
56            }
57
58            if ( class_exists( 'Jetpack_Slideshow_Shortcode' ) ) {
59                $slideshow = new Jetpack_Slideshow_Shortcode();
60                add_action( 'wp_enqueue_scripts', array( $slideshow, 'enqueue_scripts' ) );
61            }
62
63            if ( class_exists( 'Jetpack_Carousel' ) ) {
64                $carousel = new Jetpack_Carousel();
65                add_action( 'wp_enqueue_scripts', array( $carousel, 'enqueue_assets' ) );
66            }
67        }
68    }
69
70    /**
71     * Display the widget.
72     *
73     * @param array $args Display arguments including before_title, after_title, before_widget, and after_widget.
74     * @param array $instance The settings for the particular instance of the widget.
75     */
76    public function widget( $args, $instance ) {
77        $instance = wp_parse_args( (array) $instance, $this->defaults() );
78
79        $this->enqueue_frontend_scripts();
80
81        $before_widget = isset( $args['before_widget'] ) ? $args['before_widget'] : '';
82        $before_title  = isset( $args['before_title'] ) ? $args['before_title'] : '';
83        $after_title   = isset( $args['after_title'] ) ? $args['after_title'] : '';
84        $after_widget  = isset( $args['after_widget'] ) ? $args['after_widget'] : '';
85
86        $instance['attachments'] = $this->get_attachments( $instance );
87
88        $classes = array();
89
90        $classes[] = 'widget-gallery-' . $instance['type'];
91
92        /*
93         * Due to a bug in the carousel plugin,
94         * carousels will be triggered for all tiled galleries that exist on a page with other tiled galleries,
95         * regardless of whether or not the widget was set to Carousel mode.
96         * The onClick selector is simply too broad, since it was not written with widgets in mind.
97         * This special class prevents that behavior, via an override handler in gallery.js.
98         */
99        if ( 'carousel' !== $instance['link'] && 'slideshow' !== $instance['type'] ) {
100            $classes[] = 'no-carousel';
101        } else {
102            $classes[] = 'carousel';
103        }
104
105        $classes = implode( ' ', $classes );
106
107        if ( 'carousel' === $instance['link'] ) {
108            require_once plugin_dir_path( realpath( __DIR__ . '/../carousel/jetpack-carousel.php' ) ) . 'jetpack-carousel.php';
109
110            if ( class_exists( 'Jetpack_Carousel' ) ) {
111                // Create new carousel so we can use the enqueue_assets() method. Not ideal, but there is a decent amount
112                // of logic in that method that shouldn't be duplicated.
113                $carousel = new Jetpack_Carousel();
114
115                $carousel->enqueue_assets();
116            }
117        }
118
119        echo $before_widget . "\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
120
121        /** This filter is documented in core/src/wp-includes/default-widgets.php */
122        $title = apply_filters( 'widget_title', $instance['title'] );
123
124        if ( $title ) {
125            echo $before_title . $title . $after_title . "\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
126        }
127
128        echo '<div class="' . esc_attr( $classes ) . '">' . "\n";
129
130        $method = $instance['type'] . '_widget';
131
132        /**
133         * Allow the width of a gallery to be altered by themes or other code.
134         *
135         * @module widgets
136         *
137         * @since 2.5.0
138         *
139         * @param int self::DEFAULT_WIDTH Default gallery width. Default is 265.
140         * @param string $args Display arguments including before_title, after_title, before_widget, and after_widget.
141         * @param array $instance The settings for the particular instance of the widget.
142         */
143        $this->instance_width = apply_filters( 'gallery_widget_content_width', self::DEFAULT_WIDTH, $args, $instance );
144
145        // Register a filter to modify the tiled_gallery_content_width, so Jetpack_Tiled_Gallery
146        // can appropriately size the tiles.
147        add_filter( 'tiled_gallery_content_width', array( $this, 'tiled_gallery_content_width' ) );
148
149        if ( method_exists( $this, $method ) ) {
150            echo $this->$method( $args, $instance ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
151        }
152
153        // Remove the stored $instance_width, as it is no longer needed.
154        $this->instance_width = null;
155
156        // Remove the filter, so any Jetpack_Tiled_Gallery in a post is not affected.
157        remove_filter( 'tiled_gallery_content_width', array( $this, 'tiled_gallery_content_width' ) );
158
159        echo "\n" . '</div>'; // .widget-gallery-$type
160
161        echo "\n" . $after_widget; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
162
163        /** This action is documented in modules/widgets/gravatar-profile.php */
164        do_action( 'jetpack_stats_extra', 'widget_view', 'gallery' );
165    }
166
167    /**
168     * Fetch the images attached to the gallery Widget
169     *
170     * @param array $instance The Widget instance for which you'd like attachments.
171     * @return array Array of attachment objects for the Widget in $instance
172     */
173    public function get_attachments( $instance ) {
174        $ids = explode( ',', $instance['ids'] );
175
176        if ( isset( $instance['random'] ) && 'on' === $instance['random'] ) {
177            shuffle( $ids );
178        }
179
180        $attachments_query = new WP_Query(
181            array(
182                'post__in'       => $ids,
183                'post_status'    => 'inherit',
184                'post_type'      => 'attachment',
185                'post_mime_type' => 'image',
186                'posts_per_page' => -1,
187                'orderby'        => 'post__in',
188            )
189        );
190
191        $attachments = $attachments_query->get_posts();
192
193        wp_reset_postdata();
194
195        return $attachments;
196    }
197
198    /**
199     * Generate HTML for a rectangular, tiled Widget
200     *
201     * @param array $args Display arguments including before_title, after_title, before_widget, and after_widget.
202     * @param array $instance The Widget instance to generate HTML for.
203     * @return string String of HTML representing a rectangular gallery
204     */
205    public function rectangular_widget( $args, $instance ) {
206        if ( ! class_exists( 'Jetpack_Tiled_Gallery' )
207            && ! class_exists( 'Jetpack_Tiled_Gallery_Layout_Rectangular' ) ) {
208            return;
209        }
210
211        Jetpack_Tiled_Gallery::default_scripts_and_styles();
212
213        $layout = new Jetpack_Tiled_Gallery_Layout_Rectangular( $instance['attachments'], $instance['link'], false, 3 );
214        return $layout->HTML();
215    }
216
217    /**
218     * Generate HTML for a square (grid style) Widget
219     *
220     * @param array $args Display arguments including before_title, after_title, before_widget, and after_widget.
221     * @param array $instance The Widget instance to generate HTML for.
222     * @return string String of HTML representing a square gallery
223     */
224    public function square_widget( $args, $instance ) {
225        if ( ! class_exists( 'Jetpack_Tiled_Gallery' )
226            && ! class_exists( 'Jetpack_Tiled_Gallery_Layout_Square' ) ) {
227            return;
228        }
229
230        Jetpack_Tiled_Gallery::default_scripts_and_styles();
231
232        $layout = new Jetpack_Tiled_Gallery_Layout_Square( $instance['attachments'], $instance['link'], false, 3 );
233        return $layout->HTML();
234    }
235
236    /**
237     * Generate HTML for a circular (grid style) Widget
238     *
239     * @param array $args Display arguments including before_title, after_title, before_widget, and after_widget.
240     * @param array $instance The Widget instance to generate HTML for.
241     * @return string String of HTML representing a circular gallery
242     */
243    public function circle_widget( $args, $instance ) {
244        if ( ! class_exists( 'Jetpack_Tiled_Gallery' )
245            && ! class_exists( 'Jetpack_Tiled_Gallery_Layout_Circle' ) ) {
246            return;
247        }
248
249        Jetpack_Tiled_Gallery::default_scripts_and_styles();
250
251        $layout = new Jetpack_Tiled_Gallery_Layout_Circle( $instance['attachments'], $instance['link'], false, 3 );
252        return $layout->HTML();
253    }
254
255    /**
256     * Generate HTML for a slideshow Widget
257     *
258     * @todo Is slideshow_widget() still used?
259     *
260     * @param array $args Display arguments including before_title, after_title, before_widget, and after_widget.
261     * @param array $instance The Widget instance to generate HTML for.
262     * @return string String of HTML representing a slideshow gallery
263     */
264    public function slideshow_widget( $args, $instance ) {
265        global $content_width;
266
267        require_once plugin_dir_path( realpath( __DIR__ . '/../shortcodes/slideshow.php' ) ) . 'slideshow.php';
268
269        if ( ! class_exists( 'Jetpack_Slideshow_Shortcode' ) ) {
270            return;
271        }
272
273        if ( count( $instance['attachments'] ) < 1 ) {
274            return;
275        }
276
277        $slideshow = new Jetpack_Slideshow_Shortcode();
278
279        $slideshow->enqueue_scripts();
280
281        $gallery_instance = 'widget-' . $args['widget_id'];
282
283        $gallery = array();
284
285        foreach ( $instance['attachments'] as $attachment ) {
286            $attachment_image_src = wp_get_attachment_image_src( $attachment->ID, 'full' );
287            $attachment_image_src = Image_CDN_Core::cdn_url( $attachment_image_src[0], array( 'w' => $this->instance_width ) ); /** [url, width, height] */
288
289            $caption = wptexturize( wp_strip_all_tags( $attachment->post_excerpt ) );
290
291            $gallery[] = (object) array(
292                'src'     => (string) esc_url_raw( $attachment_image_src ),
293                'id'      => (string) $attachment->ID,
294                'caption' => (string) $caption,
295            );
296        }
297
298        $max_width  = (int) get_option( 'large_size_w' );
299        $max_height = 175;
300
301        if ( (int) $content_width > 0 ) {
302            $max_width = min( (int) $content_width, $max_width );
303        }
304
305        $color   = Jetpack_Options::get_option( 'slideshow_background_color', 'black' );
306        $js_attr = array(
307            'gallery'   => $gallery,
308            'selector'  => $gallery_instance,
309            'width'     => $max_width,
310            'height'    => $max_height,
311            'trans'     => 'fade',
312            'color'     => $color,
313            'autostart' => true,
314        );
315
316        $html = $slideshow->slideshow_js( $js_attr );
317
318        return $html;
319    }
320
321    /**
322     * Used to adjust the content width of Jetpack_Tiled_Gallery's in sidebars
323     *
324     * $this->instance_width is filtered in widget() and this filter is added then removed in widget()
325     *
326     * @return int The filtered width
327     */
328    public function tiled_gallery_content_width() {
329        return $this->instance_width;
330    }
331
332    /**
333     * Outputs the widget settings form.
334     *
335     * @html-template-var array $instance
336     * @html-template-var array<string,array<string|int,string|int>> $allowed_values
337     *
338     * @param array $instance Current settings.
339     * @return string|void
340     */
341    public function form( $instance ) {
342        $defaults       = $this->defaults();
343        $allowed_values = $this->allowed_values(); // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Used in included form template.
344
345        $instance = wp_parse_args( (array) $instance, $defaults );
346
347        include __DIR__ . '/gallery/templates/form.php';
348    }
349
350    /**
351     * Save the widget options.
352     *
353     * @param array $new_instance The new instance options.
354     * @param array $old_instance The old instance options.
355     * @return array The saved options.
356     */
357    public function update( $new_instance, $old_instance ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
358        $instance = $this->sanitize( $new_instance );
359
360        return $instance;
361    }
362
363    /**
364     * Sanitize the $instance's values to the set of allowed values. If a value is not acceptable,
365     * it is set to its default.
366     *
367     * Helps keep things nice and secure by listing only allowed values.
368     *
369     * @param array $instance The Widget instance to sanitize values for.
370     * @return array $instance The Widget instance with values sanitized
371     */
372    public function sanitize( $instance ) {
373        $allowed_values = $this->allowed_values();
374        $defaults       = $this->defaults();
375
376        foreach ( $instance as $key => $value ) {
377            if ( ! is_scalar( $value ) ) {
378                // $instance may hold an Array value type for the Jetpack widget visibility feature.
379                continue;
380            }
381
382            $value = trim( $value );
383
384            if ( isset( $allowed_values[ $key ] ) && $allowed_values[ $key ] && ! array_key_exists( $value, $allowed_values[ $key ] ) ) {
385                $instance[ $key ] = $defaults[ $key ];
386            } else {
387                $instance[ $key ] = sanitize_text_field( $value );
388            }
389        }
390
391        return $instance;
392    }
393
394    /**
395     * Return a multi-dimensional array of allowed values (and their labels) for all widget form
396     * elements
397     *
398     * To allow all values on an input, omit it from the returned array
399     *
400     * @return array Array of allowed values for each option
401     */
402    public function allowed_values() {
403        $max_columns = 5;
404
405        // Create an associative array of allowed column values. This just automates the generation of
406        // column <option>s, from 1 to $max_columns.
407        $allowed_columns = array_combine( range( 1, $max_columns ), range( 1, $max_columns ) );
408
409        return array(
410            'type'    => array(
411                'rectangular' => __( 'Tiles', 'jetpack' ),
412                'square'      => __( 'Square Tiles', 'jetpack' ),
413                'circle'      => __( 'Circles', 'jetpack' ),
414                'slideshow'   => __( 'Slideshow', 'jetpack' ),
415            ),
416            'columns' => $allowed_columns,
417            'link'    => array(
418                'carousel' => __( 'Carousel', 'jetpack' ),
419                'post'     => __( 'Attachment Page', 'jetpack' ),
420                'file'     => __( 'Media File', 'jetpack' ),
421            ),
422        );
423    }
424
425    /**
426     * Return an associative array of default values
427     *
428     * These values are used in new widgets as well as when sanitizing input. If a given value is not allowed,
429     * as defined in allowed_values(), that input is set to the default value defined here.
430     *
431     * @return array Array of default values for the Widget's options
432     */
433    public function defaults() {
434        return array(
435            'title'   => '',
436            'type'    => 'rectangular',
437            'ids'     => '',
438            'columns' => 3,
439            'link'    => 'carousel',
440        );
441    }
442
443    /**
444     * Enqueue frontend scripts.
445     */
446    public function enqueue_frontend_scripts() {
447        wp_register_script(
448            'gallery-widget',
449            Assets::get_file_url_for_environment(
450                '_inc/build/widgets/gallery/js/gallery.min.js',
451                'modules/widgets/gallery/js/gallery.js'
452            ),
453            array( 'jquery' ),
454            JETPACK__VERSION,
455            false
456        );
457
458        wp_enqueue_script( 'gallery-widget' );
459    }
460
461    /**
462     * Enqueue admin scripts and styles.
463     */
464    public function enqueue_admin_scripts() {
465        global $pagenow;
466
467        if ( 'widgets.php' === $pagenow || 'customize.php' === $pagenow ) {
468            wp_enqueue_media();
469
470            wp_enqueue_script(
471                'gallery-widget-admin',
472                Assets::get_file_url_for_environment(
473                    '_inc/build/widgets/gallery/js/admin.min.js',
474                    'modules/widgets/gallery/js/admin.js'
475                ),
476                array(
477                    'jquery',
478                    'media-models',
479                    'media-views',
480                ),
481                '20150501',
482                false
483            );
484
485            $js_settings = array(
486                'thumbSize' => self::THUMB_SIZE,
487            );
488
489            wp_localize_script( 'gallery-widget-admin', '_wpGalleryWidgetAdminSettings', $js_settings );
490            wp_enqueue_style(
491                'gallery-widget-admin',
492                plugins_url( '/gallery/css/admin.css', __FILE__ ),
493                array(),
494                JETPACK__VERSION
495            );
496            wp_style_add_data( 'gallery-widget-admin', 'rtl', 'replace' );
497        }
498    }
499}
500
501add_action( 'widgets_init', 'jetpack_gallery_widget_init' );
502
503/**
504 * Jetpack Gallery widget init; the widget is conditionally registered.
505 */
506function jetpack_gallery_widget_init() {
507    /**
508     * Allow the Gallery Widget to be enabled even when Core supports the Media Gallery Widget
509     *
510     * @module widgets
511     *
512     * @since 5.5.0
513     *
514     * @param bool false Whether to force-enable the gallery widget
515     */
516    if (
517        ! apply_filters( 'jetpack_force_enable_gallery_widget', false )
518        && class_exists( 'WP_Widget_Media_Gallery' )
519        && Jetpack_Options::get_option( 'gallery_widget_migration' )
520    ) {
521        return;
522    }
523    if ( ! method_exists( 'Jetpack', 'is_module_active' ) || Jetpack::is_module_active( 'tiled-gallery' ) ) {
524        register_widget( 'Jetpack_Gallery_Widget' );
525    }
526}