Completed
Push — update/add_grunion_after_feedb... ( 614f06...9f6c1a )
by
unknown
07:04
created

Jetpack_Plan::update_from_sites_response()   B

Complexity

Conditions 9
Paths 7

Size

Total Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
nc 7
nop 1
dl 0
loc 35
rs 8.0555
c 0
b 0
f 0
1
<?php //phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2
/**
3
 * Handles fetching of the site's plan from WordPress.com and caching the value 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 plan 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
	const PLAN_OPTION = 'jetpack_active_plan';
25
26
	const PLAN_DATA = array(
27
		'free'     => array(
28
			'plans'    => array(
29
				'jetpack_free',
30
			),
31
			'supports' => array(
32
				'opentable',
33
				'calendly',
34
			),
35
		),
36
		'personal' => array(
37
			'plans'    => array(
38
				'jetpack_personal',
39
				'jetpack_personal_monthly',
40
				'personal-bundle',
41
				'personal-bundle-monthly',
42
				'personal-bundle-2y',
43
			),
44
			'supports' => array(
45
				'akismet',
46
				'recurring-payments',
47
			),
48
		),
49
		'premium'  => array(
50
			'plans'    => array(
51
				'jetpack_premium',
52
				'jetpack_premium_monthly',
53
				'value_bundle',
54
				'value_bundle-monthly',
55
				'value_bundle-2y',
56
			),
57
			'supports' => array(
58
				'simple-payments',
59
				'vaultpress',
60
				'videopress',
61
			),
62
		),
63
		'business' => array(
64
			'plans'    => array(
65
				'jetpack_business',
66
				'jetpack_business_monthly',
67
				'business-bundle',
68
				'business-bundle-monthly',
69
				'business-bundle-2y',
70
				'ecommerce-bundle',
71
				'ecommerce-bundle-monthly',
72
				'ecommerce-bundle-2y',
73
				'vip',
74
			),
75
			'supports' => array(),
76
		),
77
	);
78
79
	/**
80
	 * Given a response to the `/sites/%d` endpoint, will parse the response and attempt to set the
81
	 * plan from the response.
82
	 *
83
	 * @param array $response The response from `/sites/%d`.
84
	 * @return bool Was the plan successfully updated?
85
	 */
86
	public static function update_from_sites_response( $response ) {
87
		// Bail if there was an error or malformed response.
88
		if ( is_wp_error( $response ) || ! is_array( $response ) || ! isset( $response['body'] ) ) {
89
			return false;
90
		}
91
92
		$body = wp_remote_retrieve_body( $response );
93
		if ( is_wp_error( $body ) ) {
94
			return false;
95
		}
96
97
		// Decode the results.
98
		$results = json_decode( $body, true );
99
100
		// Bail if there were no results or plan details returned.
101
		if ( ! is_array( $results ) || ! isset( $results['plan'] ) ) {
102
			return false;
103
		}
104
105
		// Store the new plan in an option and return true if updated.
106
		$result = update_option( self::PLAN_OPTION, $results['plan'], true );
107
		if ( ! $result ) {
108
			// If we got to this point, then we know we need to update. So, assume there is an issue
109
			// with caching. To fix that issue, we can delete the current option and then update.
110
			delete_option( self::PLAN_OPTION );
111
			$result = update_option( self::PLAN_OPTION, $results['plan'], true );
112
		}
113
114
		if ( $result ) {
115
			// Reset the cache since we've just updated the plan.
116
			self::$active_plan_cache = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $active_plan_cache.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
117
		}
118
119
		return $result;
120
	}
121
122
	/**
123
	 * Make an API call to WordPress.com for plan status
124
	 *
125
	 * @uses Jetpack_Options::get_option()
126
	 * @uses Client::wpcom_json_api_request_as_blog()
127
	 * @uses update_option()
128
	 *
129
	 * @access public
130
	 * @static
131
	 *
132
	 * @return bool True if plan is updated, false if no update
133
	 */
134
	public static function refresh_from_wpcom() {
135
		// Make the API request.
136
		$request  = sprintf( '/sites/%d', Jetpack_Options::get_option( 'id' ) );
137
		$response = Client::wpcom_json_api_request_as_blog( $request, '1.1' );
138
139
		return self::update_from_sites_response( $response );
140
	}
141
142
	/**
143
	 * Get the plan that this Jetpack site is currently using.
144
	 *
145
	 * @uses get_option()
146
	 *
147
	 * @access public
148
	 * @static
149
	 *
150
	 * @return array Active Jetpack plan details
151
	 */
152
	public static function get() {
153
		// this can be expensive to compute so we cache for the duration of a request.
154
		if ( is_array( self::$active_plan_cache ) && ! empty( self::$active_plan_cache ) ) {
155
			return self::$active_plan_cache;
156
		}
157
158
		$plan = get_option( self::PLAN_OPTION, array() );
159
160
		// Set the default options.
161
		$plan = wp_parse_args(
162
			$plan,
163
			array(
0 ignored issues
show
Documentation introduced by
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...
164
				'product_slug' => 'jetpack_free',
165
				'class'        => 'free',
166
				'features'     => array(
167
					'active' => array(),
168
				),
169
			)
170
		);
171
172
		list( $plan['class'], $supports ) = self::get_class_and_features( $plan['product_slug'] );
173
174
		// get available features.
175
		foreach ( Jetpack::get_available_modules() as $module_slug ) {
176
			$module = Jetpack::get_module( $module_slug );
177
			if ( ! isset( $module ) || ! is_array( $module ) ) {
178
				continue;
179
			}
180
			if ( in_array( 'free', $module['plan_classes'], true ) || in_array( $plan['class'], $module['plan_classes'], true ) ) {
181
				$supports[] = $module_slug;
182
			}
183
		}
184
185
		$plan['supports'] = $supports;
186
187
		self::$active_plan_cache = $plan;
188
189
		return $plan;
190
	}
191
192
	/**
193
	 * Get the class of plan and a list of features it supports
194
	 *
195
	 * @param string $plan_slug The plan that we're interested in.
196
	 * @return array Two item array, the plan class and the an array of features.
197
	 */
198
	private static function get_class_and_features( $plan_slug ) {
199
		$features = array();
200
		foreach ( self::PLAN_DATA as $class => $details ) {
201
			$features = array_merge( $features, $details['supports'] );
202
			if ( in_array( $plan_slug, $details['plans'], true ) ) {
203
				return array( $class, $features );
204
			}
205
		}
206
		return array( 'free', self::PLAN_DATA['free']['supports'] );
207
	}
208
209
	/**
210
	 * Gets the minimum plan slug that supports the given feature
211
	 *
212
	 * @param string $feature The name of the feature.
213
	 * @return string|bool The slug for the minimum plan that supports.
214
	 *  the feature or false if not found
215
	 */
216
	public static function get_minimum_plan_for_feature( $feature ) {
217
		foreach ( self::PLAN_DATA as $class => $details ) {
218
			if ( in_array( $feature, $details['supports'], true ) ) {
219
				return $details['plans'][0];
220
			}
221
		}
222
		return false;
223
	}
224
225
	/**
226
	 * Determine whether the active plan supports a particular feature
227
	 *
228
	 * @uses Jetpack_Plan::get()
229
	 *
230
	 * @access public
231
	 * @static
232
	 *
233
	 * @param string $feature The module or feature to check.
234
	 *
235
	 * @return bool True if plan supports feature, false if not
236
	 */
237
	public static function supports( $feature ) {
238
		// Search product bypasses plan feature check.
239
		if ( 'search' === $feature && (bool) get_option( 'has_jetpack_search_product' ) ) {
240
			return true;
241
		}
242
243
		$plan = self::get();
244
245
		// Manually mapping WordPress.com features to Jetpack module slugs.
246
		foreach ( $plan['features']['active'] as $wpcom_feature ) {
247
			switch ( $wpcom_feature ) {
248
				case 'wordads-jetpack':
249
					// WordAds are supported for this site.
250
					if ( 'wordads' === $feature ) {
251
						return true;
252
					}
253
					break;
254
			}
255
		}
256
257
		if (
258
			in_array( $feature, $plan['supports'], true )
259
			|| in_array( $feature, $plan['features']['active'], true )
260
		) {
261
			return true;
262
		}
263
264
		return false;
265
	}
266
}
267