Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
92.50% |
74 / 80 |
|
90.00% |
9 / 10 |
CRAP | |
0.00% |
0 / 1 |
| WPCOM_My_Jetpack | |
96.10% |
74 / 77 |
|
90.00% |
9 / 10 |
24 | |
0.00% |
0 / 1 |
| should_register | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| get_page_title | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| get_menu_title | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| get_initial_state | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
| get_tools | |
100.00% |
21 / 21 |
|
100.00% |
1 / 1 |
7 | |||
| get_tool_url | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
| get_tool_key | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
3 | |||
| get_tool_title | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| get_known_tools | |
100.00% |
31 / 31 |
|
100.00% |
1 / 1 |
3 | |||
| filter_modules_management_available | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
| 1 | <?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName -- Feature entry file, named after the feature, also holds the WPCOM_My_Jetpack bootstrap class. |
| 2 | /** |
| 3 | * A lightweight My Jetpack page for WordPress.com Simple sites. |
| 4 | * |
| 5 | * It lists what the site's Jetpack menu already holds, and steps aside wherever the real My Jetpack |
| 6 | * registers. It is a stopgap: delete it once My Jetpack itself runs on Simple. |
| 7 | * |
| 8 | * @package automattic/jetpack-mu-wpcom |
| 9 | */ |
| 10 | |
| 11 | namespace Automattic\Jetpack\Jetpack_Mu_Wpcom; |
| 12 | |
| 13 | use Automattic\Jetpack\Admin_UI\Admin_Menu; |
| 14 | use Automattic\Jetpack\Status\Host; |
| 15 | |
| 16 | require_once __DIR__ . '/../../common/class-wpcom-wp-build-page.php'; |
| 17 | |
| 18 | /** |
| 19 | * Registers the page and its wp-build assets, unless the real My Jetpack claimed the slug. |
| 20 | */ |
| 21 | class WPCOM_My_Jetpack extends WPCOM_WP_Build_Page { |
| 22 | |
| 23 | /** |
| 24 | * Where the rest of Jetpack links My Jetpack, and first in the Jetpack menu's sort. |
| 25 | */ |
| 26 | const MENU_SLUG = 'my-jetpack'; |
| 27 | |
| 28 | /** |
| 29 | * As declared in routes/wpcom-my-jetpack/package.json. |
| 30 | */ |
| 31 | const WP_BUILD_PAGE = 'wpcom-my-jetpack'; |
| 32 | |
| 33 | /** |
| 34 | * Authors and up: Contributors can reach no Jetpack tool on Simple, so the page would only ever be empty for them. |
| 35 | */ |
| 36 | const CAPABILITY = 'publish_posts'; |
| 37 | |
| 38 | /** |
| 39 | * Global the page's boot script reads get_initial_state() from. |
| 40 | */ |
| 41 | const INITIAL_STATE_VAR = 'wpcomMyJetpackInitialState'; |
| 42 | |
| 43 | /** |
| 44 | * Records clicks, to see which tools people reach from here. |
| 45 | */ |
| 46 | const ENQUEUE_TRACKS = true; |
| 47 | |
| 48 | /** |
| 49 | * Whether this page should claim its slug: everywhere but P2s, which drop the Jetpack menu. |
| 50 | * |
| 51 | * @return bool |
| 52 | */ |
| 53 | public static function should_register() { |
| 54 | return ! ( new Host() )->is_p2_site(); |
| 55 | } |
| 56 | |
| 57 | /** |
| 58 | * The page title. |
| 59 | * |
| 60 | * @return string |
| 61 | */ |
| 62 | public static function get_page_title() { |
| 63 | return __( 'My Jetpack', 'jetpack-mu-wpcom' ); |
| 64 | } |
| 65 | |
| 66 | /** |
| 67 | * The menu entry's label. |
| 68 | * |
| 69 | * @return string |
| 70 | */ |
| 71 | public static function get_menu_title() { |
| 72 | return __( 'My Jetpack', 'jetpack-mu-wpcom' ); |
| 73 | } |
| 74 | |
| 75 | /** |
| 76 | * The state the page's boot script reads. |
| 77 | * |
| 78 | * @return array |
| 79 | */ |
| 80 | public static function get_initial_state() { |
| 81 | return array( |
| 82 | 'tools' => static::get_tools(), |
| 83 | ); |
| 84 | } |
| 85 | |
| 86 | /** |
| 87 | * Gets the tools to list: the visible Jetpack menu items, in menu order. |
| 88 | * |
| 89 | * @return array[] Tools with `title`, `description`, `key`, `section` and `url` keys. |
| 90 | */ |
| 91 | public static function get_tools() { |
| 92 | global $submenu; |
| 93 | |
| 94 | // This runs at enqueue, before admin_head drops hidden items, so an entry stays off this page |
| 95 | // only if admin-ui hides it or it carries `hide-if-js`. |
| 96 | if ( method_exists( Admin_Menu::class, 'remove_hidden_menu_items' ) ) { |
| 97 | Admin_Menu::remove_hidden_menu_items(); |
| 98 | } |
| 99 | |
| 100 | $known = static::get_known_tools(); |
| 101 | $tools = array(); |
| 102 | |
| 103 | foreach ( $submenu[ static::PARENT_SLUG ] ?? array() as $item ) { |
| 104 | if ( static::MENU_SLUG === $item[2] || str_contains( $item[4] ?? '', 'hide-if-js' ) ) { |
| 105 | continue; |
| 106 | } |
| 107 | |
| 108 | // Slugs and titles are escaped for wp-admin's menu HTML, and React escapes them again. |
| 109 | $slug = html_entity_decode( $item[2], ENT_QUOTES, 'UTF-8' ); |
| 110 | |
| 111 | // Drops a link in any protocol WordPress doesn't allow, like `javascript:`. |
| 112 | $url = esc_url_raw( static::get_tool_url( $slug ) ); |
| 113 | if ( '' === $url || isset( $tools[ $url ] ) ) { |
| 114 | continue; |
| 115 | } |
| 116 | |
| 117 | $key = static::get_tool_key( $slug ); |
| 118 | $tool = $known[ $key ] ?? null; |
| 119 | |
| 120 | $tools[ $url ] = array( |
| 121 | 'title' => static::get_tool_title( $item[0] ), |
| 122 | 'description' => $tool['description'] ?? '', |
| 123 | 'key' => $key, |
| 124 | 'section' => $tool['section'] ?? 'more', |
| 125 | 'url' => $url, |
| 126 | ); |
| 127 | } |
| 128 | |
| 129 | return array_values( $tools ); |
| 130 | } |
| 131 | |
| 132 | /** |
| 133 | * Gets where a menu slug leads, the way wp-admin's sidebar links it. |
| 134 | * |
| 135 | * @param string $slug The menu slug. |
| 136 | * @return string |
| 137 | */ |
| 138 | private static function get_tool_url( $slug ) { |
| 139 | if ( wp_parse_url( $slug, PHP_URL_HOST ) ) { |
| 140 | return $slug; |
| 141 | } |
| 142 | |
| 143 | // Only a slug with a page callback goes through admin.php; any other is a path under wp-admin. |
| 144 | if ( get_plugin_page_hook( $slug, static::PARENT_SLUG ) ) { |
| 145 | return add_query_arg( 'page', $slug, admin_url( 'admin.php' ) ); |
| 146 | } |
| 147 | |
| 148 | return admin_url( $slug ); |
| 149 | } |
| 150 | |
| 151 | /** |
| 152 | * Gets the key a menu slug is known by: the path of a WordPress.com link to this site, or the slug without `jetpack-`. |
| 153 | * |
| 154 | * @param string $slug The menu slug. |
| 155 | * @return string The key, or '' for any other link that leaves wp-admin, so no address reaches Tracks. |
| 156 | */ |
| 157 | private static function get_tool_key( $slug ) { |
| 158 | $domain = wp_parse_url( home_url(), PHP_URL_HOST ); |
| 159 | |
| 160 | if ( preg_match( '#^https://wordpress\.com/(.+)/' . preg_quote( $domain, '#' ) . '/?$#', $slug, $matches ) ) { |
| 161 | $key = $matches[1]; |
| 162 | } elseif ( wp_parse_url( $slug, PHP_URL_HOST ) ) { |
| 163 | return ''; |
| 164 | } else { |
| 165 | $key = preg_replace( '/^jetpack-/', '', $slug ); |
| 166 | } |
| 167 | |
| 168 | $aliases = array( |
| 169 | 'forms-admin' => 'forms', |
| 170 | 'forms-responses-wp-admin' => 'forms', |
| 171 | ); |
| 172 | |
| 173 | return $aliases[ $key ] ?? $key; |
| 174 | } |
| 175 | |
| 176 | /** |
| 177 | * Gets a menu title's text, without the count badges rendered into its spans. |
| 178 | * |
| 179 | * @param string $title The menu title's HTML. |
| 180 | * @return string |
| 181 | */ |
| 182 | private static function get_tool_title( $title ) { |
| 183 | $text = trim( wp_strip_all_tags( preg_replace( '#<span\b[^>]*>.*?</span>#s', '', $title ) ) ); |
| 184 | |
| 185 | // A title may wrap its whole name in a span. |
| 186 | return html_entity_decode( '' !== $text ? $text : wp_strip_all_tags( $title ), ENT_QUOTES, 'UTF-8' ); |
| 187 | } |
| 188 | |
| 189 | /** |
| 190 | * Gets each known tool's section and description, keyed as get_tool_key() returns. |
| 191 | * |
| 192 | * The key also names the tool's icon in the page's script. A tool missing here shows under More, with the Jetpack mark. |
| 193 | * |
| 194 | * @return array[] Tools with `section` and `description` keys. |
| 195 | */ |
| 196 | private static function get_known_tools() { |
| 197 | $tools = array( |
| 198 | 'security' => array( |
| 199 | 'activity-log' => __( 'See every change made to this site', 'jetpack-mu-wpcom' ), |
| 200 | 'akismet-key-config' => __( 'Filter spam from comments and forms', 'jetpack-mu-wpcom' ), |
| 201 | 'backup' => __( 'Restorable copies of your site', 'jetpack-mu-wpcom' ), |
| 202 | 'protect' => __( 'Protect your site from malware and attacks', 'jetpack-mu-wpcom' ), |
| 203 | 'scan' => __( 'Check your site for malware and threats', 'jetpack-mu-wpcom' ), |
| 204 | ), |
| 205 | 'growth' => array( |
| 206 | 'advertising' => __( 'Promote posts across WordPress.com and Tumblr', 'jetpack-mu-wpcom' ), |
| 207 | 'earn' => __( 'Earn from ads, donations and paid content', 'jetpack-mu-wpcom' ), |
| 208 | 'marketing/traffic' => __( 'Analytics and site verification', 'jetpack-mu-wpcom' ), |
| 209 | 'newsletter' => __( 'Send your posts to subscribers', 'jetpack-mu-wpcom' ), |
| 210 | 'seo' => __( 'Titles and descriptions for search engines', 'jetpack-mu-wpcom' ), |
| 211 | 'social' => __( 'Share posts to your social accounts', 'jetpack-mu-wpcom' ), |
| 212 | ), |
| 213 | 'content' => array( |
| 214 | 'forms' => __( 'Collect and review form responses', 'jetpack-mu-wpcom' ), |
| 215 | 'podcast' => __( 'Publish your posts as a podcast feed', 'jetpack-mu-wpcom' ), |
| 216 | 'search' => __( 'Fast, relevant search for your visitors', 'jetpack-mu-wpcom' ), |
| 217 | 'videopress' => __( 'Ad-free video hosting', 'jetpack-mu-wpcom' ), |
| 218 | ), |
| 219 | ); |
| 220 | |
| 221 | $known = array(); |
| 222 | foreach ( $tools as $section => $descriptions ) { |
| 223 | foreach ( $descriptions as $key => $description ) { |
| 224 | $known[ $key ] = array( |
| 225 | 'section' => $section, |
| 226 | 'description' => $description, |
| 227 | ); |
| 228 | } |
| 229 | } |
| 230 | |
| 231 | return $known; |
| 232 | } |
| 233 | |
| 234 | /** |
| 235 | * Tells code that links to My Jetpack's Features screen that this page doesn't have one. |
| 236 | * |
| 237 | * @param bool $available Whether modules can be managed from My Jetpack. |
| 238 | * @return bool |
| 239 | */ |
| 240 | public static function filter_modules_management_available( $available ) { |
| 241 | return $available && ! static::owns_page(); |
| 242 | } |
| 243 | } |
| 244 | |
| 245 | add_action( 'init', array( WPCOM_My_Jetpack::class, 'init' ) ); |
| 246 | add_filter( 'jetpack_my_jetpack_modules_management_available', array( WPCOM_My_Jetpack::class, 'filter_modules_management_available' ) ); |