Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 122
0.00% covered (danger)
0.00%
0 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
wpcom_widget_category_cloud
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
WPCOM_Category_Cloud_Widget
0.00% covered (danger)
0.00%
0 / 120
0.00% covered (danger)
0.00%
0 / 6
306
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
2
 flush_cache
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 widget
0.00% covered (danger)
0.00%
0 / 69
0.00% covered (danger)
0.00%
0 / 1
110
 form
0.00% covered (danger)
0.00%
0 / 27
0.00% covered (danger)
0.00%
0 / 1
2
 update
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 normalize_int_value
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
1<?php // phpcs:ignore Squiz.Commenting.FileComment.Missing
2
3/**
4 * Category Cloud widget from WordPress.com
5 */
6class WPCOM_Category_Cloud_Widget extends WP_Widget {
7    /**
8     * Minimum font percentage.
9     *
10     * @var int $min_font_per
11     */
12    private $min_font_per = 100;
13
14    /**
15     * Maximum font percentage.
16     *
17     * @var int $max_font_per
18     */
19    private $max_font_per = 275;
20
21    /**
22     * Constructor.
23     */
24    public function __construct() {
25        parent::__construct(
26            'wpcom_category_cloud',
27            __( 'Category Cloud', 'wpcomsh' ),
28            array(
29                'description' => __( 'Your most used categories in cloud format.', 'wpcomsh' ),
30                'classname'   => 'widget_tag_cloud',
31            )
32        );
33
34        add_action( 'delete_category', array( $this, 'flush_cache' ) );
35        add_action( 'edit_category', array( $this, 'flush_cache' ) );
36        add_action( 'create_category', array( $this, 'flush_cache' ) );
37    }
38
39    /**
40     * Flush cache.
41     */
42    public function flush_cache() {
43        wp_cache_delete( 'widget_cat_cloud_cache' . $this->id, 'widget' );
44    }
45
46    /**
47     * Display the widget.
48     *
49     * @param array $args     Widget arguments.
50     * @param array $instance Widget instance.
51     */
52    public function widget( $args, $instance ) {
53        $instance = wp_parse_args(
54            $instance,
55            array(
56                'parent_pad'   => 0,
57                'max_tags'     => 30,
58                'exclude'      => '',
59                'min_font_per' => $this->min_font_per,
60                'max_font_per' => $this->max_font_per,
61            )
62        );
63
64        if ( empty( $instance['title'] ) ) {
65            $instance['title'] = __( 'Category Cloud', 'wpcomsh' );
66        }
67
68        $tags_info = wp_cache_get( 'widget_cat_cloud_cache' . $this->id, 'widget' );
69
70        if ( ! $tags_info ) {
71            $categories = get_categories(
72                array(
73                    'orderby'      => 'count',
74                    'order'        => 'DESC',
75                    'hierarchical' => 0,
76                    'pad_counts'   => $instance['parent_pad'],
77                    'number'       => $instance['max_tags'],
78                    'exclude'      => $instance['exclude'],
79                )
80            );
81
82            $tags     = array();
83            $tag_urls = array();
84
85            foreach ( $categories as $cat ) {
86                $tags[ $cat->name ]     = $cat->count;
87                $tag_urls[ $cat->name ] = get_category_link( $cat->term_id );
88            }
89
90            uksort( $tags, 'strnatcasecmp' ); // case insensitive alphabetical sort
91
92            // Cache only if we're not in the customizer view. That view can add some garbage to the urls, which we then cache and display to other users.
93            if ( ! $this->is_preview() ) {
94                wp_cache_add(
95                    'widget_cat_cloud_cache' . $this->id,
96                    array(
97                        'tags'     => $tags,
98                        'tag_urls' => $tag_urls,
99                    ),
100                    'widget'
101                );
102            }
103        } else {
104            $tags     = $tags_info['tags'];
105            $tag_urls = $tags_info['tag_urls'];
106        }
107
108        if ( empty( $tags ) ) {
109            return;
110        }
111
112        $min_value = min( array_values( $tags ) );
113        $max_value = max( array_values( $tags ) );
114
115        $spread = $max_value - $min_value;
116
117        if ( $spread < 1 ) {
118            $spread = 1;
119        }
120
121        $step = ( $instance['max_font_per'] - $instance['min_font_per'] ) / $spread;
122
123        echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
124        echo $args['before_title'] . esc_html( $instance['title'] ) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
125
126        echo '<div style="overflow: hidden;">';
127
128        foreach ( $tags as $tag_name => $num_posts ) {
129            $font_size = $instance['min_font_per'] + ( ( $num_posts - $min_value ) * $step );
130            echo '<a href="' . esc_url( $tag_urls[ $tag_name ] ) . '" style="font-size: ' . esc_attr( $font_size . '%' ) . '; padding: 1px; margin: 1px;" title="' . esc_attr( $tag_name . ' (' . $num_posts . ')' ) . '">' . esc_html( $tag_name ) . '</a> ';
131        }
132
133        echo '</div>';
134
135        if ( 1 >= count( $tags ) && current_user_can( 'edit_theme_options' ) ) {
136            /* translators: Link to post category documentation. */
137            echo '<p>';
138            printf(
139                wp_kses(
140                    // translators: link to support doc about categories
141                    __( 'If you use more <a href="%s">categories</a> on your site, they will appear here.', 'wpcomsh' ),
142                    array(
143                        'a' => array( 'href' => array() ),
144                    )
145                ),
146                'http://en.support.wordpress.com/posts/categories/'
147            );
148            echo '</p>';
149        }
150
151        echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
152    }
153
154    /**
155     * Display the widget settings form.
156     *
157     * @param array $instance Current settings.
158     * @return never
159     */
160    public function form( $instance ) {
161        $instance = wp_parse_args(
162            $instance,
163            array(
164                'title'        => '',
165                'parent_pad'   => false,
166                'max_tags'     => 30,
167                'exclude'      => '',
168                'min_font_per' => $this->min_font_per,
169                'max_font_per' => $this->max_font_per,
170            )
171        );
172
173        ?>
174        <p>
175            <label>
176                <?php esc_html_e( 'Title:', 'wpcomsh' ); ?>
177                <input class="widefat" name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" type="text" value="<?php echo esc_attr( $instance['title'] ); ?>"/>
178            </label>
179        </p>
180        <p>
181            <label>
182                <?php esc_html_e( 'Maximum number of categories to show:', 'wpcomsh' ); ?>
183                <input class="widefat" name="<?php echo esc_attr( $this->get_field_name( 'max_tags' ) ); ?>" type="number" value="<?php echo esc_attr( $instance['max_tags'] ); ?>"/>
184            </label>
185        </p>
186        <p>
187            <label>
188                <?php esc_html_e( 'Exclude:', 'wpcomsh' ); ?>
189                <input class="widefat" name="<?php echo esc_attr( $this->get_field_name( 'exclude' ) ); ?>" type="text" value="<?php echo esc_attr( $instance['exclude'] ); ?>"/>
190                <small><?php esc_html_e( 'Category IDs, separated by commas', 'wpcomsh' ); ?></small>
191            </label>
192        </p>
193        <p>
194            <label>
195                <?php esc_html_e( 'Minimum font percentage:', 'wpcomsh' ); ?>
196                <input class="widefat" name="<?php echo esc_attr( $this->get_field_name( 'min_font_per' ) ); ?>" type="number" value="<?php echo esc_attr( $instance['min_font_per'] ); ?>" min="10" max="1000" maxlength="4"/>
197            </label>
198        </p>
199        <p>
200            <label>
201                <?php esc_html_e( 'Maximum font percentage:', 'wpcomsh' ); ?>
202                <input class="widefat" name="<?php echo esc_attr( $this->get_field_name( 'max_font_per' ) ); ?>" type="number" value="<?php echo esc_attr( $instance['max_font_per'] ); ?>" min="10" max="9999" maxlength="4"/>
203            </label>
204        </p>
205        <p>
206            <label>
207                <input type="checkbox" name="<?php echo esc_attr( $this->get_field_name( 'parent_pad' ) ); ?><?php checked( $instance['parent_pad'] ); ?> value="1"/>
208                <?php
209                esc_html_e( 'Count items in sub-categories toward parent total.', 'wpcomsh' );
210                ?>
211                (<a href="https://en.support.wordpress.com/widgets/category-cloud-widget/#settings" target="_blank" title="<?php esc_attr_e( 'Click for more information', 'wpcomsh' ); ?>">?</a>)
212            </label>
213        </p>
214        <?php
215    }
216
217    /**
218     * Update the widget settings.
219     *
220     * @param array $new_instance New settings.
221     * @param array $old_instance Old settings.
222     */
223    public function update( $new_instance, $old_instance ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
224        $new_instance['title']        = wp_strip_all_tags( $new_instance['title'] );
225        $new_instance['parent_pad']   = isset( $new_instance['parent_pad'] );
226        $new_instance['max_tags']     = (int) $new_instance['max_tags'];
227        $new_instance['exclude']      = wp_strip_all_tags( $new_instance['exclude'] );
228        $new_instance['min_font_per'] = $this->normalize_int_value( (int) $new_instance['min_font_per'], $this->min_font_per, 1000, 10 );
229        $new_instance['max_font_per'] = $this->normalize_int_value( (int) $new_instance['max_font_per'], $this->max_font_per, 9999, 10 );
230
231        $this->flush_cache();
232
233        return $new_instance;
234    }
235
236    /**
237     * Normalize int value.
238     *
239     * @param int $value Provided value.
240     * @param int $default Default value used if provided value is not between $min and $max.
241     * @param int $max Maximum value.
242     * @param int $min Minimum value.
243     *
244     * @return int Normalized value.
245     */
246    private function normalize_int_value( $value, $default = 0, $max = 0, $min = 0 ) {
247        $value = (int) $value;
248
249        if ( $value > $max || $value < $min ) {
250            $value = $default;
251        }
252
253        return (int) $value;
254    }
255}
256
257/**
258 * Register the widget.
259 */
260function wpcom_widget_category_cloud() { // phpcs:ignore Universal.Files.SeparateFunctionsFromOO.Mixed
261    register_widget( 'WPCOM_Category_Cloud_Widget' );
262}
263add_action( 'widgets_init', 'wpcom_widget_category_cloud', 11 );