Completed
Push — update/anchor-fm-checks-spotif... ( f9835e...28fd2e )
by
unknown
234:35 queued 224:59
created

Jetpack_Calypsoify::get_instance()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 0
dl 0
loc 7
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * This is Calypso skin of the wp-admin interface that is conditionally triggered via the ?calypsoify=1 param.
4
 *
5
 * @package Jetpack
6
 */
7
8
use Automattic\Jetpack\Dashboard_Customizations\Masterbar;
9
use Automattic\Jetpack\Redirect;
10
use Automattic\Jetpack\Status;
11
12
/**
13
 * Class Jetpack_Calypsoify
14
 */
15
class Jetpack_Calypsoify {
16
17
	/**
18
	 * Singleton instance of `Jetpack_Calypsoify`.
19
	 *
20
	 * @var object
21
	 */
22
	public static $instance = false;
23
24
	/**
25
	 * Is Calypsoify enabled, based on any value of `calypsoify` user meta.
26
	 *
27
	 * @var bool
28
	 */
29
	public $is_calypsoify_enabled = false;
30
31
	/**
32
	 * Jetpack_Calypsoify constructor.
33
	 */
34
	private function __construct() {
35
		add_action( 'wp_loaded', array( $this, 'setup' ) );
36
	}
37
38
	/**
39
	 * Original singleton.
40
	 *
41
	 * @todo We need to leave this in place until wpcomsh is updated. wpcomsh can be updated once 9.3.0 is stable.
42
	 *
43
	 * Deprecated 9.3.0
44
	 *
45
	 * @return Jetpack_Calypsoify
46
	 */
47
	public static function getInstance() { //phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid
48
		_deprecated_function( __METHOD__, 'Jetpack 9.3.0', 'Jetpack_Calypsoify::get_instance' );
49
		return self::get_instance();
50
	}
51
52
	/**
53
	 * Singleton.
54
	 *
55
	 * @return Jetpack_Calypsoify
56
	 */
57
	public static function get_instance() {
58
		if ( ! self::$instance ) {
59
			self::$instance = new self();
60
		}
61
62
		return self::$instance;
63
	}
64
65
	/**
66
	 * Setup function that is loaded on the `wp_loaded` hook via the constructor.
67
	 */
68
	public function setup() {
69
		$this->is_calypsoify_enabled = 1 === (int) get_user_meta( get_current_user_id(), 'calypsoify', true );
70
		if ( isset( $_GET['calypsoify'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
71
			$this->is_calypsoify_enabled = 1 === (int) $_GET['calypsoify']; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
72
		}
73
74
		add_action( 'admin_init', array( $this, 'check_param' ), 4 );
75
76
		if ( $this->is_calypsoify_enabled ) {
77
			add_action( 'admin_init', array( $this, 'setup_admin' ), 6 );
78
79
			if ( $this->should_override_nav() ) {
80
				add_action( 'admin_menu', array( $this, 'remove_core_menus' ), 100 );
81
				add_action( 'admin_menu', array( $this, 'add_custom_menus' ), 101 );
82
			}
83
		}
84
	}
85
86
	/**
87
	 * Determines whether Calypsoify should override the navigation.
88
	 *
89
	 * @return bool Whether the navigation should be overridden.
90
	 */
91
	public function should_override_nav() {
92
		if ( ! $this->is_calypsoify_enabled ) {
93
			return false;
94
		}
95
96
		/**
97
		 * Filters whether Calypsoify should override the navigation.
98
		 *
99
		 * @since 9.4.0
100
		 *
101
		 * @param bool $should_override_nav Should the navigation be overridden? Default to true.
102
		 */
103
		return apply_filters( 'jetpack_calypsoify_override_nav', true );
104
	}
105
106
	/**
107
	 * Setup functionality within wp-admin via the `admin_init` hook.
108
	 */
109
	public function setup_admin() {
110
		if ( $this->is_page_gutenberg() ) {
111
			add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_for_gutenberg' ), 100 );
112
			return;
113
		}
114
115
		if ( $this->should_override_nav() ) {
116
			// Masterbar is currently required for this to work properly. Mock the instance of it.
117
			if ( ! Jetpack::is_module_active( 'masterbar' ) ) {
118
				$this->mock_masterbar_activation();
119
			}
120
121
			add_action( 'admin_init', array( $this, 'check_page' ) );
122
			add_action( 'admin_enqueue_scripts', array( $this, 'enqueue' ), 100 );
123
			add_action( 'in_admin_header', array( $this, 'insert_sidebar_html' ) );
124
			add_action( 'wp_before_admin_bar_render', array( $this, 'modify_masterbar' ), 100000 );
125
126
			add_filter( 'get_user_option_admin_color', array( $this, 'admin_color_override' ) );
127
128
			add_action( 'current_screen', array( $this, 'attach_views_filter' ) );
129
		}
130
	}
131
132
	/**
133
	 * Set admin color.
134
	 *
135
	 * Used via the get_user_option_admin_color filter.
136
	 *
137
	 * @return string 'fresh'
138
	 */
139
	public function admin_color_override() {
140
		return 'fresh';
141
	}
142
143
	/**
144
	 * Mocks the Masterbar module.
145
	 *
146
	 * Calypsoify uses the Masterbar, so for sites without the Masterbar module active, this will use it for the sake of a Calyposify request.
147
	 *
148
	 * @return Masterbar
149
	 */
150
	public function mock_masterbar_activation() {
151
		include_once JETPACK__PLUGIN_DIR . 'modules/masterbar/masterbar/class-masterbar.php';
152
		return new Masterbar();
153
	}
154
155
	/**
156
	 * Removes Core's menu pages that we don't display.
157
	 */
158
	public function remove_core_menus() {
159
		remove_menu_page( 'edit.php?post_type=feedback' );
160
		remove_menu_page( 'index.php' );
161
		remove_menu_page( 'jetpack' );
162
		remove_menu_page( 'edit.php' );
163
		remove_menu_page( 'upload.php' );
164
		remove_menu_page( 'edit.php?post_type=page' );
165
		remove_menu_page( 'edit-comments.php' );
166
		remove_menu_page( 'themes.php' );
167
		remove_menu_page( 'plugins.php' );
168
		remove_menu_page( 'users.php' );
169
		remove_menu_page( 'tools.php' );
170
		remove_menu_page( 'link-manager.php' );
171
172
		// Core settings pages.
173
		remove_submenu_page( 'options-general.php', 'options-general.php' );
174
		remove_submenu_page( 'options-general.php', 'options-writing.php' );
175
		remove_submenu_page( 'options-general.php', 'options-reading.php' );
176
		remove_submenu_page( 'options-general.php', 'options-discussion.php' );
177
		remove_submenu_page( 'options-general.php', 'options-media.php' );
178
		remove_submenu_page( 'options-general.php', 'options-permalink.php' );
179
		remove_submenu_page( 'options-general.php', 'privacy.php' );
180
		remove_submenu_page( 'options-general.php', 'sharing' );
181
	}
182
183
	/**
184
	 * Adds the custom menus.
185
	 */
186
	public function add_custom_menus() {
187
		global $menu, $submenu;
188
189
		if ( isset( $_GET['post_type'] ) && 'feedback' === $_GET['post_type'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
190
			// there is currently no gridicon for feedback, so using dashicon.
191
			add_menu_page( __( 'Feedback', 'jetpack' ), __( 'Feedback', 'jetpack' ), 'edit_pages', 'edit.php?post_type=feedback', '', 'dashicons-feedback', 1 );
192
			remove_menu_page( 'options-general.php' );
193
			remove_submenu_page( 'edit.php?post_type=feedback', 'feedback-export' );
194
		} else {
195
			add_menu_page( __( 'Manage Plugins', 'jetpack' ), __( 'Manage Plugins', 'jetpack' ), 'activate_plugins', 'plugins.php', '', $this->installed_plugins_icon(), 1 );
196
			// Count the settings page submenus, if it's zero then don't show this.
197
			if ( empty( $submenu['options-general.php'] ) ) {
198
				remove_menu_page( 'options-general.php' );
199
			} else {
200
				// Rename and make sure the plugin settings menu is always last.
201
				// Sneaky plugins seem to override this otherwise.
202
				// Settings is always key 80.
203
				$menu[80][0]                            = __( 'Plugin Settings', 'jetpack' ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
204
				$menu[ max( array_keys( $menu ) ) + 1 ] = $menu[80]; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
205
				unset( $menu[80] );
206
			}
207
		}
208
	}
209
210
	/**
211
	 * Enqueues scripts, data, and styles.
212
	 */
213
	public function enqueue() {
214
		wp_enqueue_style( 'calypsoify_wpadminmods_css', plugin_dir_url( __FILE__ ) . 'style.min.css', false, JETPACK__VERSION );
215
		wp_style_add_data( 'calypsoify_wpadminmods_css', 'rtl', 'replace' );
216
		wp_style_add_data( 'calypsoify_wpadminmods_css', 'suffix', '.min' );
217
218
		wp_enqueue_script( 'calypsoify_wpadminmods_js', plugin_dir_url( __FILE__ ) . 'mods.js', false, JETPACK__VERSION, false );
219
		wp_localize_script(
220
			'calypsoify_wpadminmods_js',
221
			'CalypsoifyOpts',
222
			array(
223
				'nonces' => array(
224
					'autoupdate_plugins'              => wp_create_nonce( 'jetpack_toggle_autoupdate-plugins' ),
225
					'autoupdate_plugins_translations' => wp_create_nonce( 'jetpack_toggle_autoupdate-plugins_translations' ),
226
				),
227
			)
228
		);
229
	}
230
231
	/**
232
	 * Enqueues scripts, data, and styles for Gutenberg.
233
	 */
234
	public function enqueue_for_gutenberg() {
235
		wp_enqueue_style( 'calypsoify_wpadminmods_css', plugin_dir_url( __FILE__ ) . 'style-gutenberg.min.css', false, JETPACK__VERSION );
236
		wp_style_add_data( 'calypsoify_wpadminmods_css', 'rtl', 'replace' );
237
		wp_style_add_data( 'calypsoify_wpadminmods_css', 'suffix', '.min' );
238
239
		wp_enqueue_script( 'calypsoify_wpadminmods_js', plugin_dir_url( __FILE__ ) . 'mods-gutenberg.js', false, JETPACK__VERSION, false );
240
		wp_localize_script(
241
			'calypsoify_wpadminmods_js',
242
			'calypsoifyGutenberg',
243
			array(
244
				'closeUrl'                => $this->get_close_gutenberg_url(),
245
				'manageReusableBlocksUrl' => $this->get_calypso_origin() . '/types/wp_block/' . ( new Status() )->get_site_suffix(),
246
			)
247
		);
248
	}
249
250
	/**
251
	 * Inserts Sidebar HTML
252
	 *
253
	 * @return void
254
	 */
255
	public function insert_sidebar_html() {
256
		$heading  = ( isset( $_GET['post_type'] ) && 'feedback' === $_GET['post_type'] ) ? __( 'Feedback', 'jetpack' ) : __( 'Plugins', 'jetpack' ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
257
		$home_url = Redirect::get_url( 'calypso-home' );
258
		?>
259
		<a href="<?php echo esc_url( $home_url ); ?>" id="calypso-sidebar-header">
260
			<svg class="gridicon gridicons-chevron-left" height="24" width="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g><path d="M14 20l-8-8 8-8 1.414 1.414L8.828 12l6.586 6.586"></path></g></svg>
261
262
			<ul>
263
				<li id="calypso-sitename"><?php bloginfo( 'name' ); ?></li>
264
				<li id="calypso-plugins"><?php echo esc_html( $heading ); ?></li>
265
			</ul>
266
		</a>
267
		<?php
268
	}
269
270
	/**
271
	 * Modifies the masterbar.
272
	 */
273
	public function modify_masterbar() {
274
		global $wp_admin_bar;
275
276
		// Add proper links to masterbar top sections.
277
		$my_sites_node       = (object) $wp_admin_bar->get_node( 'blog' );
278
		$my_sites_node->href = Redirect::get_url( 'calypso-home' );
279
		$wp_admin_bar->add_node( $my_sites_node );
280
281
		$reader_node       = (object) $wp_admin_bar->get_node( 'newdash' );
282
		$reader_node->href = Redirect::get_url( 'calypso-read' );
283
		$wp_admin_bar->add_node( $reader_node );
284
285
		$me_node       = (object) $wp_admin_bar->get_node( 'my-account' );
286
		$me_node->href = Redirect::get_url( 'calypso-me' );
287
		$wp_admin_bar->add_node( $me_node );
288
	}
289
290
	/**
291
	 * Returns a SVG of the installed plugins icon.
292
	 *
293
	 * @return string SVG+XML of the installed plugins icon.
294
	 */
295
	private function installed_plugins_icon() {
296
		$svg = '<svg class="gridicon gridicons-plugins" height="24" width="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 24"><g><path d="M16 8V3c0-.552-.448-1-1-1s-1 .448-1 1v5h-4V3c0-.552-.448-1-1-1s-1 .448-1 1v5H5v4c0 2.79 1.637 5.193 4 6.317V22h6v-3.683c2.363-1.124 4-3.527 4-6.317V8h-3z" fill="black"></path></g></svg>';
297
298
		return 'data:image/svg+xml;base64,' . base64_encode( $svg ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
299
	}
300
301
	/**
302
	 * Returns the Calypso domain that originated the current request.
303
	 *
304
	 * @return string
305
	 */
306
	private function get_calypso_origin() {
307
		$origin  = ! empty( $_GET['origin'] ) ? $_GET['origin'] : 'https://wordpress.com'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
308
		$allowed = array(
309
			'http://calypso.localhost:3000',
310
			'http://127.0.0.1:41050', // Desktop App.
311
			'https://wpcalypso.wordpress.com',
312
			'https://horizon.wordpress.com',
313
			'https://wordpress.com',
314
		);
315
		return in_array( $origin, $allowed, true ) ? $origin : 'https://wordpress.com';
316
	}
317
318
	/**
319
	 * Returns the Calypso URL that displays either the current post type list (if no args
320
	 * are supplied) or the classic editor for the current post (if a post ID is supplied).
321
	 *
322
	 * @param int|null $post_id Post ID.
323
	 *
324
	 * @return string
325
	 */
326
	public function get_calypso_url( $post_id = null ) {
327
		$screen      = get_current_screen();
328
		$post_type   = $screen->post_type;
329
		$site_suffix = ( new Status() )->get_site_suffix();
330
331
		if ( is_null( $post_id ) ) {
332
			// E.g. posts or pages have no special suffix. CPTs are in the `types/{cpt}` format.
333
			$post_type_suffix = ( 'post' === $post_type || 'page' === $post_type )
334
				? "/${post_type}s/"
335
				: "/types/${post_type}/";
336
			$post_suffix      = '';
337
		} else {
338
			$post_type_suffix = ( 'post' === $post_type || 'page' === $post_type )
339
				? "/${post_type}/"
340
				: "/edit/${post_type}/";
341
			$post_suffix      = "/${post_id}";
342
		}
343
344
		return $this->get_calypso_origin() . $post_type_suffix . $site_suffix . $post_suffix;
345
	}
346
347
	/**
348
	 * Returns the URL to be used on the block editor close button for going back to the
349
	 * Calypso post list.
350
	 *
351
	 * @return string
352
	 */
353
	public function get_close_gutenberg_url() {
354
		return $this->get_calypso_url();
355
	}
356
357
	/**
358
	 * Returns the URL for switching the user's editor to the Calypso (WordPress.com Classic) editor.
359
	 *
360
	 * @return string
361
	 */
362
	public function get_switch_to_classic_editor_url() {
363
		return add_query_arg(
364
			'set-editor',
365
			'classic',
366
			$this->is_calypsoify_enabled ? $this->get_calypso_url( get_the_ID() ) : false
367
		);
368
	}
369
370
	/**
371
	 * Checks for the URL parameter if this is a Calypsoify request.
372
	 */
373
	public function check_param() {
374
		if ( isset( $_GET['calypsoify'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
375
			if ( 1 === (int) $_GET['calypsoify'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
376
				update_user_meta( get_current_user_id(), 'calypsoify', 1 );
377
			} else {
378
				update_user_meta( get_current_user_id(), 'calypsoify', 0 );
379
			}
380
		}
381
	}
382
383
	/**
384
	 * If the visitor is hitting wp-admin/ then disable this functionality.
385
	 */
386
	public function check_page() {
387
		// If the user hits plain /wp-admin/ then disable Calypso styles.
388
		$page = wp_basename( esc_url( $_SERVER['REQUEST_URI'] ) );
389
390
		if ( false !== strpos( 'index.php', $page ) || false !== strpos( 'wp-admin', $page ) ) {
391
			update_user_meta( get_current_user_id(), 'calypsoify', 0 );
392
			wp_safe_redirect( admin_url() );
393
			die;
394
		}
395
	}
396
397
	/**
398
	 * Return whether a post type should display the Gutenberg/block editor.
399
	 *
400
	 * @since 6.7.0
401
	 *
402
	 * @param string $post_type Post type.
403
	 */
404
	public function is_post_type_gutenberg( $post_type ) {
405
		return use_block_editor_for_post_type( $post_type );
406
	}
407
408
	/**
409
	 * Determines if the page is an instance of the Gutenberg block editor.
410
	 *
411
	 * @return bool
412
	 */
413
	public function is_page_gutenberg() {
414
		// phpcs:disable WordPress.Security.NonceVerification.Recommended
415
		// Disabling WordPress.Security.NonceVerification.Recommended because this function fires within admin_init and this is only changing display.
416
		$page = wp_basename( esc_url( $_SERVER['REQUEST_URI'] ) );
417
418
		if ( false !== strpos( $page, 'post-new.php' ) && empty( $_GET['post_type'] ) ) {
419
			return true;
420
		}
421
422
		if ( false !== strpos( $page, 'post-new.php' ) && isset( $_GET['post_type'] ) && $this->is_post_type_gutenberg( $_GET['post_type'] ) ) {
423
			return true;
424
		}
425
426 View Code Duplication
		if ( false !== strpos( $page, 'post.php' ) ) {
427
			$post = get_post( $_GET['post'] );
428
			if ( isset( $post ) && isset( $post->post_type ) && $this->is_post_type_gutenberg( $post->post_type ) ) {
429
				return true;
430
			}
431
		}
432
433 View Code Duplication
		if ( false !== strpos( $page, 'revision.php' ) ) {
434
			$post   = get_post( $_GET['revision'] );
435
			$parent = get_post( $post->post_parent );
436
			if ( isset( $parent ) && isset( $parent->post_type ) && $this->is_post_type_gutenberg( $parent->post_type ) ) {
437
				return true;
438
			}
439
		}
440
441
		return false;
442
		// phpcs:enable
443
	}
444
445
	/**
446
	 * Attach a WP_List_Table views filter to all screens.
447
	 *
448
	 * @param WP_Screen $current_screen Current WP_Screen instance.
449
	 */
450
	public function attach_views_filter( $current_screen ) {
451
		add_filter( "views_{$current_screen->id}", array( $this, 'filter_views' ) );
452
	}
453
454
	/**
455
	 * Remove the parentheses from list table view counts when Calypsofied.
456
	 *
457
	 * @param array $views Array of views. See: WP_List_Table::get_views().
458
	 * @return array Filtered views.
459
	 */
460
	public function filter_views( $views ) {
461
		foreach ( $views as $id => $view ) {
462
			$views[ $id ] = preg_replace( '/<span class="count">\((\d+)\)<\/span>/', '<span class="count">$1</span>', $view );
463
		}
464
465
		return $views;
466
	}
467
}
468
469
Jetpack_Calypsoify::get_instance();
470