Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
84.31% |
43 / 51 |
|
62.50% |
5 / 8 |
CRAP | |
0.00% |
0 / 1 |
| Analytics_Dashboard | |
84.31% |
43 / 51 |
|
62.50% |
5 / 8 |
20.39 | |
0.00% |
0 / 1 |
| init | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
| register_section | |
100.00% |
17 / 17 |
|
100.00% |
1 / 1 |
3 | |||
| get_default_layout | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
| register_widget_types | |
92.31% |
12 / 13 |
|
0.00% |
0 / 1 |
4.01 | |||
| widget_contract_moved_on | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| load_build | |
40.00% |
2 / 5 |
|
0.00% |
0 / 1 |
4.94 | |||
| build_dir | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| dashboard_has_section_slug | |
33.33% |
2 / 6 |
|
0.00% |
0 / 1 |
8.74 | |||
| 1 | <?php |
| 2 | /** |
| 3 | * The Ads section and widgets of the Premium Analytics dashboard. |
| 4 | * |
| 5 | * @package automattic/jetpack-ads |
| 6 | */ |
| 7 | |
| 8 | namespace Automattic\Jetpack\WordAds; |
| 9 | |
| 10 | use Automattic\Jetpack\PremiumAnalytics\Capabilities; |
| 11 | use function Automattic\Jetpack\PremiumAnalytics\get_dashboard_default_widget_instance; |
| 12 | use function Automattic\Jetpack\PremiumAnalytics\register_dashboard_section; |
| 13 | use function Automattic\Jetpack\PremiumAnalytics\register_widget_types_from_manifest; |
| 14 | use const Automattic\Jetpack\PremiumAnalytics\DASHBOARD_NAME; |
| 15 | |
| 16 | /** |
| 17 | * Registers the Ads section, its default layout and its widget types on the Premium Analytics |
| 18 | * dashboard. |
| 19 | * |
| 20 | * The package decides nothing about who gets Ads: the WordAds module of the Jetpack plugin calls |
| 21 | * `init()` outside the WordPress.com platform, jetpack-mu-wpcom calls the registrants on Simple |
| 22 | * and Atomic where the plan includes WordAds and the site has it on. Everything registers when the dashboard's registries |
| 23 | * hydrate, so a call on a site without the dashboard is inert. |
| 24 | * |
| 25 | * @since 0.1.0 |
| 26 | */ |
| 27 | class Analytics_Dashboard { |
| 28 | |
| 29 | const PACKAGE_VERSION = '0.1.1'; |
| 30 | |
| 31 | /** |
| 32 | * Namespaced section identifier. Its slug, `ads`, keys the section's URL and stored layouts. |
| 33 | */ |
| 34 | const SECTION_ID = 'wordads/ads'; |
| 35 | |
| 36 | /** |
| 37 | * Widget type names the package builds, from `widgets/*\/widget.json`. |
| 38 | */ |
| 39 | const CHART_TABS_TYPE = 'wordads/chart-tabs'; |
| 40 | const HIGHLIGHTS_TYPE = 'wordads/highlights'; |
| 41 | const EARNINGS_HISTORY_TYPE = 'wordads/earnings-history'; |
| 42 | |
| 43 | /** |
| 44 | * Registry actions of the dashboard package. |
| 45 | */ |
| 46 | const REGISTER_SECTIONS_ACTION = 'jetpack_premium_analytics_register_dashboard_sections'; |
| 47 | const REGISTER_WIDGET_TYPES_ACTION = 'jetpack_premium_analytics_register_widget_types'; |
| 48 | |
| 49 | /** |
| 50 | * Text domain of the widget metadata and of the built widget bundles. |
| 51 | */ |
| 52 | const TEXTDOMAIN = 'jetpack-ads-pkg'; |
| 53 | |
| 54 | /** |
| 55 | * Hook both registrants on the dashboard's registry actions. |
| 56 | * |
| 57 | * Priority 20, after the dashboard package's own registrants: an older package that still |
| 58 | * registers the section itself is found by slug and left alone. |
| 59 | * |
| 60 | * @return void |
| 61 | */ |
| 62 | public static function init() { |
| 63 | add_action( self::REGISTER_SECTIONS_ACTION, array( __CLASS__, 'register_section' ), 20 ); |
| 64 | add_action( self::REGISTER_WIDGET_TYPES_ACTION, array( __CLASS__, 'register_widget_types' ), 20 ); |
| 65 | } |
| 66 | |
| 67 | /** |
| 68 | * Register the Ads section unless another owner already holds the `ads` slug. |
| 69 | * |
| 70 | * Also skipped when the dashboard's widget contract moved past this build: a tab whose every |
| 71 | * widget is unavailable is worse than no tab. |
| 72 | * |
| 73 | * @param object $registry The section registry being hydrated. |
| 74 | * @return void |
| 75 | */ |
| 76 | public static function register_section( $registry ) { |
| 77 | if ( self::widget_contract_moved_on() || self::dashboard_has_section_slug( $registry, DASHBOARD_NAME, 'ads' ) ) { |
| 78 | return; |
| 79 | } |
| 80 | |
| 81 | register_dashboard_section( |
| 82 | DASHBOARD_NAME, |
| 83 | self::SECTION_ID, |
| 84 | array( |
| 85 | 'label' => __( 'Ads', 'jetpack-ads-pkg' ), |
| 86 | 'title' => __( 'Ads performance', 'jetpack-ads-pkg' ), |
| 87 | 'order' => 50, |
| 88 | 'is_available' => array( Capabilities::class, 'current_user_can_view_ad_reports' ), |
| 89 | // Only the chart supports dates, so it owns the control. No Ads widget |
| 90 | // supports comparison. |
| 91 | 'date_filter_options' => array( |
| 92 | 'with_date_comparison' => false, |
| 93 | 'with_header_date_control' => false, |
| 94 | ), |
| 95 | 'default_layout' => array( __CLASS__, 'get_default_layout' ), |
| 96 | ) |
| 97 | ); |
| 98 | } |
| 99 | |
| 100 | /** |
| 101 | * The default layout of the Ads section: chart on top, balance, then earnings history. |
| 102 | * |
| 103 | * @return array[] Widget instances, as `get_dashboard_default_widget_instance()` builds them. |
| 104 | */ |
| 105 | public static function get_default_layout() { |
| 106 | return array( |
| 107 | get_dashboard_default_widget_instance( 'default-wordads-chart-tabs-widget-instance', self::CHART_TABS_TYPE, 0, 3, 2 ), |
| 108 | get_dashboard_default_widget_instance( 'default-wordads-highlights-widget-instance', self::HIGHLIGHTS_TYPE, 1, 3, 1 ), |
| 109 | get_dashboard_default_widget_instance( 'default-wordads-earnings-history-widget-instance', self::EARNINGS_HISTORY_TYPE, 2, 1, 2 ), |
| 110 | ); |
| 111 | } |
| 112 | |
| 113 | /** |
| 114 | * Register the widget types from the package's build manifest. |
| 115 | * |
| 116 | * Loads the generated build first: after `init` it registers the widget script modules on the |
| 117 | * spot, so they reach the page import map the same request. |
| 118 | * |
| 119 | * @param object $registry The widget type registry being hydrated. |
| 120 | * @return void |
| 121 | */ |
| 122 | public static function register_widget_types( $registry ) { |
| 123 | if ( ! defined( 'Automattic\\Jetpack\\PremiumAnalytics\\WIDGET_API_VERSION' ) || self::widget_contract_moved_on() ) { |
| 124 | return; |
| 125 | } |
| 126 | |
| 127 | self::load_build(); |
| 128 | if ( ! function_exists( 'jetpack_ads_get_registered_widget_modules' ) ) { |
| 129 | return; |
| 130 | } |
| 131 | |
| 132 | register_widget_types_from_manifest( |
| 133 | jetpack_ads_get_registered_widget_modules(), |
| 134 | array( |
| 135 | 'textdomain' => self::TEXTDOMAIN, |
| 136 | 'i18n_manifest' => add_query_arg( 'ver', self::PACKAGE_VERSION, plugins_url( 'i18n-manifest.json', self::build_dir() . '/build.php' ) ), |
| 137 | ), |
| 138 | $registry |
| 139 | ); |
| 140 | } |
| 141 | |
| 142 | /** |
| 143 | * Whether the dashboard's widget contract moved to a major this package was not built against. |
| 144 | * |
| 145 | * Undefined is not a mismatch: the sections REST route hydrates the section registry before |
| 146 | * the dashboard loads the file that defines the version. |
| 147 | * |
| 148 | * @return bool |
| 149 | */ |
| 150 | private static function widget_contract_moved_on() { |
| 151 | return defined( 'Automattic\\Jetpack\\PremiumAnalytics\\WIDGET_API_VERSION' ) |
| 152 | && version_compare( \Automattic\Jetpack\PremiumAnalytics\WIDGET_API_VERSION, '2', '>=' ); |
| 153 | } |
| 154 | |
| 155 | /** |
| 156 | * Load the generated build once. Guarded by symbol, not by path: on WordPress.com Simple two |
| 157 | * copies of the package can share a request. |
| 158 | * |
| 159 | * @return void |
| 160 | */ |
| 161 | private static function load_build() { |
| 162 | if ( function_exists( 'jetpack_ads_get_registered_widget_modules' ) ) { |
| 163 | return; |
| 164 | } |
| 165 | $loader = self::build_dir() . '/build.php'; |
| 166 | if ( file_exists( $loader ) ) { |
| 167 | require_once $loader; |
| 168 | } |
| 169 | } |
| 170 | |
| 171 | /** |
| 172 | * Directory of the wp-build output. |
| 173 | * |
| 174 | * @return string |
| 175 | */ |
| 176 | private static function build_dir() { |
| 177 | return dirname( __DIR__ ) . '/build'; |
| 178 | } |
| 179 | |
| 180 | /** |
| 181 | * Whether a section with the slug is registered on the dashboard. |
| 182 | * |
| 183 | * Reads the registry through the slug lookup when the package offers it, and through the full |
| 184 | * list otherwise: the package and this one ship on different cadences. |
| 185 | * |
| 186 | * @param object $registry The registry being hydrated. |
| 187 | * @param string $dashboard_name Dashboard identifier. |
| 188 | * @param string $slug Section slug. |
| 189 | * @return bool |
| 190 | */ |
| 191 | private static function dashboard_has_section_slug( $registry, $dashboard_name, $slug ) { |
| 192 | if ( method_exists( $registry, 'get_registered_by_slug' ) ) { |
| 193 | return null !== $registry->get_registered_by_slug( $dashboard_name, $slug ); |
| 194 | } |
| 195 | |
| 196 | foreach ( $registry->get_all_registered( $dashboard_name ) as $section ) { |
| 197 | if ( $section->slug === $slug ) { |
| 198 | return true; |
| 199 | } |
| 200 | } |
| 201 | |
| 202 | return false; |
| 203 | } |
| 204 | } |