Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 56
0.00% covered (danger)
0.00%
0 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
Dashboard
0.00% covered (danger)
0.00%
0 / 56
0.00% covered (danger)
0.00%
0 / 10
506
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
20
 init_hooks
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 add_wp_admin_submenu
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
12
 render
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 should_add_search_submenu
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 remove_search_submenu_if_exists
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 admin_init
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 load_admin_scripts
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
6
 should_enqueue_tracking_script
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 check_plan_deactivate_search_module
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
30
1<?php
2/**
3 * A class that adds a search dashboard to wp-admin.
4 *
5 * @package automattic/jetpack
6 */
7
8namespace Automattic\Jetpack\Search;
9
10use Automattic\Jetpack\Admin_UI\Admin_Menu;
11use Automattic\Jetpack\Assets;
12use Automattic\Jetpack\Connection\Initial_State as Connection_Initial_State;
13use Automattic\Jetpack\Connection\Manager as Connection_Manager;
14use Automattic\Jetpack\Status;
15use Automattic\Jetpack\Tracking;
16/**
17 * Responsible for adding a search dashboard to wp-admin.
18 *
19 * @package Automattic\Jetpack\Search
20 */
21class Dashboard {
22    /**
23     * Whether the class has been initialized
24     *
25     * @var boolean
26     */
27    private static $initialized = false;
28    /**
29     * Plan instance
30     *
31     * @var \Automattic\Jetpack\Search\Plan
32     */
33    protected $plan;
34
35    /**
36     * Connection manager instance
37     *
38     * @var \Automattic\Jetpack\Connection\Manager
39     */
40    protected $connection_manager;
41
42    /**
43     * Module_Control instance
44     *
45     * @var \Automattic\Jetpack\Search\Module_Control
46     */
47    protected $module_control;
48
49    /**
50     * Priority for the dashboard menu
51     * For Jetpack sites: Akismet uses 4, so we use 1 to ensure both menus are added when only they exist.
52     * For Simple sites: the value is overriden in a child class with value 100000 to wait for all menus to be registered.
53     *
54     * @var int
55     */
56    protected $search_menu_priority = 1;
57
58    /**
59     * Contructor
60     *
61     * @param \Automattic\Jetpack\Search\Plan           $plan - Plan instance.
62     * @param \Automattic\Jetpack\Connection\Manager    $connection_manager - Connection Manager instance.
63     * @param \Automattic\Jetpack\Search\Module_Control $module_control - Module_Control instance.
64     */
65    public function __construct( $plan = null, $connection_manager = null, $module_control = null ) {
66        $this->plan               = $plan ? $plan : new Plan();
67        $this->connection_manager = $connection_manager ? $connection_manager : new Connection_Manager( Package::SLUG );
68        $this->module_control     = $module_control ? $module_control : new Module_Control( $this->plan );
69        $this->plan->init_hooks();
70    }
71
72    /**
73     * Initialise hooks.
74     *
75     * We use the `config` package to initialize the search package, which ensures the package is
76     * only initialized once. However earlier versions of Jetpack would still forcely initialize the
77     * dashboard. As a result, there would be two `Search` submenus if we don't ensure the dashboard
78     * is initialized only once. So we use `$initialized` to ensure the class is only initialized once.
79     *
80     * Ref: https://github.com/Automattic/jetpack/pull/21888/files#diff-aae7d66951585fc55053a4d53b68552a41864d2c69aee900574ef4404b7ad5f7L42
81     */
82    public function init_hooks() {
83        if ( ! self::$initialized ) {
84            self::$initialized = true;
85            add_action( 'admin_menu', array( $this, 'add_wp_admin_submenu' ), $this->search_menu_priority );
86            // Check if the site plan changed and deactivate module accordingly.
87            add_action( 'current_screen', array( $this, 'check_plan_deactivate_search_module' ) );
88        }
89    }
90
91    /**
92     * The page to be added to submenu
93     */
94    public function add_wp_admin_submenu() {
95        // Jetpack of version <= 10.5 would register `jetpack-search` submenu with its built-in search module.
96        $this->remove_search_submenu_if_exists();
97
98        if ( $this->should_add_search_submenu() ) {
99            $page_suffix = Admin_Menu::add_menu(
100                /** "Search" is a product name, do not translate. */
101                'Jetpack Search',
102                'Search',
103                'manage_options',
104                'jetpack-search',
105                array( $this, 'render' ),
106                10
107            );
108        } else {
109            // always add the page, but hide it from the menu.
110            $page_suffix = add_submenu_page(
111                '',
112                /** "Search" is a product name, do not translate. */
113                'Jetpack Search',
114                'Search',
115                'manage_options',
116                'jetpack-search',
117                array( $this, 'render' )
118            );
119        }
120
121        if ( $page_suffix ) {
122            add_action( 'load-' . $page_suffix, array( $this, 'admin_init' ) );
123        }
124    }
125
126    /**
127     * Override render funtion
128     */
129    public function render() {
130        ?>
131        <div id="jp-search-dashboard" class="jp-search-dashboard">
132            <div class="hide-if-js"><?php esc_html_e( 'Your Jetpack Search dashboard requires JavaScript to function properly.', 'jetpack-search-pkg' ); ?></div>
133        </div>
134        <?php
135    }
136
137    /**
138     * Test whether we should show Search menu.
139     *
140     * @return boolean Show search sub menu or not.
141     */
142    protected function should_add_search_submenu() {
143        /**
144         * The filter allows to ommit adding a submenu item for Jetpack Search.
145         *
146         * @since 0.11.2
147         *
148         * @param boolean $should_add_search_submenu Default value is true.
149         */
150        return apply_filters( 'jetpack_search_should_add_search_submenu', current_user_can( 'manage_options' ) );
151    }
152
153    /**
154     * Remove `jetpack-search` submenu page
155     */
156    protected function remove_search_submenu_if_exists() {
157        remove_submenu_page( 'jetpack', 'jetpack-search' );
158    }
159
160    /**
161     * Initialize the admin resources.
162     */
163    public function admin_init() {
164        add_action( 'admin_enqueue_scripts', array( $this, 'load_admin_scripts' ) );
165    }
166
167    /**
168     * Enqueue admin scripts.
169     */
170    public function load_admin_scripts() {
171        if ( $this->should_enqueue_tracking_script() ) {
172            // Required for Analytics.
173            Tracking::register_tracks_functions_scripts( true );
174        }
175
176        Assets::register_script(
177            'jp-search-dashboard',
178            '../../build/dashboard/jp-search-dashboard.js',
179            __FILE__,
180            array(
181                'in_footer'  => true,
182                'textdomain' => 'jetpack-search-pkg',
183            )
184        );
185        Assets::enqueue_script( 'jp-search-dashboard' );
186
187        // Add objects to be passed to the initial state of the app.
188        // Use wp_add_inline_script instead of wp_localize_script, see https://core.trac.wordpress.org/ticket/25280.
189        wp_add_inline_script(
190            'jp-search-dashboard',
191            ( new Initial_State() )->render(),
192            'before'
193        );
194
195        // Connection initial state.
196        Connection_Initial_State::render_script( 'jp-search-dashboard' );
197    }
198
199    /**
200     * Check if we should enqueue the tracking script.
201     */
202    protected function should_enqueue_tracking_script() {
203        return ! ( new Status() )->is_offline_mode() && $this->connection_manager->is_connected();
204    }
205
206    /**
207     * Deactivate search module if plan doesn't support search.
208     *
209     * @param \WP_Screen $current_screen Creent screen object.
210     */
211    public function check_plan_deactivate_search_module( $current_screen ) {
212        // Only run on Jetpack admin pages.
213        // The first two checks for current screen are cheap to run on every page.
214        if (
215            property_exists( $current_screen, 'base' ) &&
216            strpos( $current_screen->base, 'jetpack_page_' ) !== false &&
217            ( ! $this->plan->supports_search() || $this->plan->must_upgrade() )
218        ) {
219            $this->module_control->deactivate();
220        }
221    }
222}