Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
97.22% covered (success)
97.22%
245 / 252
75.00% covered (warning)
75.00%
6 / 8
CRAP
n/a
0 / 0
Automattic\Jetpack\PremiumAnalytics\is_woocommerce_dashboard_section_available
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
Automattic\Jetpack\PremiumAnalytics\is_woocommerce_dashboard_section_available_to_current_user
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
Automattic\Jetpack\PremiumAnalytics\is_store_dashboard_section_available
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
20
Automattic\Jetpack\PremiumAnalytics\is_subscribers_dashboard_section_available
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
Automattic\Jetpack\PremiumAnalytics\get_traffic_section_default_layout
100.00% covered (success)
100.00%
82 / 82
100.00% covered (success)
100.00%
1 / 1
1
Automattic\Jetpack\PremiumAnalytics\get_insights_section_default_layout
100.00% covered (success)
100.00%
86 / 86
100.00% covered (success)
100.00%
1 / 1
1
Automattic\Jetpack\PremiumAnalytics\get_subscribers_section_default_layout
100.00% covered (success)
100.00%
33 / 33
100.00% covered (success)
100.00%
1 / 1
1
Automattic\Jetpack\PremiumAnalytics\register_default_dashboard_sections
100.00% covered (success)
100.00%
38 / 38
100.00% covered (success)
100.00%
1 / 1
4
1<?php
2/**
3 * The package's own dashboard sections: the built-in tabs, their availability gates and default
4 * layouts, and the filters over those gates. They register through the section API in
5 * dashboard-sections.php, the same way a plugin extending the dashboard does; the Ads tab and the
6 * WooCommerce tab are such sections, registered by their own packages.
7 *
8 * @package automattic/jetpack-premium-analytics
9 */
10
11namespace Automattic\Jetpack\PremiumAnalytics;
12
13use Automattic\Jetpack\Modules;
14
15// Guarded on a symbol the file declares, so a second copy of the package can't
16// redeclare it. See the include block in Analytics::load_dashboard_components().
17if ( ! function_exists( __NAMESPACE__ . '\\register_dashboard_section' ) ) {
18    require_once __DIR__ . '/dashboard-sections.php';
19}
20
21/**
22 * Filter through which WooCommerce section availability is resolved.
23 */
24const WOOCOMMERCE_DASHBOARD_SECTION_AVAILABLE_FILTER = 'jetpack_premium_analytics_woocommerce_dashboard_section_available';
25
26/**
27 * Filter through which Subscribers section availability is resolved.
28 */
29const SUBSCRIBERS_DASHBOARD_SECTION_AVAILABLE_FILTER = 'jetpack_premium_analytics_subscribers_dashboard_section_available';
30
31/**
32 * Whether the WooCommerce dashboard section should be exposed.
33 *
34 * @return bool True when WooCommerce is active.
35 */
36function is_woocommerce_dashboard_section_available() {
37    $is_available = class_exists( 'WooCommerce' ) || function_exists( 'WC' );
38
39    /**
40     * Filters whether the WooCommerce dashboard section is available.
41     *
42     * @param bool $is_available Whether WooCommerce was detected in the current request.
43     */
44    return (bool) apply_filters( WOOCOMMERCE_DASHBOARD_SECTION_AVAILABLE_FILTER, $is_available );
45}
46
47/**
48 * Whether the current user should be shown the WooCommerce dashboard section.
49 *
50 * The sibling is_woocommerce_dashboard_section_available() answers "is
51 * WooCommerce here"; this adds "and may this reader see store data".
52 *
53 * @since 0.1.0
54 *
55 * @return bool
56 */
57function is_woocommerce_dashboard_section_available_to_current_user() {
58    return is_woocommerce_dashboard_section_available() && Capabilities::current_user_can_view_store_reports();
59}
60
61/**
62 * Whether the Store dashboard section should be exposed.
63 *
64 * The site's own opt-in needs the Store flag; the blog sticker and the
65 * `jetpack_premium_analytics_enabled` filter leave the option off and keep every section.
66 *
67 * @since 0.10.0
68 *
69 * @return bool
70 */
71function is_store_dashboard_section_available() {
72    // An older copy of the package may have loaded dashboard-policy.php without the flag.
73    $is_enabled = ! get_option( Enablement_Setting::ENABLED_OPTION )
74        || ( function_exists( __NAMESPACE__ . '\\is_dashboard_store_section_enabled' ) && is_dashboard_store_section_enabled() );
75
76    return $is_enabled && is_woocommerce_dashboard_section_available_to_current_user();
77}
78
79/**
80 * Whether the Subscribers dashboard section should be exposed.
81 *
82 * Sites without Jetpack have no module state to check, so the section remains
83 * available. Modules::is_active() also returns true on WPCOM Simple.
84 *
85 * @since 0.3.0
86 *
87 * @return bool True when the subscriptions module is active and the reader may see Stats.
88 */
89function is_subscribers_dashboard_section_available() {
90    // Outside the filter, which answers only whether the module is there.
91    if ( ! Capabilities::current_user_can_view_stats() ) {
92        return false;
93    }
94
95    $is_available = ! class_exists( 'Jetpack' ) || ( new Modules() )->is_active( 'subscriptions' );
96
97    /**
98     * Filters whether the Subscribers dashboard section is available.
99     *
100     * @since 0.3.0
101     *
102     * @param bool $is_available Whether the subscriptions module was detected in the current request.
103     */
104    return (bool) apply_filters( SUBSCRIBERS_DASHBOARD_SECTION_AVAILABLE_FILTER, $is_available );
105}
106
107/**
108 * The Traffic tab's default widget layout.
109 *
110 * @return array Widget instances.
111 */
112function get_traffic_section_default_layout() {
113    return array(
114        // Rows fill the three-column grid in the prototype's order. Plan usage
115        // is intentionally not a default; it stays available from the widget
116        // picker.
117        // Row 1: traffic chart.
118        get_dashboard_default_widget_instance(
119            'default-traffic-chart-widget-instance',
120            'jpa/traffic-chart',
121            0,
122            3,
123            2
124        ),
125        // Row 2: most-viewed posts + referrers + devices.
126        get_dashboard_default_widget_instance(
127            'default-stats-top-posts-widget-instance',
128            'jpa/stats-top-posts',
129            1,
130            1,
131            2
132        ),
133        get_dashboard_default_widget_instance(
134            'default-referrers-widget-instance',
135            'jpa/referrers',
136            2,
137            1,
138            2
139        ),
140        get_dashboard_default_widget_instance(
141            'default-devices-widget-instance',
142            'jpa/devices',
143            3,
144            1,
145            2
146        ),
147        // Row 3: locations map + top platforms.
148        get_dashboard_default_widget_instance(
149            'default-locations-widget-instance',
150            'jpa/locations',
151            4,
152            2,
153            2
154        ),
155        get_dashboard_default_widget_instance(
156            'default-top-platforms-widget-instance',
157            'jpa/top-platforms',
158            5,
159            1,
160            2
161        ),
162        // Row 4: UTM insights + clicks; the VideoPress package seeds Top videos at order 8.
163        get_dashboard_default_widget_instance(
164            'default-utm-insights-widget-instance',
165            'jpa/utm-insights',
166            6,
167            1,
168            2,
169            array(
170                'utmDimension' => 'utm_source,utm_medium',
171            )
172        ),
173        get_dashboard_default_widget_instance(
174            'default-clicks-widget-instance',
175            'jpa/clicks',
176            7,
177            1,
178            2
179        ),
180        // Row 5: authors + search terms + file downloads (Simple only).
181        get_dashboard_default_widget_instance(
182            'default-authors-widget-instance',
183            'jpa/authors',
184            9,
185            1,
186            2
187        ),
188        get_dashboard_default_widget_instance(
189            'default-search-terms-widget-instance',
190            'jpa/search-terms',
191            10,
192            1,
193            2
194        ),
195        get_dashboard_default_widget_instance(
196            'default-file-downloads-widget-instance',
197            'jpa/file-downloads',
198            11,
199            1,
200            2
201        ),
202    );
203}
204
205/**
206 * The Insights tab's default widget layout.
207 *
208 * @return array Widget instances.
209 */
210function get_insights_section_default_layout() {
211    return array(
212        // Rows follow the design (WOOA7S-2009); Emails lives on the Subscribers tab.
213        // Row 1: highlights banner.
214        get_dashboard_default_widget_instance(
215            'default-annual-highlights-widget-instance',
216            'jpa/annual-highlights',
217            0,
218            3,
219            1
220        ),
221        // Row 2: at-a-glance cards. Two rows tall: their display-sized figures overflow a 200px tile.
222        get_dashboard_default_widget_instance(
223            'default-all-time-stats-widget-instance',
224            'jpa/all-time-stats',
225            1,
226            1,
227            2
228        ),
229        get_dashboard_default_widget_instance(
230            'default-most-popular-time-widget-instance',
231            'jpa/most-popular-time',
232            2,
233            1,
234            2
235        ),
236        get_dashboard_default_widget_instance(
237            'default-most-popular-day-widget-instance',
238            'jpa/most-popular-day',
239            3,
240            1,
241            2
242        ),
243        // Row 3: the two post spotlights.
244        get_dashboard_default_widget_instance(
245            'default-popular-post-widget-instance',
246            'jpa/popular-post',
247            4,
248            2,
249            2
250        ),
251        get_dashboard_default_widget_instance(
252            'default-latest-post-widget-instance',
253            'jpa/latest-post',
254            5,
255            1,
256            2
257        ),
258        // Row 4: posting-activity heatmap.
259        get_dashboard_default_widget_instance(
260            'default-posting-activity-widget-instance',
261            'jpa/posting-activity',
262            6,
263            3,
264            1
265        ),
266        // Row 5: the all-time views table, one row per year. Two rows tall so a
267        // few years fit before the grid scrolls.
268        get_dashboard_default_widget_instance(
269            'default-views-over-years-widget-instance',
270            'jpa/views-over-years',
271            7,
272            3,
273            2
274        ),
275        // Row 6: tags + most commented posts.
276        get_dashboard_default_widget_instance(
277            'default-tags-widget-instance',
278            'jpa/tags',
279            8,
280            2,
281            2
282        ),
283        get_dashboard_default_widget_instance(
284            'default-most-commented-posts-widget-instance',
285            'jpa/most-commented-posts',
286            9,
287            1,
288            2
289        ),
290        // Row 7: shares + most commented authors.
291        get_dashboard_default_widget_instance(
292            'default-shares-widget-instance',
293            'jpa/shares',
294            10,
295            1,
296            2
297        ),
298        get_dashboard_default_widget_instance(
299            'default-most-commented-authors-widget-instance',
300            'jpa/most-commented-authors',
301            11,
302            2,
303            2
304        ),
305    );
306}
307
308/**
309 * The Subscribers tab's default widget layout.
310 *
311 * @return array Widget instances.
312 */
313function get_subscribers_section_default_layout() {
314    return array(
315        // Row 1: subscribers chart.
316        get_dashboard_default_widget_instance(
317            'default-subscribers-chart-widget-instance',
318            'jpa/subscribers-chart',
319            0,
320            3,
321            2
322        ),
323        // Row 2: subscriber highlights.
324        get_dashboard_default_widget_instance(
325            'default-subscriber-highlights-widget-instance',
326            'jpa/subscriber-highlights',
327            1,
328            3,
329            1
330        ),
331        // Row 3: latest subscribers + the wider latest emails sent table.
332        get_dashboard_default_widget_instance(
333            'default-subscribers-list-widget-instance',
334            'jpa/subscribers-list',
335            2,
336            1,
337            2
338        ),
339        get_dashboard_default_widget_instance(
340            'default-subscribers-emails-widget-instance',
341            'jpa/stats-emails',
342            3,
343            2,
344            2,
345            array(
346                'metric' => 'opens',
347            )
348        ),
349    );
350}
351
352/**
353 * Registers the default Premium Analytics dashboard sections.
354 *
355 * Hooked on the registration action and safe to call directly: a section already registered
356 * is skipped.
357 *
358 * @param Dashboard_Section_Registry|null $registry Optional. The registry being hydrated. Defaults to the main instance.
359 * @return void
360 */
361function register_default_dashboard_sections( $registry = null ) {
362    if ( ! $registry instanceof Dashboard_Section_Registry ) {
363        $registry = Dashboard_Section_Registry::get_instance();
364    }
365
366    $sections = array(
367        'analytics/traffic'     => array(
368            'label'               => __( 'Traffic', 'jetpack-premium-analytics-pkg' ),
369            'title'               => __( 'Site traffic', 'jetpack-premium-analytics-pkg' ),
370            'order'               => 10,
371            // Only the Traffic summary groups by interval, and it saves its own.
372            'date_filter_options' => array(
373                'with_header_interval_control' => false,
374            ),
375            'default_layout'      => __NAMESPACE__ . '\\get_traffic_section_default_layout',
376        ),
377        'analytics/insights'    => array(
378            'label'               => __( 'Insights', 'jetpack-premium-analytics-pkg' ),
379            'title'               => __( 'Site insights', 'jetpack-premium-analytics-pkg' ),
380            'order'               => 20,
381            // Insights reads whole history: all time and single years, with nothing
382            // to compare them against. Most widgets have fixed periods of their own,
383            // so no header control; Highlights hosts the only year control.
384            'date_filter'         => Dashboard_Section::DATE_FILTER_YEAR,
385            'date_filter_options' => array(
386                'with_date_comparison'     => false,
387                'with_header_date_control' => false,
388            ),
389            'default_layout'      => __NAMESPACE__ . '\\get_insights_section_default_layout',
390        ),
391        'analytics/subscribers' => array(
392            'label'               => __( 'Subscribers', 'jetpack-premium-analytics-pkg' ),
393            'title'               => __( 'Subscribers stats', 'jetpack-premium-analytics-pkg' ),
394            'order'               => 30,
395            'is_available'        => __NAMESPACE__ . '\\is_subscribers_dashboard_section_available',
396            // Only the summary chart supports dates, so it owns the control. No
397            // Subscribers widget supports comparison.
398            'date_filter_options' => array(
399                'with_date_comparison'     => false,
400                'with_header_date_control' => false,
401            ),
402            'default_layout'      => __NAMESPACE__ . '\\get_subscribers_section_default_layout',
403        ),
404    );
405
406    foreach ( $sections as $id => $args ) {
407        if ( ! $registry->is_registered( DASHBOARD_NAME, $id ) ) {
408            $registry->register( DASHBOARD_NAME, $id, $args );
409        }
410    }
411}
412
413// Registered when the registry hydrates, through the same action a plugin extending the dashboard uses.
414add_action( Dashboard_Section_Registry::REGISTER_ACTION, __NAMESPACE__ . '\\register_default_dashboard_sections' );