Completed
Push — update/business-hours-block-ph... ( 3401d5...d076d1 )
by
unknown
09:58 queued 10s
created

Jetpack_Redux_State_Helper::get_purchase_token()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 0
dl 0
loc 14
rs 9.7998
c 0
b 0
f 0
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\Manager as Connection_Manager;
10
use Automattic\Jetpack\Connection\REST_Connector;
11
use Automattic\Jetpack\Constants;
12
use Automattic\Jetpack\Device_Detection\User_Agent_Info;
13
use Automattic\Jetpack\Licensing;
14
use Automattic\Jetpack\Partner;
15
use Automattic\Jetpack\Status;
16
17
/**
18
 * Responsible for populating the initial Redux state.
19
 */
20
class Jetpack_Redux_State_Helper {
21
	/**
22
	 * Generate the initial state array to be used by the Redux store.
23
	 */
24
	public static function get_initial_state() {
25
		global $is_safari;
26
27
		// Load API endpoint base classes and endpoints for getting the module list fed into the JS Admin Page.
28
		require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-xmlrpc-consumer-endpoint.php';
29
		require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-module-endpoints.php';
30
		$module_list_endpoint = new Jetpack_Core_API_Module_List_Endpoint();
31
		$modules              = $module_list_endpoint->get_modules();
32
33
		// Preparing translated fields for JSON encoding by transforming all HTML entities to
34
		// respective characters.
35
		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...
36
			$modules[ $slug ]['name']              = html_entity_decode( $data['name'] );
37
			$modules[ $slug ]['description']       = html_entity_decode( $data['description'] );
38
			$modules[ $slug ]['short_description'] = html_entity_decode( $data['short_description'] );
39
			$modules[ $slug ]['long_description']  = html_entity_decode( $data['long_description'] );
40
		}
41
42
		// Collecting roles that can view site stats.
43
		$stats_roles   = array();
44
		$enabled_roles = function_exists( 'stats_get_option' ) ? stats_get_option( 'roles' ) : array( 'administrator' );
45
46
		if ( ! function_exists( 'get_editable_roles' ) ) {
47
			require_once ABSPATH . 'wp-admin/includes/user.php';
48
		}
49
		foreach ( get_editable_roles() as $slug => $role ) {
50
			$stats_roles[ $slug ] = array(
51
				'name'    => translate_user_role( $role['name'] ),
52
				'canView' => is_array( $enabled_roles ) ? in_array( $slug, $enabled_roles, true ) : false,
53
			);
54
		}
55
56
		// Get information about current theme.
57
		$current_theme = wp_get_theme();
58
59
		// Get all themes that Infinite Scroll provides support for natively.
60
		$inf_scr_support_themes = array();
61
		foreach ( Jetpack::glob_php( JETPACK__PLUGIN_DIR . 'modules/infinite-scroll/themes' ) as $path ) {
62
			if ( is_readable( $path ) ) {
63
				$inf_scr_support_themes[] = basename( $path, '.php' );
64
			}
65
		}
66
67
		// Get last post, to build the link to Customizer in the Related Posts module.
68
		$last_post = get_posts( array( 'posts_per_page' => 1 ) );
69
		$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...
70
			? get_permalink( $last_post[0]->ID )
71
			: get_home_url();
72
73
		$current_user_data = jetpack_current_user_data();
74
75
		/**
76
		 * Adds information to the `connectionStatus` API field that is unique to the Jetpack React dashboard.
77
		 */
78
		$connection_status = array(
79
			'isInIdentityCrisis' => Jetpack::validate_sync_error_idc_option(),
80
			'sandboxDomain'      => JETPACK__SANDBOX_DOMAIN,
81
82
			/**
83
			 * Filter to add connection errors
84
			 * Format: array( array( 'code' => '...', 'message' => '...', 'action' => '...' ), ... )
85
			 *
86
			 * @since 8.7.0
87
			 *
88
			 * @param array $errors Connection errors.
89
			 */
90
			'errors'             => apply_filters( 'react_connection_errors_initial_state', array() ),
91
		);
92
93
		$connection_status = array_merge( REST_Connector::connection_status( false ), $connection_status );
94
95
		return array(
96
			'WP_API_root'                 => esc_url_raw( rest_url() ),
97
			'WP_API_nonce'                => wp_create_nonce( 'wp_rest' ),
98
			'purchaseToken'               => self::get_purchase_token(),
99
			'pluginBaseUrl'               => plugins_url( '', JETPACK__PLUGIN_FILE ),
100
			'connectionStatus'            => $connection_status,
101
			'connectUrl'                  => false == $current_user_data['isConnected'] // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
102
				? Jetpack::init()->build_connect_url( true, false, false )
103
				: '',
104
			'dismissedNotices'            => self::get_dismissed_jetpack_notices(),
105
			'isDevVersion'                => Jetpack::is_development_version(),
106
			'currentVersion'              => JETPACK__VERSION,
107
			'is_gutenberg_available'      => true,
108
			'getModules'                  => $modules,
109
			'rawUrl'                      => ( new Status() )->get_site_suffix(),
110
			'adminUrl'                    => esc_url( admin_url() ),
111
			'siteTitle'                   => (string) htmlspecialchars_decode( get_option( 'blogname' ), ENT_QUOTES ),
112
			'stats'                       => array(
113
				// data is populated asynchronously on page load.
114
				'data'  => array(
115
					'general' => false,
116
					'day'     => false,
117
					'week'    => false,
118
					'month'   => false,
119
				),
120
				'roles' => $stats_roles,
121
			),
122
			'aff'                         => Partner::init()->get_partner_code( Partner::AFFILIATE_CODE ),
123
			'partnerSubsidiaryId'         => Partner::init()->get_partner_code( Partner::SUBSIDIARY_CODE ),
124
			'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...
125
			'userData'                    => array(
126
				'currentUser' => $current_user_data,
127
			),
128
			'siteData'                    => array(
129
				'icon'                       => has_site_icon()
130
					? 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...
131
					: '',
132
				'siteVisibleToSearchEngines' => '1' == get_option( 'blog_public' ), // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
133
				/**
134
				 * Whether promotions are visible or not.
135
				 *
136
				 * @since 4.8.0
137
				 *
138
				 * @param bool $are_promotions_active Status of promotions visibility. True by default.
139
				 */
140
				'showPromotions'             => apply_filters( 'jetpack_show_promotions', true ),
141
				'isAtomicSite'               => jetpack_is_atomic_site(),
142
				'plan'                       => Jetpack_Plan::get(),
143
				'showBackups'                => Jetpack::show_backups_ui(),
144
				'showRecommendations'        => Jetpack_Recommendations::is_enabled(),
145
				'isMultisite'                => is_multisite(),
146
				'dateFormat'                 => get_option( 'date_format' ),
147
			),
148
			'themeData'                   => array(
149
				'name'      => $current_theme->get( 'Name' ),
150
				'hasUpdate' => (bool) get_theme_update_available( $current_theme ),
151
				'support'   => array(
152
					'infinite-scroll' => current_theme_supports( 'infinite-scroll' ) || in_array( $current_theme->get_stylesheet(), $inf_scr_support_themes, true ),
153
				),
154
			),
155
			'jetpackStateNotices'         => array(
156
				'messageCode'      => Jetpack::state( 'message' ),
157
				'errorCode'        => Jetpack::state( 'error' ),
158
				'errorDescription' => Jetpack::state( 'error_description' ),
159
				'messageContent'   => Jetpack::state( 'display_update_modal' ) ? self::get_update_modal_data() : null,
160
			),
161
			'tracksUserData'              => Jetpack_Tracks_Client::get_connected_user_tracks_identity(),
162
			'currentIp'                   => function_exists( 'jetpack_protect_get_ip' ) ? jetpack_protect_get_ip() : false,
163
			'lastPostUrl'                 => esc_url( $last_post ),
164
			'externalServicesConnectUrls' => self::get_external_services_connect_urls(),
165
			'calypsoEnv'                  => Jetpack::get_calypso_env(),
166
			'products'                    => Jetpack::get_products_for_purchase(),
167
			'recommendationsStep'         => Jetpack_Core_Json_Api_Endpoints::get_recommendations_step()['step'],
168
			'isSafari'                    => $is_safari || User_Agent_Info::is_opera_desktop(), // @todo Rename isSafari everywhere.
169
			'doNotUseConnectionIframe'    => Constants::is_true( 'JETPACK_SHOULD_NOT_USE_CONNECTION_IFRAME' ),
170
			'licensing'                   => array(
171
				'error'           => Licensing::instance()->last_error(),
172
				'showLicensingUi' => Licensing::instance()->is_licensing_input_enabled(),
173
			),
174
		);
175
	}
176
177
	/**
178
	 * Gets array of any Jetpack notices that have been dismissed.
179
	 *
180
	 * @return mixed|void
181
	 */
182
	public static function get_dismissed_jetpack_notices() {
183
		$jetpack_dismissed_notices = get_option( 'jetpack_dismissed_notices', array() );
184
		/**
185
		 * Array of notices that have been dismissed.
186
		 *
187
		 * @param array $jetpack_dismissed_notices If empty, will not show any Jetpack notices.
188
		 */
189
		$dismissed_notices = apply_filters( 'jetpack_dismissed_notices', $jetpack_dismissed_notices );
190
		return $dismissed_notices;
191
	}
192
193
	/**
194
	 * Returns an array of modules and settings both as first class members of the object.
195
	 *
196
	 * @return array flattened settings with modules.
197
	 */
198
	public static function get_flattened_settings() {
199
		$core_api_endpoint = new Jetpack_Core_API_Data();
200
		$settings          = $core_api_endpoint->get_all_options();
201
		return $settings->data;
202
	}
203
204
	/**
205
	 * Returns the release post content and image data as an associative array.
206
	 * This data is used to create the update modal.
207
	 */
208
	public static function get_update_modal_data() {
209
		$post_data = self::get_release_post_data();
210
211
		if ( ! isset( $post_data['posts'][0] ) ) {
212
			return;
213
		}
214
215
		$post = $post_data['posts'][0];
216
217
		if ( empty( $post['content'] ) ) {
218
			return;
219
		}
220
221
		// This allows us to embed videopress videos into the release post.
222
		add_filter( 'wp_kses_allowed_html', array( __CLASS__, 'allow_post_embed_iframe' ), 10, 2 );
223
		$content = wp_kses_post( $post['content'] );
224
		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...
225
226
		$post_title = isset( $post['title'] ) ? $post['title'] : null;
227
		$title      = wp_kses( $post_title, array() );
228
229
		$post_thumbnail = isset( $post['post_thumbnail'] ) ? $post['post_thumbnail'] : null;
230
		if ( ! empty( $post_thumbnail ) ) {
231
			jetpack_require_lib( 'class.jetpack-photon-image' );
232
			$photon_image = new Jetpack_Photon_Image(
233
				array(
234
					'file'   => jetpack_photon_url( $post_thumbnail['URL'] ),
235
					'width'  => $post_thumbnail['width'],
236
					'height' => $post_thumbnail['height'],
237
				),
238
				$post_thumbnail['mime_type']
239
			);
240
			$photon_image->resize(
241
				array(
242
					'width'  => 600,
243
					'height' => null,
244
					'crop'   => false,
245
				)
246
			);
247
			$post_thumbnail_url = $photon_image->get_raw_filename();
248
		} else {
249
			$post_thumbnail_url = null;
250
		}
251
252
		$post_array = array(
253
			'release_post_content'        => $content,
254
			'release_post_featured_image' => $post_thumbnail_url,
255
			'release_post_title'          => $title,
256
		);
257
258
		return $post_array;
259
	}
260
261
	/**
262
	 * Temporarily allow post content to contain iframes, e.g. for videopress.
263
	 *
264
	 * @param string $tags    The tags.
265
	 * @param string $context The context.
266
	 */
267
	public static function allow_post_embed_iframe( $tags, $context ) {
268
		if ( 'post' === $context ) {
269
			$tags['iframe'] = array(
270
				'src'             => true,
271
				'height'          => true,
272
				'width'           => true,
273
				'frameborder'     => true,
274
				'allowfullscreen' => true,
275
			);
276
		}
277
278
		return $tags;
279
	}
280
281
	/**
282
	 * Obtains the release post from the Jetpack release post blog. A release post will be displayed in the
283
	 * update modal when a post has a tag equal to the Jetpack version number.
284
	 *
285
	 * The response parameters for the post array can be found here:
286
	 * https://developer.wordpress.com/docs/api/1.1/get/sites/%24site/posts/%24post_ID/#apidoc-response
287
	 *
288
	 * @return array|null Returns an associative array containing the release post data at index ['posts'][0].
289
	 *                    Returns null if the release post data is not available.
290
	 */
291
	public static function get_release_post_data() {
292
		if ( Constants::is_defined( 'TESTING_IN_JETPACK' ) && Constants::get_constant( 'TESTING_IN_JETPACK' ) ) {
293
			return null;
294
		}
295
296
		$release_post_src = add_query_arg(
297
			array(
298
				'order_by' => 'date',
299
				'tag'      => JETPACK__VERSION,
300
				'number'   => '1',
301
			),
302
			'https://public-api.wordpress.com/rest/v1/sites/' . JETPACK__RELEASE_POST_BLOG_SLUG . '/posts'
303
		);
304
305
		$response = wp_remote_get( $release_post_src );
306
307
		if ( ! is_array( $response ) ) {
308
			return null;
309
		}
310
311
		return json_decode( wp_remote_retrieve_body( $response ), true );
312
	}
313
314
	/**
315
	 * Get external services connect URLs.
316
	 */
317
	public static function get_external_services_connect_urls() {
318
		$connect_urls = array();
319
		jetpack_require_lib( 'class.jetpack-keyring-service-helper' );
320
		// phpcs:disable
321
		foreach ( Jetpack_Keyring_Service_Helper::$SERVICES as $service_name => $service_info ) {
322
			// phpcs:enable
323
			$connect_urls[ $service_name ] = Jetpack_Keyring_Service_Helper::connect_url( $service_name, $service_info['for'] );
324
		}
325
		return $connect_urls;
326
	}
327
328
	/**
329
	 * Gets a purchase token that is used for Jetpack logged out visitor checkout.
330
	 * The purchase token should be appended to all CTA url's that lead to checkout.
331
	 *
332
	 * @since 9.8.0
333
	 * @return string|boolean
334
	 */
335
	public static function get_purchase_token() {
336
		if ( ! Jetpack::current_user_can_purchase() ) {
337
			return false;
338
		}
339
340
		$purchase_token = Jetpack_Options::get_option( 'purchase_token', false );
341
342
		if ( $purchase_token ) {
343
			return $purchase_token;
344
		}
345
		// If the purchase token is not saved in the options table yet, then add it.
346
		Jetpack_Options::update_option( 'purchase_token', self::generate_purchase_token(), true );
0 ignored issues
show
Documentation introduced by
true is of type boolean, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
347
		return Jetpack_Options::get_option( 'purchase_token', false );
348
	}
349
350
	/**
351
	 * Generates a purchase token that is used for Jetpack logged out visitor checkout.
352
	 *
353
	 * @since 9.8.0
354
	 * @return string
355
	 */
356
	public static function generate_purchase_token() {
357
		return wp_generate_password( 12, false );
358
	}
359
}
360
361
/**
362
 * Gather data about the current user.
363
 *
364
 * @since 4.1.0
365
 *
366
 * @return array
367
 */
368
function jetpack_current_user_data() {
369
	$jetpack_connection = new Connection_Manager( 'jetpack' );
370
371
	$current_user      = wp_get_current_user();
372
	$is_user_connected = $jetpack_connection->is_user_connected( $current_user->ID );
373
	$is_master_user    = $is_user_connected && (int) $current_user->ID && (int) Jetpack_Options::get_option( 'master_user' ) === (int) $current_user->ID;
374
	$dotcom_data       = $jetpack_connection->get_connected_user_data();
375
376
	// Add connected user gravatar to the returned dotcom_data.
377
	$dotcom_data['avatar'] = ( ! empty( $dotcom_data['email'] ) ?
378
		get_avatar_url(
379
			$dotcom_data['email'],
380
			array(
381
				'size'    => 64,
382
				'default' => 'mysteryman',
383
			)
384
		)
385
		: false );
386
387
	$current_user_data = array(
388
		'isConnected' => $is_user_connected,
389
		'isMaster'    => $is_master_user,
390
		'username'    => $current_user->user_login,
391
		'id'          => $current_user->ID,
392
		'wpcomUser'   => $dotcom_data,
393
		'gravatar'    => get_avatar_url( $current_user->ID, 64, 'mm', '', array( 'force_display' => true ) ),
394
		'permissions' => array(
395
			'admin_page'         => current_user_can( 'jetpack_admin_page' ),
396
			'connect'            => current_user_can( 'jetpack_connect' ),
397
			'connect_user'       => current_user_can( 'jetpack_connect_user' ),
398
			'disconnect'         => current_user_can( 'jetpack_disconnect' ),
399
			'manage_modules'     => current_user_can( 'jetpack_manage_modules' ),
400
			'network_admin'      => current_user_can( 'jetpack_network_admin_page' ),
401
			'network_sites_page' => current_user_can( 'jetpack_network_sites_page' ),
402
			'edit_posts'         => current_user_can( 'edit_posts' ),
403
			'publish_posts'      => current_user_can( 'publish_posts' ),
404
			'manage_options'     => current_user_can( 'manage_options' ),
405
			'view_stats'         => current_user_can( 'view_stats' ),
406
			'manage_plugins'     => current_user_can( 'install_plugins' )
407
									&& current_user_can( 'activate_plugins' )
408
									&& current_user_can( 'update_plugins' )
409
									&& current_user_can( 'delete_plugins' ),
410
		),
411
	);
412
413
	return $current_user_data;
414
}
415