Completed
Push — add/security-and-complete-plan... ( 5e52f0 )
by
unknown
56:03 queued 47:53
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
				'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
				'jetpack_personal',
57
				'jetpack_personal_monthly',
58
				'personal-bundle',
59
				'personal-bundle-monthly',
60
				'personal-bundle-2y',
61
			),
62
			'supports' => array(
63
				'akismet',
64
				'recurring-payments',
65
			),
66
		),
67
		'premium'  => array(
68
			'plans'    => array(
69
				'jetpack_premium',
70
				'jetpack_premium_monthly',
71
				'value_bundle',
72
				'value_bundle-monthly',
73
				'value_bundle-2y',
74
			),
75
			'supports' => array(
76
				'donations',
77
				'simple-payments',
78
				'vaultpress',
79
				'videopress',
80
			),
81
		),
82
		'business' => array(
83
			'plans'    => array(
84
				'jetpack_business',
85
				'jetpack_business_monthly',
86
				'business-bundle',
87
				'business-bundle-monthly',
88
				'business-bundle-2y',
89
				'ecommerce-bundle',
90
				'ecommerce-bundle-monthly',
91
				'ecommerce-bundle-2y',
92
				'vip',
93
			),
94
			'supports' => array(),
95
		),
96
	);
97
98
	/**
99
	 * Given a response to the `/sites/%d` endpoint, will parse the response and attempt to set the
100
	 * site's plan and products from the response.
101
	 *
102
	 * @param array $response The response from `/sites/%d`.
103
	 * @return bool Was the plan successfully updated?
104
	 */
105
	public static function update_from_sites_response( $response ) {
106
		// Bail if there was an error or malformed response.
107
		if ( is_wp_error( $response ) || ! is_array( $response ) || ! isset( $response['body'] ) ) {
108
			return false;
109
		}
110
111
		$body = wp_remote_retrieve_body( $response );
112
		if ( is_wp_error( $body ) ) {
113
			return false;
114
		}
115
116
		// Decode the results.
117
		$results = json_decode( $body, true );
118
119
		if ( ! is_array( $results ) ) {
120
			return false;
121
		}
122
123
		if ( isset( $results['products'] ) ) {
124
			// Store the site's products in an option and return true if updated.
125
			self::store_data_in_option( self::SITE_PRODUCTS_OPTION, $results['products'] );
126
		}
127
128
		if ( ! isset( $results['plan'] ) ) {
129
			return false;
130
		}
131
132
		// Store the new plan in an option and return true if updated.
133
		$result = self::store_data_in_option( self::PLAN_OPTION, $results['plan'] );
134
135
		if ( $result ) {
136
			// Reset the cache since we've just updated the plan.
137
			self::$active_plan_cache = null;
138
		}
139
140
		return $result;
141
	}
142
143
	/**
144
	 * Store data in an option.
145
	 *
146
	 * @param string $option The name of the option that will store the data.
147
	 * @param array  $data Data to be store in an option.
148
	 * @return bool Were the subscriptions successfully updated?
149
	 */
150
	private static function store_data_in_option( $option, $data ) {
151
		$result = update_option( $option, $data, true );
152
153
		// If something goes wrong with the update, so delete the current option and then update it.
154
		if ( ! $result ) {
155
			delete_option( $option );
156
			$result = update_option( $option, $data, true );
157
		}
158
159
		return $result;
160
	}
161
162
	/**
163
	 * Make an API call to WordPress.com for plan status
164
	 *
165
	 * @uses Jetpack_Options::get_option()
166
	 * @uses Client::wpcom_json_api_request_as_blog()
167
	 * @uses update_option()
168
	 *
169
	 * @access public
170
	 * @static
171
	 *
172
	 * @return bool True if plan is updated, false if no update
173
	 */
174
	public static function refresh_from_wpcom() {
175
		// Make the API request.
176
		$request  = sprintf( '/sites/%d', Jetpack_Options::get_option( 'id' ) );
177
		$response = Client::wpcom_json_api_request_as_blog( $request, '1.1' );
178
179
		return self::update_from_sites_response( $response );
180
	}
181
182
	/**
183
	 * Get the plan that this Jetpack site is currently using.
184
	 *
185
	 * @uses get_option()
186
	 *
187
	 * @access public
188
	 * @static
189
	 *
190
	 * @return array Active Jetpack plan details
191
	 */
192
	public static function get() {
193
		// this can be expensive to compute so we cache for the duration of a request.
194
		if ( is_array( self::$active_plan_cache ) && ! empty( self::$active_plan_cache ) ) {
195
			return self::$active_plan_cache;
196
		}
197
198
		$plan = get_option( self::PLAN_OPTION, array() );
199
200
		// Set the default options.
201
		$plan = wp_parse_args(
202
			$plan,
203
			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...
204
				'product_slug' => 'jetpack_free',
205
				'class'        => 'free',
206
				'features'     => array(
207
					'active' => array(),
208
				),
209
			)
210
		);
211
212
		list( $plan['class'], $supports ) = self::get_class_and_features( $plan['product_slug'] );
213
214
		// get available features.
215
		foreach ( Jetpack::get_available_modules() as $module_slug ) {
216
			$module = Jetpack::get_module( $module_slug );
217
			if ( ! isset( $module ) || ! is_array( $module ) ) {
218
				continue;
219
			}
220
			if ( in_array( 'free', $module['plan_classes'], true ) || in_array( $plan['class'], $module['plan_classes'], true ) ) {
221
				$supports[] = $module_slug;
222
			}
223
		}
224
225
		$plan['supports'] = $supports;
226
227
		self::$active_plan_cache = $plan;
228
229
		return $plan;
230
	}
231
232
	/**
233
	 * Get the site's products.
234
	 *
235
	 * @uses get_option()
236
	 *
237
	 * @access public
238
	 * @static
239
	 *
240
	 * @return array Active Jetpack products
241
	 */
242
	public static function get_products() {
243
		return get_option( self::SITE_PRODUCTS_OPTION, array() );
244
	}
245
246
	/**
247
	 * Get the class of plan and a list of features it supports
248
	 *
249
	 * @param string $plan_slug The plan that we're interested in.
250
	 * @return array Two item array, the plan class and the an array of features.
251
	 */
252
	private static function get_class_and_features( $plan_slug ) {
253
		$features = array();
254
		foreach ( self::PLAN_DATA as $class => $details ) {
255
			$features = array_merge( $features, $details['supports'] );
256
			if ( in_array( $plan_slug, $details['plans'], true ) ) {
257
				return array( $class, $features );
258
			}
259
		}
260
		return array( 'free', self::PLAN_DATA['free']['supports'] );
261
	}
262
263
	/**
264
	 * Gets the minimum plan slug that supports the given feature
265
	 *
266
	 * @param string $feature The name of the feature.
267
	 * @return string|bool The slug for the minimum plan that supports.
268
	 *  the feature or false if not found
269
	 */
270
	public static function get_minimum_plan_for_feature( $feature ) {
271
		foreach ( self::PLAN_DATA as $class => $details ) {
272
			if ( in_array( $feature, $details['supports'], true ) ) {
273
				return $details['plans'][0];
274
			}
275
		}
276
		return false;
277
	}
278
279
	/**
280
	 * Determine whether the active plan supports a particular feature
281
	 *
282
	 * @uses Jetpack_Plan::get()
283
	 *
284
	 * @access public
285
	 * @static
286
	 *
287
	 * @param string $feature The module or feature to check.
288
	 *
289
	 * @return bool True if plan supports feature, false if not
290
	 */
291
	public static function supports( $feature ) {
292
		// Search product bypasses plan feature check.
293
		if ( 'search' === $feature && (bool) get_option( 'has_jetpack_search_product' ) ) {
294
			return true;
295
		}
296
297
		$plan = self::get();
298
299
		// Manually mapping WordPress.com features to Jetpack module slugs.
300
		foreach ( $plan['features']['active'] as $wpcom_feature ) {
301
			switch ( $wpcom_feature ) {
302
				case 'wordads-jetpack':
303
					// WordAds are supported for this site.
304
					if ( 'wordads' === $feature ) {
305
						return true;
306
					}
307
					break;
308
			}
309
		}
310
311
		if (
312
			in_array( $feature, $plan['supports'], true )
313
			|| in_array( $feature, $plan['features']['active'], true )
314
		) {
315
			return true;
316
		}
317
318
		return false;
319
	}
320
}
321