Completed
Push — update/react-page-redux-state-... ( c4bec2 )
by
unknown
48:27 queued 38:22
created

get_update_modal_data()   B

Complexity

Conditions 6
Paths 10

Size

Total Lines 52

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
nc 10
nop 0
dl 0
loc 52
rs 8.425
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * A utility class that generates the initial state for Redux in wp-admin.
4
 * Modularized from `class.jetpack-react-page.php`.
5
 *
6
 * @package automattic/jetpack
7
 */
8
9
use Automattic\Jetpack\Connection\REST_Connector;
10
use Automattic\Jetpack\Constants;
11
use Automattic\Jetpack\Device_Detection\User_Agent_Info;
12
use Automattic\Jetpack\Licensing;
13
use Automattic\Jetpack\Partner;
14
use Automattic\Jetpack\Status;
15
16
/**
17
 * Responsible for populating the initial Redux state.
18
 */
19
class Jetpack_Redux_State_Helper {
20
	/**
21
	 * Generate the initial state array to be used by the Redux store.
22
	 */
23
	public static function get_initial_state() {
24
		global $is_safari;
25
26
		// Load API endpoint base classes and endpoints for getting the module list fed into the JS Admin Page.
27
		require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-xmlrpc-consumer-endpoint.php';
28
		require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-module-endpoints.php';
29
		$module_list_endpoint = new Jetpack_Core_API_Module_List_Endpoint();
30
		$modules              = $module_list_endpoint->get_modules();
31
32
		// Preparing translated fields for JSON encoding by transforming all HTML entities to
33
		// respective characters.
34
		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...
35
			$modules[ $slug ]['name']              = html_entity_decode( $data['name'] );
36
			$modules[ $slug ]['description']       = html_entity_decode( $data['description'] );
37
			$modules[ $slug ]['short_description'] = html_entity_decode( $data['short_description'] );
38
			$modules[ $slug ]['long_description']  = html_entity_decode( $data['long_description'] );
39
		}
40
41
		// Collecting roles that can view site stats.
42
		$stats_roles   = array();
43
		$enabled_roles = function_exists( 'stats_get_option' ) ? stats_get_option( 'roles' ) : array( 'administrator' );
44
45
		if ( ! function_exists( 'get_editable_roles' ) ) {
46
			require_once ABSPATH . 'wp-admin/includes/user.php';
47
		}
48
		foreach ( get_editable_roles() as $slug => $role ) {
49
			$stats_roles[ $slug ] = array(
50
				'name'    => translate_user_role( $role['name'] ),
51
				'canView' => is_array( $enabled_roles ) ? in_array( $slug, $enabled_roles, true ) : false,
52
			);
53
		}
54
55
		// Get information about current theme.
56
		$current_theme = wp_get_theme();
57
58
		// Get all themes that Infinite Scroll provides support for natively.
59
		$inf_scr_support_themes = array();
60
		foreach ( Jetpack::glob_php( JETPACK__PLUGIN_DIR . 'modules/infinite-scroll/themes' ) as $path ) {
61
			if ( is_readable( $path ) ) {
62
				$inf_scr_support_themes[] = basename( $path, '.php' );
63
			}
64
		}
65
66
		// Get last post, to build the link to Customizer in the Related Posts module.
67
		$last_post = get_posts( array( 'posts_per_page' => 1 ) );
68
		$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...
69
			? get_permalink( $last_post[0]->ID )
70
			: get_home_url();
71
72
		$current_user_data = jetpack_current_user_data();
73
74
		/**
75
		 * Adds information to the `connectionStatus` API field that is unique to the Jetpack React dashboard.
76
		 */
77
		$connection_status = array(
78
			'isInIdentityCrisis' => Jetpack::validate_sync_error_idc_option(),
79
			'sandboxDomain'      => JETPACK__SANDBOX_DOMAIN,
80
81
			/**
82
			 * Filter to add connection errors
83
			 * Format: array( array( 'code' => '...', 'message' => '...', 'action' => '...' ), ... )
84
			 *
85
			 * @since 8.7.0
86
			 *
87
			 * @param array $errors Connection errors.
88
			 */
89
			'errors'             => apply_filters( 'react_connection_errors_initial_state', array() ),
90
		);
91
92
		$connection_status = array_merge( REST_Connector::connection_status( false ), $connection_status );
93
94
		return array(
95
			'WP_API_root'                 => esc_url_raw( rest_url() ),
96
			'WP_API_nonce'                => wp_create_nonce( 'wp_rest' ),
97
			'pluginBaseUrl'               => plugins_url( '', JETPACK__PLUGIN_FILE ),
98
			'connectionStatus'            => $connection_status,
99
			'connectUrl'                  => false == $current_user_data['isConnected'] // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
100
				? Jetpack::init()->build_connect_url( true, false, false )
101
				: '',
102
			'dismissedNotices'            => self::get_dismissed_jetpack_notices(),
103
			'isDevVersion'                => Jetpack::is_development_version(),
104
			'currentVersion'              => JETPACK__VERSION,
105
			'is_gutenberg_available'      => true,
106
			'getModules'                  => $modules,
107
			'rawUrl'                      => ( new Status() )->get_site_suffix(),
108
			'adminUrl'                    => esc_url( admin_url() ),
109
			'siteTitle'                   => (string) htmlspecialchars_decode( get_option( 'blogname' ), ENT_QUOTES ),
110
			'stats'                       => array(
111
				// data is populated asynchronously on page load.
112
				'data'  => array(
113
					'general' => false,
114
					'day'     => false,
115
					'week'    => false,
116
					'month'   => false,
117
				),
118
				'roles' => $stats_roles,
119
			),
120
			'aff'                         => Partner::init()->get_partner_code( Partner::AFFILIATE_CODE ),
121
			'partnerSubsidiaryId'         => Partner::init()->get_partner_code( Partner::SUBSIDIARY_CODE ),
122
			'settings'                    => self::get_flattened_settings( $modules ),
0 ignored issues
show
Unused Code introduced by
The call to Jetpack_Redux_State_Help...et_flattened_settings() has too many arguments starting with $modules.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
123
			'userData'                    => array(
124
				'currentUser' => $current_user_data,
125
			),
126
			'siteData'                    => array(
127
				'icon'                       => has_site_icon()
128
					? apply_filters( 'jetpack_photon_url', get_site_icon_url(), array( 'w' => 64 ) )
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with array('w' => 64).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
129
					: '',
130
				'siteVisibleToSearchEngines' => '1' == get_option( 'blog_public' ), // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
131
				/**
132
				 * Whether promotions are visible or not.
133
				 *
134
				 * @since 4.8.0
135
				 *
136
				 * @param bool $are_promotions_active Status of promotions visibility. True by default.
137
				 */
138
				'showPromotions'             => apply_filters( 'jetpack_show_promotions', true ),
139
				'isAtomicSite'               => jetpack_is_atomic_site(),
140
				'plan'                       => Jetpack_Plan::get(),
141
				'showBackups'                => Jetpack::show_backups_ui(),
142
				'showRecommendations'        => Jetpack_Recommendations::is_enabled(),
143
				'isMultisite'                => is_multisite(),
144
				'dateFormat'                 => get_option( 'date_format' ),
145
			),
146
			'themeData'                   => array(
147
				'name'      => $current_theme->get( 'Name' ),
148
				'hasUpdate' => (bool) get_theme_update_available( $current_theme ),
149
				'support'   => array(
150
					'infinite-scroll' => current_theme_supports( 'infinite-scroll' ) || in_array( $current_theme->get_stylesheet(), $inf_scr_support_themes, true ),
151
				),
152
			),
153
			'jetpackStateNotices'         => array(
154
				'messageCode'      => Jetpack::state( 'message' ),
155
				'errorCode'        => Jetpack::state( 'error' ),
156
				'errorDescription' => Jetpack::state( 'error_description' ),
157
				'messageContent'   => Jetpack::state( 'display_update_modal' ) ? self::get_update_modal_data() : null,
158
			),
159
			'tracksUserData'              => Jetpack_Tracks_Client::get_connected_user_tracks_identity(),
160
			'currentIp'                   => function_exists( 'jetpack_protect_get_ip' ) ? jetpack_protect_get_ip() : false,
161
			'lastPostUrl'                 => esc_url( $last_post ),
162
			'externalServicesConnectUrls' => self::get_external_services_connect_urls(),
163
			'calypsoEnv'                  => Jetpack::get_calypso_env(),
164
			'products'                    => Jetpack::get_products_for_purchase(),
165
			'recommendationsStep'         => Jetpack_Core_Json_Api_Endpoints::get_recommendations_step()['step'],
166
			'isSafari'                    => $is_safari || User_Agent_Info::is_opera_desktop(), // @todo Rename isSafari everywhere.
167
			'doNotUseConnectionIframe'    => Constants::is_true( 'JETPACK_SHOULD_NOT_USE_CONNECTION_IFRAME' ),
168
			'licensing'                   => array(
169
				'error'           => Licensing::instance()->last_error(),
170
				'showLicensingUi' => Licensing::instance()->is_licensing_input_enabled(),
171
			),
172
		);
173
	}
174
175
	/**
176
	 * Gets array of any Jetpack notices that have been dismissed.
177
	 *
178
	 * @return mixed|void
179
	 */
180
	public static function get_dismissed_jetpack_notices() {
181
		$jetpack_dismissed_notices = get_option( 'jetpack_dismissed_notices', array() );
182
		/**
183
		 * Array of notices that have been dismissed.
184
		 *
185
		 * @param array $jetpack_dismissed_notices If empty, will not show any Jetpack notices.
186
		 */
187
		$dismissed_notices = apply_filters( 'jetpack_dismissed_notices', $jetpack_dismissed_notices );
188
		return $dismissed_notices;
189
	}
190
191
	/**
192
	 * Returns an array of modules and settings both as first class members of the object.
193
	 *
194
	 * @return array flattened settings with modules.
195
	 */
196
	public static function get_flattened_settings() {
197
		$core_api_endpoint = new Jetpack_Core_API_Data();
198
		$settings          = $core_api_endpoint->get_all_options();
199
		return $settings->data;
200
	}
201
202
	/**
203
	 * Returns the release post content and image data as an associative array.
204
	 * This data is used to create the update modal.
205
	 */
206
	public static function get_update_modal_data() {
207
		$post_data = self::get_release_post_data();
208
209
		if ( ! isset( $post_data['posts'][0] ) ) {
210
			return;
211
		}
212
213
		$post = $post_data['posts'][0];
214
215
		if ( empty( $post['content'] ) ) {
216
			return;
217
		}
218
219
		// This allows us to embed videopress videos into the release post.
220
		add_filter( 'wp_kses_allowed_html', array( __CLASS__, 'allow_post_embed_iframe' ), 10, 2 );
221
		$content = wp_kses_post( $post['content'] );
222
		remove_filter( 'wp_kses_allowed_html', array( __CLASS__, 'allow_post_embed_iframe' ), 10, 2 );
0 ignored issues
show
Unused Code introduced by
The call to remove_filter() has too many arguments starting with 2.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
223
224
		$post_title = isset( $post['title'] ) ? $post['title'] : null;
225
		$title      = wp_kses( $post_title, array() );
226
227
		$post_thumbnail = isset( $post['post_thumbnail'] ) ? $post['post_thumbnail'] : null;
228
		if ( ! empty( $post_thumbnail ) ) {
229
			jetpack_require_lib( 'class.jetpack-photon-image' );
230
			$photon_image = new Jetpack_Photon_Image(
231
				array(
232
					'file'   => jetpack_photon_url( $post_thumbnail['URL'] ),
233
					'width'  => $post_thumbnail['width'],
234
					'height' => $post_thumbnail['height'],
235
				),
236
				$post_thumbnail['mime_type']
237
			);
238
			$photon_image->resize(
239
				array(
240
					'width'  => 600,
241
					'height' => null,
242
					'crop'   => false,
243
				)
244
			);
245
			$post_thumbnail_url = $photon_image->get_raw_filename();
246
		} else {
247
			$post_thumbnail_url = null;
248
		}
249
250
		$post_array = array(
251
			'release_post_content'        => $content,
252
			'release_post_featured_image' => $post_thumbnail_url,
253
			'release_post_title'          => $title,
254
		);
255
256
		return $post_array;
257
	}
258
259
	/**
260
	 * Temporarily allow post content to contain iframes, e.g. for videopress.
261
	 *
262
	 * @param string $tags    The tags.
263
	 * @param string $context The context.
264
	 */
265
	public static function allow_post_embed_iframe( $tags, $context ) {
266
		if ( 'post' === $context ) {
267
			$tags['iframe'] = array(
268
				'src'             => true,
269
				'height'          => true,
270
				'width'           => true,
271
				'frameborder'     => true,
272
				'allowfullscreen' => true,
273
			);
274
		}
275
276
		return $tags;
277
	}
278
279
	/**
280
	 * Obtains the release post from the Jetpack release post blog. A release post will be displayed in the
281
	 * update modal when a post has a tag equal to the Jetpack version number.
282
	 *
283
	 * The response parameters for the post array can be found here:
284
	 * https://developer.wordpress.com/docs/api/1.1/get/sites/%24site/posts/%24post_ID/#apidoc-response
285
	 *
286
	 * @return array|null Returns an associative array containing the release post data at index ['posts'][0].
287
	 *                    Returns null if the release post data is not available.
288
	 */
289
	public static function get_release_post_data() {
290
		if ( Constants::is_defined( 'TESTING_IN_JETPACK' ) && Constants::get_constant( 'TESTING_IN_JETPACK' ) ) {
291
			return null;
292
		}
293
294
		$release_post_src = add_query_arg(
295
			array(
296
				'order_by' => 'date',
297
				'tag'      => JETPACK__VERSION,
298
				'number'   => '1',
299
			),
300
			'https://public-api.wordpress.com/rest/v1/sites/' . JETPACK__RELEASE_POST_BLOG_SLUG . '/posts'
301
		);
302
303
		$response = wp_remote_get( $release_post_src );
304
305
		if ( ! is_array( $response ) ) {
306
			return null;
307
		}
308
309
		return json_decode( wp_remote_retrieve_body( $response ), true );
310
	}
311
312
	/**
313
	 * Get external services connect URLs.
314
	 */
315
	public static function get_external_services_connect_urls() {
316
		$connect_urls = array();
317
		jetpack_require_lib( 'class.jetpack-keyring-service-helper' );
318
		// phpcs:disable
319
		foreach ( Jetpack_Keyring_Service_Helper::$SERVICES as $service_name => $service_info ) {
320
			// phpcs:enable
321
			$connect_urls[ $service_name ] = Jetpack_Keyring_Service_Helper::connect_url( $service_name, $service_info['for'] );
322
		}
323
		return $connect_urls;
324
	}
325
}
326