Completed
Push — branch-7.0-built ( d22ff0...e59732 )
by Jeremy
24:33 queued 16:50
created

Jetpack_React_Page   C

Complexity

Total Complexity 57

Size/Duplication

Total Lines 352
Duplicated Lines 6.53 %

Coupling/Cohesion

Components 1
Dependencies 8

Importance

Changes 0
Metric Value
dl 23
loc 352
rs 5.04
c 0
b 0
f 0
wmc 57
lcom 1
cbo 8

16 Methods

Rating   Name   Duplication   Size   Complexity  
A get_page_hook() 0 4 1
B add_page_actions() 0 24 6
A jetpack_add_dashboard_sub_nav_item() 6 8 4
A jetpack_add_settings_sub_nav_item() 4 6 5
A add_fallback_head_meta() 0 3 1
A add_noscript_head_meta() 0 5 1
A add_legacy_browsers_head_script() 0 10 1
A jetpack_menu_order() 13 13 4
A render_nojs_configurable() 0 13 3
A page_render() 0 25 3
A get_dismissed_jetpack_notices() 0 12 1
A additional_styles() 0 3 1
B page_admin_scripts() 0 32 7
F get_initial_state() 0 131 16
A get_external_services_connect_urls() 0 8 2
A get_flattened_settings() 0 5 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Jetpack_React_Page often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Jetpack_React_Page, and based on these observations, apply Extract Interface, too.

1
<?php
2
include_once( 'class.jetpack-admin-page.php' );
3
4
// Builds the landing page and its menu
5
class Jetpack_React_Page extends Jetpack_Admin_Page {
6
7
	protected $dont_show_if_not_active = false;
8
9
	protected $is_redirecting = false;
10
11
	function get_page_hook() {
12
		// Add the main admin Jetpack menu
13
		return add_menu_page( 'Jetpack', 'Jetpack', 'jetpack_admin_page', 'jetpack', array( $this, 'render' ), 'div' );
14
	}
15
16
	function add_page_actions( $hook ) {
17
		/** This action is documented in class.jetpack.php */
18
		do_action( 'jetpack_admin_menu', $hook );
19
20
		// Place the Jetpack menu item on top and others in the order they appear
21
		add_filter( 'custom_menu_order',         '__return_true' );
22
		add_filter( 'menu_order',                array( $this, 'jetpack_menu_order' ) );
23
24
		if ( ! isset( $_GET['page'] ) || 'jetpack' !== $_GET['page'] || ! empty( $_GET['configure'] ) ) {
25
			return; // No need to handle the fallback redirection if we are not on the Jetpack page
26
		}
27
28
		// Adding a redirect meta tag for older WordPress versions or if the REST API is disabled
29
		if ( $this->is_wp_version_too_old() || ! $this->is_rest_api_enabled() ) {
30
			$this->is_redirecting = true;
31
			add_action( 'admin_head', array( $this, 'add_fallback_head_meta' ) );
32
		}
33
34
		// Adding a redirect meta tag wrapped in noscript tags for all browsers in case they have JavaScript disabled
35
		add_action( 'admin_head', array( $this, 'add_noscript_head_meta' ) );
36
37
		// Adding a redirect tag wrapped in browser conditional comments
38
		add_action( 'admin_head', array( $this, 'add_legacy_browsers_head_script' ) );
39
	}
40
41
	/**
42
	 * Add Jetpack Dashboard sub-link and point it to AAG if the user can view stats, manage modules or if Protect is active.
43
	 *
44
	 * Works in Dev Mode or when user is connected.
45
	 *
46
	 * @since 4.3.0
47
	 */
48
	function jetpack_add_dashboard_sub_nav_item() {
49 View Code Duplication
		if ( Jetpack::is_development_mode() || Jetpack::is_active() ) {
50
			global $submenu;
51
			if ( current_user_can( 'jetpack_admin_page' ) ) {
52
				$submenu['jetpack'][] = array( __( 'Dashboard', 'jetpack' ), 'jetpack_admin_page', 'admin.php?page=jetpack#/dashboard' );
53
			}
54
		}
55
	}
56
57
	/**
58
	 * If user is allowed to see the Jetpack Admin, add Settings sub-link.
59
	 *
60
	 * @since 4.3.0
61
	 */
62
	function jetpack_add_settings_sub_nav_item() {
63 View Code Duplication
		if ( ( Jetpack::is_development_mode() || Jetpack::is_active() ) && current_user_can( 'jetpack_admin_page' ) && current_user_can( 'edit_posts' ) ) {
64
			global $submenu;
65
			$submenu['jetpack'][] = array( __( 'Settings', 'jetpack' ), 'jetpack_admin_page', 'admin.php?page=jetpack#/settings' );
66
		}
67
	}
68
69
	function add_fallback_head_meta() {
70
		echo '<meta http-equiv="refresh" content="0; url=?page=jetpack_modules">';
71
	}
72
73
	function add_noscript_head_meta() {
74
		echo '<noscript>';
75
		$this->add_fallback_head_meta();
76
		echo '</noscript>';
77
	}
78
79
	function add_legacy_browsers_head_script() {
80
		echo
81
			"<script type=\"text/javascript\">\n"
82
			. "/*@cc_on\n"
83
			. "if ( @_jscript_version <= 10) {\n"
84
			. "window.location.href = '?page=jetpack_modules';\n"
85
			. "}\n"
86
			. "@*/\n"
87
			. "</script>";
88
	}
89
90 View Code Duplication
	function jetpack_menu_order( $menu_order ) {
91
		$jp_menu_order = array();
92
93
		foreach ( $menu_order as $index => $item ) {
94
			if ( $item != 'jetpack' )
95
				$jp_menu_order[] = $item;
96
97
			if ( $index == 0 )
98
				$jp_menu_order[] = 'jetpack';
99
		}
100
101
		return $jp_menu_order;
102
	}
103
104
	// Render the configuration page for the module if it exists and an error
105
	// screen if the module is not configurable
106
	// @todo remove when real settings are in place
107
	function render_nojs_configurable( $module_name ) {
108
		$module_name = preg_replace( '/[^\da-z\-]+/', '', $_GET['configure'] );
109
110
		echo '<div class="wrap configure-module">';
111
112
		if ( Jetpack::is_module( $module_name ) && current_user_can( 'jetpack_configure_modules' ) ) {
113
			Jetpack::admin_screen_configure_module( $module_name );
114
		} else {
115
			echo '<h2>' . esc_html__( 'Error, bad module.', 'jetpack' ) . '</h2>';
116
		}
117
118
		echo '</div><!-- /wrap -->';
119
	}
120
121
	function page_render() {
122
		// Handle redirects to configuration pages
123
		if ( ! empty( $_GET['configure'] ) ) {
124
			return $this->render_nojs_configurable( $_GET['configure'] );
125
		}
126
127
		/** This action is already documented in views/admin/admin-page.php */
128
		do_action( 'jetpack_notices' );
129
130
		// Try fetching by patch
131
		$static_html = @file_get_contents( JETPACK__PLUGIN_DIR . '_inc/build/static.html' );
132
133
		if ( false === $static_html ) {
134
135
			// If we still have nothing, display an error
136
			echo '<p>';
137
			esc_html_e( 'Error fetching static.html. Try running: ', 'jetpack' );
138
			echo '<code>yarn distclean && yarn build</code>';
139
			echo '</p>';
140
		} else {
141
142
			// We got the static.html so let's display it
143
			echo $static_html;
144
		}
145
	}
146
147
	/**
148
	 * Gets array of any Jetpack notices that have been dismissed.
149
	 *
150
	 * @since 4.0.1
151
	 * @return mixed|void
152
	 */
153
	function get_dismissed_jetpack_notices() {
154
		$jetpack_dismissed_notices = get_option( 'jetpack_dismissed_notices', array() );
155
		/**
156
		 * Array of notices that have been dismissed.
157
		 *
158
		 * @since 4.0.1
159
		 *
160
		 * @param array $jetpack_dismissed_notices If empty, will not show any Jetpack notices.
161
		 */
162
		$dismissed_notices = apply_filters( 'jetpack_dismissed_notices', $jetpack_dismissed_notices );
163
		return $dismissed_notices;
164
	}
165
166
	function additional_styles() {
167
		Jetpack_Admin_Page::load_wrapper_styles();
168
	}
169
170
	function page_admin_scripts() {
171
		if ( $this->is_redirecting || isset( $_GET['configure'] ) ) {
172
			return; // No need for scripts on a fallback page.
173
		}
174
175
		// Enqueue jp.js and localize it.
176
		$dependencies = array();
177
178
		// WordPress 5.0 and later supports the new client side i18n mechanism.
179
		if ( version_compare( $GLOBALS['wp_version'], '5.0', '>' ) ) {
180
			$dependencies = array( 'wp-i18n' );
181
		}
182
		wp_enqueue_script(
183
			'react-plugin',
184
			plugins_url( '_inc/build/admin.js', JETPACK__PLUGIN_FILE ),
185
			$dependencies,
186
			JETPACK__VERSION,
187
			true
188
		);
189
190
		if ( function_exists( 'wp_set_script_translations' ) ) {
191
			wp_set_script_translations( 'react-plugin', 'jetpack', JETPACK__PLUGIN_DIR . 'languages/json' );
192
		}
193
194
		if ( ! Jetpack::is_development_mode() && Jetpack::is_active() ) {
195
			// Required for Analytics.
196
			wp_enqueue_script( 'jp-tracks', '//stats.wp.com/w.js', array(), gmdate( 'YW' ), true );
197
		}
198
199
		// Add objects to be passed to the initial state of the app.
200
		wp_localize_script( 'react-plugin', 'Initial_State', $this->get_initial_state() );
201
	}
202
203
	function get_initial_state() {
204
		// Load API endpoint base classes and endpoints for getting the module list fed into the JS Admin Page
205
		require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-xmlrpc-consumer-endpoint.php';
206
		require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-module-endpoints.php';
207
		$moduleListEndpoint = new Jetpack_Core_API_Module_List_Endpoint();
208
		$modules = $moduleListEndpoint->get_modules();
209
210
		// Preparing translated fields for JSON encoding by transforming all HTML entities to
211
		// respective characters.
212
		foreach( $modules as $slug => $data ) {
0 ignored issues
show
Bug introduced by
The expression $modules of type string|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
213
			$modules[ $slug ]['name'] = html_entity_decode( $data['name'] );
214
			$modules[ $slug ]['description'] = html_entity_decode( $data['description'] );
215
			$modules[ $slug ]['short_description'] = html_entity_decode( $data['short_description'] );
216
			$modules[ $slug ]['long_description'] = html_entity_decode( $data['long_description'] );
217
		}
218
219
		// Collecting roles that can view site stats.
220
		$stats_roles = array();
221
		$enabled_roles = function_exists( 'stats_get_option' ) ? stats_get_option( 'roles' ) : array( 'administrator' );
222
223
		if ( ! function_exists( 'get_editable_roles' ) ) {
224
			require_once ABSPATH . 'wp-admin/includes/user.php';
225
		}
226
		foreach ( get_editable_roles() as $slug => $role ) {
227
			$stats_roles[ $slug ] = array(
228
				'name' => translate_user_role( $role['name'] ),
229
				'canView' => is_array( $enabled_roles ) ? in_array( $slug, $enabled_roles, true ) : false,
230
			);
231
		}
232
233
		// Get information about current theme.
234
		$current_theme = wp_get_theme();
235
236
		// Get all themes that Infinite Scroll provides support for natively.
237
		$inf_scr_support_themes = array();
238
		foreach ( Jetpack::glob_php( JETPACK__PLUGIN_DIR . 'modules/infinite-scroll/themes' ) as $path ) {
239
			if ( is_readable( $path ) ) {
240
				$inf_scr_support_themes[] = basename( $path, '.php' );
241
			}
242
		}
243
244
		// Get last post, to build the link to Customizer in the Related Posts module.
245
		$last_post = get_posts( array( 'posts_per_page' => 1 ) );
246
		$last_post = isset( $last_post[0] ) && $last_post[0] instanceof WP_Post
0 ignored issues
show
Bug introduced by
The class WP_Post does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
247
			? get_permalink( $last_post[0]->ID )
248
			: get_home_url();
249
250
		// Ensure that class to get the affiliate code is loaded
251
		if ( ! class_exists( 'Jetpack_Affiliate' ) ) {
252
			require_once JETPACK__PLUGIN_DIR . 'class.jetpack-affiliate.php';
253
		}
254
255
		return array(
256
			'WP_API_root' => esc_url_raw( rest_url() ),
257
			'WP_API_nonce' => wp_create_nonce( 'wp_rest' ),
258
			'pluginBaseUrl' => plugins_url( '', JETPACK__PLUGIN_FILE ),
259
			'connectionStatus' => array(
260
				'isActive'  => Jetpack::is_active(),
261
				'isStaging' => Jetpack::is_staging_site(),
262
				'devMode'   => array(
263
					'isActive' => Jetpack::is_development_mode(),
264
					'constant' => defined( 'JETPACK_DEV_DEBUG' ) && JETPACK_DEV_DEBUG,
265
					'url'      => site_url() && false === strpos( site_url(), '.' ),
266
					'filter'   => apply_filters( 'jetpack_development_mode', false ),
267
				),
268
				'isPublic'	=> '1' == get_option( 'blog_public' ),
269
				'isInIdentityCrisis' => Jetpack::validate_sync_error_idc_option(),
270
				'sandboxDomain' => JETPACK__SANDBOX_DOMAIN,
271
			),
272
			'connectUrl' => Jetpack::init()->build_connect_url( true, false, false ),
273
			'dismissedNotices' => $this->get_dismissed_jetpack_notices(),
274
			'isDevVersion' => Jetpack::is_development_version(),
275
			'currentVersion' => JETPACK__VERSION,
276
			'is_gutenberg_available' => Jetpack_Gutenberg::is_gutenberg_available(),
277
			'getModules' => $modules,
278
			'showJumpstart' => jetpack_show_jumpstart(),
279
			'rawUrl' => Jetpack::build_raw_urls( get_home_url() ),
280
			'adminUrl' => esc_url( admin_url() ),
281
			'stats' => array(
282
				// data is populated asynchronously on page load
283
				'data'  => array(
284
					'general' => false,
285
					'day'     => false,
286
					'week'    => false,
287
					'month'   => false,
288
				),
289
				'roles' => $stats_roles,
290
			),
291
			'aff' => Jetpack_Affiliate::init()->get_affiliate_code(),
292
			'settings' => $this->get_flattened_settings( $modules ),
0 ignored issues
show
Bug introduced by
It seems like $modules defined by $moduleListEndpoint->get_modules() on line 208 can also be of type string; however, Jetpack_React_Page::get_flattened_settings() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
293
			'userData' => array(
294
//				'othersLinked' => Jetpack::get_other_linked_admins(),
295
				'currentUser'  => jetpack_current_user_data(),
296
			),
297
			'siteData' => array(
298
				'icon' => has_site_icon()
299
					? apply_filters( 'jetpack_photon_url', get_site_icon_url(), array( 'w' => 64 ) )
300
					: '',
301
				'siteVisibleToSearchEngines' => '1' == get_option( 'blog_public' ),
302
				/**
303
				 * Whether promotions are visible or not.
304
				 *
305
				 * @since 4.8.0
306
				 *
307
				 * @param bool $are_promotions_active Status of promotions visibility. True by default.
308
				 */
309
				'showPromotions' => apply_filters( 'jetpack_show_promotions', true ),
310
				'isAtomicSite' => jetpack_is_atomic_site(),
311
				'plan' => Jetpack::get_active_plan(),
312
				'showBackups' => Jetpack::show_backups_ui(),
313
			),
314
			'themeData' => array(
315
				'name'      => $current_theme->get( 'Name' ),
316
				'hasUpdate' => (bool) get_theme_update_available( $current_theme ),
317
				'support'   => array(
318
					'infinite-scroll' => current_theme_supports( 'infinite-scroll' ) || in_array( $current_theme->get_stylesheet(), $inf_scr_support_themes ),
319
				),
320
			),
321
			'locale' => Jetpack::get_i18n_data_json(),
322
			'localeSlug' => join( '-', explode( '_', get_user_locale() ) ),
323
			'jetpackStateNotices' => array(
324
				'messageCode' => Jetpack::state( 'message' ),
325
				'errorCode' => Jetpack::state( 'error' ),
326
				'errorDescription' => Jetpack::state( 'error_description' ),
327
			),
328
			'tracksUserData' => Jetpack_Tracks_Client::get_connected_user_tracks_identity(),
329
			'currentIp' => function_exists( 'jetpack_protect_get_ip' ) ? jetpack_protect_get_ip() : false,
330
			'lastPostUrl' => esc_url( $last_post ),
331
			'externalServicesConnectUrls' => $this->get_external_services_connect_urls()
332
		);
333
	}
334
335
	function get_external_services_connect_urls() {
336
		$connect_urls = array();
337
		jetpack_require_lib( 'class.jetpack-keyring-service-helper' );
338
		foreach ( Jetpack_Keyring_Service_Helper::$SERVICES as $service_name => $service_info ) {
339
			$connect_urls[ $service_name ] = Jetpack_Keyring_Service_Helper::connect_url( $service_name, $service_info[ 'for' ] );
340
		}
341
		return $connect_urls;
342
	}
343
344
	/**
345
	 * Returns an array of modules and settings both as first class members of the object.
346
	 *
347
	 * @param array $modules the result of an API request to get all modules.
348
	 *
349
	 * @return array flattened settings with modules.
350
	 */
351
	function get_flattened_settings( $modules ) {
352
		$core_api_endpoint = new Jetpack_Core_API_Data();
353
		$settings = $core_api_endpoint->get_all_options();
354
		return $settings->data;
355
	}
356
}
357
358
/*
359
 * Only show Jump Start on first activation.
360
 * Any option 'jumpstart' other than 'new connection' will hide it.
361
 *
362
 * The option can be of 4 things, and will be stored as such:
363
 * new_connection      : Brand new connection - Show
364
 * jumpstart_activated : Jump Start has been activated - dismiss
365
 * jetpack_action_taken: Manual activation of a module already happened - dismiss
366
 * jumpstart_dismissed : Manual dismissal of Jump Start - dismiss
367
 *
368
 * @todo move to functions.global.php when available
369
 * @since 3.6
370
 * @return bool | show or hide
371
 */
372
function jetpack_show_jumpstart() {
373
	if ( ! Jetpack::is_active() ) {
374
		return false;
375
	}
376
	$jumpstart_option = Jetpack_Options::get_option( 'jumpstart' );
377
378
	$hide_options = array(
379
		'jumpstart_activated',
380
		'jetpack_action_taken',
381
		'jumpstart_dismissed'
382
	);
383
384
	if ( ! $jumpstart_option || in_array( $jumpstart_option, $hide_options ) ) {
385
		return false;
386
	}
387
388
	return true;
389
}
390
391
/**
392
 * Gather data about the current user.
393
 *
394
 * @since 4.1.0
395
 *
396
 * @return array
397
 */
398
function jetpack_current_user_data() {
399
	$current_user = wp_get_current_user();
400
	$is_master_user = $current_user->ID == Jetpack_Options::get_option( 'master_user' );
401
	$dotcom_data    = Jetpack::get_connected_user_data();
402
	// Add connected user gravatar to the returned dotcom_data.
403
	$dotcom_data['avatar'] = get_avatar_url( $dotcom_data['email'], array( 'size' => 64, 'default' => 'mysteryman' ) );
404
405
	$current_user_data = array(
406
		'isConnected' => Jetpack::is_user_connected( $current_user->ID ),
407
		'isMaster'    => $is_master_user,
408
		'username'    => $current_user->user_login,
409
		'id'          => $current_user->ID,
410
		'wpcomUser'   => $dotcom_data,
411
		'gravatar'    => get_avatar( $current_user->ID, 40, 'mm', '', array( 'force_display' => true ) ),
412
		'permissions' => array(
413
			'admin_page'         => current_user_can( 'jetpack_admin_page' ),
414
			'connect'            => current_user_can( 'jetpack_connect' ),
415
			'disconnect'         => current_user_can( 'jetpack_disconnect' ),
416
			'manage_modules'     => current_user_can( 'jetpack_manage_modules' ),
417
			'network_admin'      => current_user_can( 'jetpack_network_admin_page' ),
418
			'network_sites_page' => current_user_can( 'jetpack_network_sites_page' ),
419
			'edit_posts'         => current_user_can( 'edit_posts' ),
420
			'publish_posts'      => current_user_can( 'publish_posts' ),
421
			'manage_options'     => current_user_can( 'manage_options' ),
422
			'view_stats'		 => current_user_can( 'view_stats' ),
423
			'manage_plugins'	 => current_user_can( 'install_plugins' )
424
									&& current_user_can( 'activate_plugins' )
425
									&& current_user_can( 'update_plugins' )
426
									&& current_user_can( 'delete_plugins' ),
427
		),
428
	);
429
430
	return $current_user_data;
431
}
432