Completed
Push — add/security-and-complete-plan... ( 981d56...ad802a )
by
unknown
08:20
created

class.jetpack-plan.php (1 issue)

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 //phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2
/**
3
 * Handles fetching of the site's plan and products from WordPress.com and caching values locally.
4
 *
5
 * Not to be confused with the `Jetpack_Plans` class (in `_inc/lib/plans.php`), which
6
 * fetches general information about all available plans from WordPress.com, side-effect free.
7
 *
8
 * @package Jetpack
9
 */
10
11
use Automattic\Jetpack\Connection\Client;
12
13
/**
14
 * Provides methods methods for fetching the site's plan and products from WordPress.com.
15
 */
16
class Jetpack_Plan {
17
	/**
18
	 * A cache variable to hold the active plan for the current request.
19
	 *
20
	 * @var array
21
	 */
22
	private static $active_plan_cache;
23
24
	/**
25
	 * The name of the option that will store the site's plan.
26
	 *
27
	 * @var string
28
	 */
29
	const PLAN_OPTION = 'jetpack_active_plan';
30
31
	/**
32
	 * The name of the option that will store the site's products.
33
	 *
34
	 * @var string
35
	 */
36
	const SITE_PRODUCTS_OPTION = 'jetpack_site_products';
37
38
	const PLAN_DATA = array(
39
		'free'     => array(
40
			'plans'    => array(
41
				PLAN_JETPACK_FREE,
42
			),
43
			'supports' => array(
44
				'opentable',
45
				'calendly',
46
				'send-a-message',
47
				'social-previews',
48
49
				'core/video',
50
				'core/cover',
51
				'core/audio',
52
			),
53
		),
54
		'personal' => array(
55
			'plans'    => array(
56
				PLAN_JETPACK_PERSONAL,
57
				PLAN_JETPACK_PERSONAL_MONTHLY,
58
				PLAN_PERSONAL,
59
				PLAN_PERSONAL_MONTHLY,
60
				PLAN_PERSONAL_2_YEARS,
61
			),
62
			'supports' => array(
63
				'akismet',
64
				'recurring-payments',
65
			),
66
		),
67
		'premium'  => array(
68
			'plans'    => array(
69
				PLAN_JETPACK_PREMIUM,
70
				PLAN_JETPACK_PREMIUM_MONTHLY,
71
				PLAN_PREMIUM,
72
				PLAN_PREMIUM_MONTHLY,
73
				PLAN_PREMIUM_2_YEARS,
74
			),
75
			'supports' => array(
76
				'donations',
77
				'simple-payments',
78
				'vaultpress',
79
				'videopress',
80
			),
81
		),
82
		'security' => array(
83
			'plans' => array(
84
				PLAN_JETPACK_SECURITY_DAILY,
85
				PLAN_JETPACK_SECURITY_DAILY_MONTHLY,
86
				PLAN_JETPACK_SECURITY_REALTIME,
87
				PLAN_JETPACK_SECURITY_REALTIME_MONTHLY,
88
			),
89
			'supports' => array(),
90
		),
91
		'business' => array(
92
			'plans'    => array(
93
				PLAN_JETPACK_BUSINESS,
94
				PLAN_JETPACK_BUSINESS_MONTHLY,
95
				PLAN_BUSINESS,
96
				PLAN_BUSINESS_MONTHLY,
97
				PLAN_BUSINESS_2_YEARS,
98
				PLAN_ECOMMERCE,
99
				PLAN_ECOMMERCE_MONTHLY,
100
				PLAN_ECOMMERCE_2_YEARS,
101
				PLAN_VIP,
102
			),
103
			'supports' => array(),
104
		),
105
		'complete' => array(
106
			'plans' => array(
107
				PLAN_JETPACK_COMPLETE,
108
				PLAN_JETPACK_COMPLETE_MONTHLY
109
			),
110
			'supports' => array(),
111
		),
112
113
	);
114
115
	/**
116
	 * Given a response to the `/sites/%d` endpoint, will parse the response and attempt to set the
117
	 * site's plan and products from the response.
118
	 *
119
	 * @param array $response The response from `/sites/%d`.
120
	 * @return bool Was the plan successfully updated?
121
	 */
122
	public static function update_from_sites_response( $response ) {
123
		// Bail if there was an error or malformed response.
124
		if ( is_wp_error( $response ) || ! is_array( $response ) || ! isset( $response['body'] ) ) {
125
			return false;
126
		}
127
128
		$body = wp_remote_retrieve_body( $response );
129
		if ( is_wp_error( $body ) ) {
130
			return false;
131
		}
132
133
		// Decode the results.
134
		$results = json_decode( $body, true );
135
136
		if ( ! is_array( $results ) ) {
137
			return false;
138
		}
139
140
		if ( isset( $results['products'] ) ) {
141
			// Store the site's products in an option and return true if updated.
142
			self::store_data_in_option( self::SITE_PRODUCTS_OPTION, $results['products'] );
143
		}
144
145
		if ( ! isset( $results['plan'] ) ) {
146
			return false;
147
		}
148
149
		// Store the new plan in an option and return true if updated.
150
		$result = self::store_data_in_option( self::PLAN_OPTION, $results['plan'] );
151
152
		if ( $result ) {
153
			// Reset the cache since we've just updated the plan.
154
			self::$active_plan_cache = null;
155
		}
156
157
		return $result;
158
	}
159
160
	/**
161
	 * Store data in an option.
162
	 *
163
	 * @param string $option The name of the option that will store the data.
164
	 * @param array  $data Data to be store in an option.
165
	 * @return bool Were the subscriptions successfully updated?
166
	 */
167
	private static function store_data_in_option( $option, $data ) {
168
		$result = update_option( $option, $data, true );
169
170
		// If something goes wrong with the update, so delete the current option and then update it.
171
		if ( ! $result ) {
172
			delete_option( $option );
173
			$result = update_option( $option, $data, true );
174
		}
175
176
		return $result;
177
	}
178
179
	/**
180
	 * Make an API call to WordPress.com for plan status
181
	 *
182
	 * @uses Jetpack_Options::get_option()
183
	 * @uses Client::wpcom_json_api_request_as_blog()
184
	 * @uses update_option()
185
	 *
186
	 * @access public
187
	 * @static
188
	 *
189
	 * @return bool True if plan is updated, false if no update
190
	 */
191
	public static function refresh_from_wpcom() {
192
		// Make the API request.
193
		$request  = sprintf( '/sites/%d', Jetpack_Options::get_option( 'id' ) );
194
		$response = Client::wpcom_json_api_request_as_blog( $request, '1.1' );
195
196
		return self::update_from_sites_response( $response );
197
	}
198
199
	/**
200
	 * Get the plan that this Jetpack site is currently using.
201
	 *
202
	 * @uses get_option()
203
	 *
204
	 * @access public
205
	 * @static
206
	 *
207
	 * @return array Active Jetpack plan details
208
	 */
209
	public static function get() {
210
		// this can be expensive to compute so we cache for the duration of a request.
211
		if ( is_array( self::$active_plan_cache ) && ! empty( self::$active_plan_cache ) ) {
212
			return self::$active_plan_cache;
213
		}
214
215
		$plan = get_option( self::PLAN_OPTION, array() );
216
217
		// Set the default options.
218
		$plan = wp_parse_args(
219
			$plan,
220
			array(
0 ignored issues
show
array('product_slug' => ...y('active' => array())) is of type array<string,string|arra...active\":\"array\"}>"}>, but the function expects a string.

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...
221
				'product_slug' => PLAN_JETPACK_FREE,
222
				'class'        => 'free',
223
				'features'     => array(
224
					'active' => array(),
225
				),
226
			)
227
		);
228
229
		list( $plan['class'], $supports ) = self::get_class_and_features( $plan['product_slug'] );
230
231
		// get available features.
232
		foreach ( Jetpack::get_available_modules() as $module_slug ) {
233
			$module = Jetpack::get_module( $module_slug );
234
			if ( ! isset( $module ) || ! is_array( $module ) ) {
235
				continue;
236
			}
237
			if ( in_array( 'free', $module['plan_classes'], true ) || in_array( $plan['class'], $module['plan_classes'], true ) ) {
238
				$supports[] = $module_slug;
239
			}
240
		}
241
242
		$plan['supports'] = $supports;
243
244
		self::$active_plan_cache = $plan;
245
246
		return $plan;
247
	}
248
249
	/**
250
	 * Get the site's products.
251
	 *
252
	 * @uses get_option()
253
	 *
254
	 * @access public
255
	 * @static
256
	 *
257
	 * @return array Active Jetpack products
258
	 */
259
	public static function get_products() {
260
		return get_option( self::SITE_PRODUCTS_OPTION, array() );
261
	}
262
263
	/**
264
	 * Get the class of plan and a list of features it supports
265
	 *
266
	 * @param string $plan_slug The plan that we're interested in.
267
	 * @return array Two item array, the plan class and the an array of features.
268
	 */
269
	private static function get_class_and_features( $plan_slug ) {
270
		$features = array();
271
		foreach ( self::PLAN_DATA as $class => $details ) {
272
			$features = array_merge( $features, $details['supports'] );
273
			if ( in_array( $plan_slug, $details['plans'], true ) ) {
274
				return array( $class, $features );
275
			}
276
		}
277
		return array( 'free', self::PLAN_DATA['free']['supports'] );
278
	}
279
280
	/**
281
	 * Gets the minimum plan slug that supports the given feature
282
	 *
283
	 * @param string $feature The name of the feature.
284
	 * @return string|bool The slug for the minimum plan that supports.
285
	 *  the feature or false if not found
286
	 */
287
	public static function get_minimum_plan_for_feature( $feature ) {
288
		foreach ( self::PLAN_DATA as $class => $details ) {
289
			if ( in_array( $feature, $details['supports'], true ) ) {
290
				return $details['plans'][0];
291
			}
292
		}
293
		return false;
294
	}
295
296
	/**
297
	 * Determine whether the active plan supports a particular feature
298
	 *
299
	 * @uses Jetpack_Plan::get()
300
	 *
301
	 * @access public
302
	 * @static
303
	 *
304
	 * @param string $feature The module or feature to check.
305
	 *
306
	 * @return bool True if plan supports feature, false if not
307
	 */
308
	public static function supports( $feature ) {
309
		// Search product bypasses plan feature check.
310
		if ( 'search' === $feature && (bool) get_option( 'has_jetpack_search_product' ) ) {
311
			return true;
312
		}
313
314
		$plan = self::get();
315
316
		// Manually mapping WordPress.com features to Jetpack module slugs.
317
		foreach ( $plan['features']['active'] as $wpcom_feature ) {
318
			switch ( $wpcom_feature ) {
319
				case FEATURE_WORDADS_JETPACK:
320
					// WordAds are supported for this site.
321
					if ( 'wordads' === $feature ) {
322
						return true;
323
					}
324
					break;
325
			}
326
		}
327
328
		if (
329
			in_array( $feature, $plan['supports'], true )
330
			|| in_array( $feature, $plan['features']['active'], true )
331
		) {
332
			return true;
333
		}
334
335
		return false;
336
	}
337
}
338