Completed
Push — fix/7357 ( 13a0c4...764bff )
by
unknown
11:52
created

_inc/lib/admin-pages/class.jetpack-react-page.php (2 issues)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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
		$title = _x( 'Jetpack', 'The menu item label', 'jetpack' );
13
14
		// Add the main admin Jetpack menu
15
		return add_menu_page( 'Jetpack', $title, 'jetpack_admin_page', 'jetpack', array( $this, 'render' ), 'div' );
16
	}
17
18
	function add_page_actions( $hook ) {
19
		/** This action is documented in class.jetpack.php */
20
		do_action( 'jetpack_admin_menu', $hook );
21
22
		// Place the Jetpack menu item on top and others in the order they appear
23
		add_filter( 'custom_menu_order',         '__return_true' );
24
		add_filter( 'menu_order',                array( $this, 'jetpack_menu_order' ) );
25
26
		if ( ! isset( $_GET['page'] ) || 'jetpack' !== $_GET['page'] || ! empty( $_GET['configure'] ) ) {
27
			return; // No need to handle the fallback redirection if we are not on the Jetpack page
28
		}
29
30
		// Adding a redirect meta tag for older WordPress versions or if the REST API is disabled
31
		if ( $this->is_wp_version_too_old() || ! $this->is_rest_api_enabled() ) {
32
			$this->is_redirecting = true;
33
			add_action( 'admin_head', array( $this, 'add_fallback_head_meta' ) );
34
		}
35
36
		// Adding a redirect meta tag wrapped in noscript tags for all browsers in case they have JavaScript disabled
37
		add_action( 'admin_head', array( $this, 'add_noscript_head_meta' ) );
38
39
		// Adding a redirect tag wrapped in browser conditional comments
40
		add_action( 'admin_head', array( $this, 'add_legacy_browsers_head_script' ) );
41
	}
42
43
	/**
44
	 * Add Jetpack Dashboard sub-link and point it to AAG if the user can view stats, manage modules or if Protect is active.
45
	 *
46
	 * Works in Dev Mode or when user is connected.
47
	 *
48
	 * @since 4.3.0
49
	 */
50
	function jetpack_add_dashboard_sub_nav_item() {
51 View Code Duplication
		if ( Jetpack::is_development_mode() || Jetpack::is_active() ) {
52
			global $submenu;
53
			if ( current_user_can( 'jetpack_admin_page' ) ) {
54
				$submenu['jetpack'][] = array( __( 'Dashboard', 'jetpack' ), 'jetpack_admin_page', 'admin.php?page=jetpack#/dashboard' );
55
			}
56
		}
57
	}
58
59
	/**
60
	 * If user is allowed to see the Jetpack Admin, add Settings sub-link.
61
	 *
62
	 * @since 4.3.0
63
	 */
64
	function jetpack_add_settings_sub_nav_item() {
65 View Code Duplication
		if ( ( Jetpack::is_development_mode() || Jetpack::is_active() ) && current_user_can( 'jetpack_admin_page' ) && current_user_can( 'edit_posts' ) ) {
66
			global $submenu;
67
			$submenu['jetpack'][] = array( __( 'Settings', 'jetpack' ), 'jetpack_admin_page', 'admin.php?page=jetpack#/settings' );
68
		}
69
	}
70
71
	function add_fallback_head_meta() {
72
		echo '<meta http-equiv="refresh" content="0; url=?page=jetpack_modules">';
73
	}
74
75
	function add_noscript_head_meta() {
76
		echo '<noscript>';
77
		$this->add_fallback_head_meta();
78
		echo '</noscript>';
79
	}
80
81
	function add_legacy_browsers_head_script() {
82
		echo
83
			"<script type=\"text/javascript\">\n"
84
			. "/*@cc_on\n"
85
			. "if ( @_jscript_version <= 10) {\n"
86
			. "window.location.href = '?page=jetpack_modules';\n"
87
			. "}\n"
88
			. "@*/\n"
89
			. "</script>";
90
	}
91
92 View Code Duplication
	function jetpack_menu_order( $menu_order ) {
93
		$jp_menu_order = array();
94
95
		foreach ( $menu_order as $index => $item ) {
96
			if ( $item != 'jetpack' )
97
				$jp_menu_order[] = $item;
98
99
			if ( $index == 0 )
100
				$jp_menu_order[] = 'jetpack';
101
		}
102
103
		return $jp_menu_order;
104
	}
105
106
	// Render the configuration page for the module if it exists and an error
107
	// screen if the module is not configurable
108
	// @todo remove when real settings are in place
109
	function render_nojs_configurable( $module_name ) {
110
		$module_name = preg_replace( '/[^\da-z\-]+/', '', $_GET['configure'] );
111
112
		include_once( JETPACK__PLUGIN_DIR . '_inc/header.php' );
113
		echo '<div class="wrap configure-module">';
114
115
		if ( Jetpack::is_module( $module_name ) && current_user_can( 'jetpack_configure_modules' ) ) {
116
			Jetpack::admin_screen_configure_module( $module_name );
117
		} else {
118
			echo '<h2>' . esc_html__( 'Error, bad module.', 'jetpack' ) . '</h2>';
119
		}
120
121
		echo '</div><!-- /wrap -->';
122
	}
123
124
	function page_render() {
125
		// Handle redirects to configuration pages
126
		if ( ! empty( $_GET['configure'] ) ) {
127
			return $this->render_nojs_configurable( $_GET['configure'] );
128
		}
129
130
		/** This action is already documented in views/admin/admin-page.php */
131
		do_action( 'jetpack_notices' );
132
133
		// Try fetching by patch
134
		$static_html = @file_get_contents( JETPACK__PLUGIN_DIR . '_inc/build/static.html' );
135
136 View Code Duplication
		if ( false === $static_html ) {
137
138
			// If we still have nothing, display an error
139
			echo '<p>';
140
			esc_html_e( 'Error fetching static.html. Try running: ', 'jetpack' );
141
			echo '<code>yarn distclean && yarn build</code>';
142
			echo '</p>';
143
		} else {
144
145
			// We got the static.html so let's display it
146
			echo $static_html;
147
		}
148
	}
149
150
	function get_i18n_data() {
151
152
		$i18n_json = JETPACK__PLUGIN_DIR . 'languages/json/jetpack-' . jetpack_get_user_locale() . '.json';
153
154
		if ( is_file( $i18n_json ) && is_readable( $i18n_json ) ) {
155
			$locale_data = @file_get_contents( $i18n_json );
156
			if ( $locale_data ) {
157
				return $locale_data;
158
			}
159
		}
160
161
		// Return empty if we have nothing to return so it doesn't fail when parsed in JS
162
		return '{}';
163
	}
164
165
	/**
166
	 * Gets array of any Jetpack notices that have been dismissed.
167
	 *
168
	 * @since 4.0.1
169
	 * @return mixed|void
170
	 */
171
	function get_dismissed_jetpack_notices() {
172
		$jetpack_dismissed_notices = get_option( 'jetpack_dismissed_notices', array() );
173
		/**
174
		 * Array of notices that have been dismissed.
175
		 *
176
		 * @since 4.0.1
177
		 *
178
		 * @param array $jetpack_dismissed_notices If empty, will not show any Jetpack notices.
179
		 */
180
		$dismissed_notices = apply_filters( 'jetpack_dismissed_notices', $jetpack_dismissed_notices );
181
		return $dismissed_notices;
182
	}
183
184
	function additional_styles() {
185
		$rtl = is_rtl() ? '.rtl' : '';
186
187
		wp_enqueue_style( 'dops-css', plugins_url( "_inc/build/admin.dops-style$rtl.css", JETPACK__PLUGIN_FILE ), array(), JETPACK__VERSION );
188
		wp_enqueue_style( 'components-css', plugins_url( "_inc/build/style.min$rtl.css", JETPACK__PLUGIN_FILE ), array(), JETPACK__VERSION );
189
	}
190
191
	function page_admin_scripts() {
192
		if ( $this->is_redirecting ) {
193
			return; // No need for scripts on a fallback page
194
		}
195
196
		$is_dev_mode = Jetpack::is_development_mode();
197
198
		// Enqueue jp.js and localize it
199
		wp_enqueue_script( 'react-plugin', plugins_url( '_inc/build/admin.js', JETPACK__PLUGIN_FILE ), array(), JETPACK__VERSION, true );
200
201
		if ( ! $is_dev_mode && Jetpack::is_active() ) {
202
			// Required for Analytics
203
			wp_enqueue_script( 'jp-tracks', '//stats.wp.com/w.js', array(), gmdate( 'YW' ), true );
204
		}
205
206
		// Collecting roles that can view site stats.
207
		$stats_roles = array();
208
		$enabled_roles = function_exists( 'stats_get_option' ) ? stats_get_option( 'roles' ) : array( 'administrator' );
209
210
		if ( ! function_exists( 'get_editable_roles' ) ) {
211
			require_once ABSPATH . 'wp-admin/includes/user.php';
212
		}
213
		foreach ( get_editable_roles() as $slug => $role ) {
214
			$stats_roles[ $slug ] = array(
215
				'name' => translate_user_role( $role['name'] ),
216
				'canView' => is_array( $enabled_roles ) ? in_array( $slug, $enabled_roles, true ) : false,
217
			);
218
		}
219
220
		// Load API endpoint base classes and endpoints for getting the module list fed into the JS Admin Page
221
		require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-xmlrpc-consumer-endpoint.php';
222
		require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-module-endpoints.php';
223
		$moduleListEndpoint = new Jetpack_Core_API_Module_List_Endpoint();
224
		$modules = $moduleListEndpoint->get_modules();
225
226
		// Preparing translated fields for JSON encoding by transforming all HTML entities to
227
		// respective characters.
228
		foreach( $modules as $slug => $data ) {
0 ignored issues
show
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...
229
			$modules[ $slug ]['name'] = html_entity_decode( $data['name'] );
230
			$modules[ $slug ]['description'] = html_entity_decode( $data['description'] );
231
			$modules[ $slug ]['short_description'] = html_entity_decode( $data['short_description'] );
232
			$modules[ $slug ]['long_description'] = html_entity_decode( $data['long_description'] );
233
		}
234
235
		// Get last post, to build the link to Customizer in the Related Posts module.
236
		$last_post = get_posts( array( 'posts_per_page' => 1 ) );
237
		$last_post = isset( $last_post[0] ) && $last_post[0] instanceof WP_Post
238
			? get_permalink( $last_post[0]->ID )
239
			: get_home_url();
240
241
		// Get information about current theme.
242
		$current_theme = wp_get_theme();
243
244
		// Get all themes that Infinite Scroll provides support for natively.
245
		$inf_scr_support_themes = array();
246
		foreach ( Jetpack::glob_php( JETPACK__PLUGIN_DIR . 'modules/infinite-scroll/themes' ) as $path ) {
247
			if ( is_readable( $path ) ) {
248
				$inf_scr_support_themes[] = basename( $path, '.php' );
249
			}
250
		}
251
252
		// Add objects to be passed to the initial state of the app
253
		wp_localize_script( 'react-plugin', 'Initial_State', array(
254
			'WP_API_root' => esc_url_raw( rest_url() ),
255
			'WP_API_nonce' => wp_create_nonce( 'wp_rest' ),
256
			'pluginBaseUrl' => plugins_url( '', JETPACK__PLUGIN_FILE ),
257
			'connectionStatus' => array(
258
				'isActive'  => Jetpack::is_active(),
259
				'isStaging' => Jetpack::is_staging_site(),
260
				'devMode'   => array(
261
					'isActive' => $is_dev_mode,
262
					'constant' => defined( 'JETPACK_DEV_DEBUG' ) && JETPACK_DEV_DEBUG,
263
					'url'      => site_url() && false === strpos( site_url(), '.' ),
264
					'filter'   => apply_filters( 'jetpack_development_mode', false ),
265
				),
266
				'isPublic'	=> '1' == get_option( 'blog_public' ),
267
				'isInIdentityCrisis' => Jetpack::validate_sync_error_idc_option(),
268
			),
269
			'connectUrl' => Jetpack::init()->build_connect_url( true, false, false ),
270
			'dismissedNotices' => $this->get_dismissed_jetpack_notices(),
271
			'isDevVersion' => Jetpack::is_development_version(),
272
			'currentVersion' => JETPACK__VERSION,
273
			'getModules' => $modules,
274
			'showJumpstart' => jetpack_show_jumpstart(),
275
			'showHolidaySnow' => function_exists( 'jetpack_show_holiday_snow_option' ) ? jetpack_show_holiday_snow_option() : false,
276
			'rawUrl' => Jetpack::build_raw_urls( get_home_url() ),
277
			'adminUrl' => esc_url( admin_url() ),
278
			'stats' => array(
279
				// data is populated asynchronously on page load
280
				'data'  => array(
281
					'general' => false,
282
					'day'     => false,
283
					'week'    => false,
284
					'month'   => false,
285
				),
286
				'roles' => $stats_roles,
287
			),
288
			'settings' => $this->get_flattened_settings( $modules ),
0 ignored issues
show
It seems like $modules defined by $moduleListEndpoint->get_modules() on line 224 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...
289
			'settingNames' => array(
290
				'jetpack_holiday_snow_enabled' => function_exists( 'jetpack_holiday_snow_option_name' ) ? jetpack_holiday_snow_option_name() : false,
291
			),
292
			'userData' => array(
293
//				'othersLinked' => Jetpack::get_other_linked_admins(),
294
				'currentUser'  => jetpack_current_user_data(),
295
			),
296
			'siteData' => array(
297
				'icon' => has_site_icon()
298
					? apply_filters( 'jetpack_photon_url', get_site_icon_url(), array( 'w' => 64 ) )
299
					: '',
300
				'siteVisibleToSearchEngines' => '1' == get_option( 'blog_public' ),
301
				/**
302
				 * Whether promotions are visible or not.
303
				 *
304
				 * @since 4.8.0
305
				 *
306
				 * @param bool $are_promotions_active Status of promotions visibility. True by default.
307
				 */
308
				'showPromotions' => apply_filters( 'jetpack_show_promotions', true ),
309
				'isAtomicSite' => jetpack_is_atomic_site(),
310
			),
311
			'themeData' => array(
312
				'name'      => $current_theme->get( 'Name' ),
313
				'hasUpdate' => (bool) get_theme_update_available( $current_theme ),
314
				'support'   => array(
315
					'infinite-scroll' => current_theme_supports( 'infinite-scroll' ) || in_array( $current_theme->get_stylesheet(), $inf_scr_support_themes ),
316
				),
317
			),
318
			'locale' => $this->get_i18n_data(),
319
			'localeSlug' => join( '-', explode( '_', jetpack_get_user_locale() ) ),
320
			'jetpackStateNotices' => array(
321
				'messageCode' => Jetpack::state( 'message' ),
322
				'errorCode' => Jetpack::state( 'error' ),
323
				'errorDescription' => Jetpack::state( 'error_description' ),
324
			),
325
			'tracksUserData' => Jetpack_Tracks_Client::get_connected_user_tracks_identity(),
326
			'currentIp' => function_exists( 'jetpack_protect_get_ip' ) ? jetpack_protect_get_ip() : false,
327
			'lastPostUrl' => esc_url( $last_post ),
328
		) );
329
	}
330
331
	/**
332
	 * Returns an array of modules and settings both as first class members of the object.
333
	 *
334
	 * @param array $modules the result of an API request to get all modules.
335
	 *
336
	 * @return array flattened settings with modules.
337
	 */
338
	function get_flattened_settings( $modules ) {
339
		$core_api_endpoint = new Jetpack_Core_API_Data();
340
		$settings = $core_api_endpoint->get_all_options();
341
		return $settings->data;
342
	}
343
}
344
345
/*
346
 * Only show Jump Start on first activation.
347
 * Any option 'jumpstart' other than 'new connection' will hide it.
348
 *
349
 * The option can be of 4 things, and will be stored as such:
350
 * new_connection      : Brand new connection - Show
351
 * jumpstart_activated : Jump Start has been activated - dismiss
352
 * jetpack_action_taken: Manual activation of a module already happened - dismiss
353
 * jumpstart_dismissed : Manual dismissal of Jump Start - dismiss
354
 *
355
 * @todo move to functions.global.php when available
356
 * @since 3.6
357
 * @return bool | show or hide
358
 */
359
function jetpack_show_jumpstart() {
360
	if ( ! Jetpack::is_active() ) {
361
		return false;
362
	}
363
	$jumpstart_option = Jetpack_Options::get_option( 'jumpstart' );
364
365
	$hide_options = array(
366
		'jumpstart_activated',
367
		'jetpack_action_taken',
368
		'jumpstart_dismissed'
369
	);
370
371
	if ( ! $jumpstart_option || in_array( $jumpstart_option, $hide_options ) ) {
372
		return false;
373
	}
374
375
	return true;
376
}
377
378
/**
379
 * Gather data about the current user.
380
 *
381
 * @since 4.1.0
382
 *
383
 * @return array
384
 */
385
function jetpack_current_user_data() {
386
	$current_user = wp_get_current_user();
387
	$is_master_user = $current_user->ID == Jetpack_Options::get_option( 'master_user' );
388
	$dotcom_data    = Jetpack::get_connected_user_data();
389
	// Add connected user gravatar to the returned dotcom_data.
390
	$dotcom_data['avatar'] = get_avatar_url( $dotcom_data['email'], array( 'size' => 64, 'default' => 'mysteryman' ) );
391
392
	$current_user_data = array(
393
		'isConnected' => Jetpack::is_user_connected( $current_user->ID ),
394
		'isMaster'    => $is_master_user,
395
		'username'    => $current_user->user_login,
396
		'wpcomUser'   => $dotcom_data,
397
		'gravatar'    => get_avatar( $current_user->ID, 40, 'mm', '', array( 'force_display' => true ) ),
398
		'permissions' => array(
399
			'admin_page'         => current_user_can( 'jetpack_admin_page' ),
400
			'connect'            => current_user_can( 'jetpack_connect' ),
401
			'disconnect'         => current_user_can( 'jetpack_disconnect' ),
402
			'manage_modules'     => current_user_can( 'jetpack_manage_modules' ),
403
			'network_admin'      => current_user_can( 'jetpack_network_admin_page' ),
404
			'network_sites_page' => current_user_can( 'jetpack_network_sites_page' ),
405
			'edit_posts'         => current_user_can( 'edit_posts' ),
406
			'publish_posts'      => current_user_can( 'publish_posts' ),
407
			'manage_options'     => current_user_can( 'manage_options' ),
408
			'view_stats'		 => current_user_can( 'view_stats' ),
409
			'manage_plugins'	 => current_user_can( 'install_plugins' )
410
									&& current_user_can( 'activate_plugins' )
411
									&& current_user_can( 'update_plugins' )
412
									&& current_user_can( 'delete_plugins' ),
413
		),
414
	);
415
416
	return $current_user_data;
417
}
418
419
/**
420
 * Set the admin language, based on user language.
421
 *
422
 * @since 4.5.0
423
 *
424
 * @return string
425
 *
426
 * @todo Remove this function when WordPress 4.8 is released
427
 * and replace `jetpack_get_user_locale()` in this file with `get_user_locale()`.
428
 */
429
function jetpack_get_user_locale() {
430
	$locale = get_locale();
431
432
	if ( function_exists( 'get_user_locale' ) ) {
433
		$locale = get_user_locale();
434
	}
435
436
	return $locale;
437
}
438