PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-a.3
Jetpack – WP Security, Backup, Speed, & Growth v16.3-a.3
16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 All 506 releases
jetpack / jetpack_vendor / automattic / jetpack-licensing / src / class-licensing.php

class-licensing.php in Jetpack – WP Security, Backup, Speed, & Growth 16.3-a.3, at jetpack_vendor/automattic/jetpack-licensing/src/class-licensing.php

391 lines 9.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * A Terms of Service class for Jetpack.
4 *
5 * @package automattic/jetpack-licensing
6 */
7
8 namespace Automattic\Jetpack;
9
10 use Automattic\Jetpack\Connection\Manager as Connection_Manager;
11 use Automattic\Jetpack\Licensing\Endpoints;
12 use Jetpack_IXR_ClientMulticall;
13 use Jetpack_Options;
14 use WP_Error;
15
16 /**
17 * Class Licensing.
18 * Helper class that is responsible for attaching licenses to the current site.
19 *
20 * @since 1.1.1
21 */
22 class Licensing {
23 /**
24 * Name of the WordPress option that holds all known Jetpack licenses.
25 *
26 * @const string
27 */
28 const LICENSES_OPTION_NAME = 'jetpack_licenses';
29
30 /**
31 * Name of the WordPress transient that holds the last license attaching error, if any.
32 *
33 * @const string
34 */
35 const ERROR_TRANSIENT_NAME = 'jetpack_licenses_error';
36
37 /**
38 * Holds the singleton instance of this class.
39 *
40 * @var self
41 */
42 protected static $instance = false;
43
44 /**
45 * Singleton.
46 *
47 * @static
48 */
49 public static function instance() {
50 if ( ! self::$instance ) {
51 self::$instance = new self();
52 }
53
54 return self::$instance;
55 }
56
57 /**
58 * Initialize.
59 *
60 * @return void
61 */
62 public function initialize() {
63 add_action( 'add_option_' . self::LICENSES_OPTION_NAME, array( $this, 'attach_stored_licenses' ) );
64 add_action( 'update_option_' . self::LICENSES_OPTION_NAME, array( $this, 'attach_stored_licenses' ) );
65 add_action( 'jetpack_authorize_ending_authorized', array( $this, 'attach_stored_licenses_on_connection' ) );
66 add_action( 'rest_api_init', array( $this, 'initialize_endpoints' ) );
67 }
68
69 /**
70 * Register the hooks the package needs on every request.
71 *
72 * Separate from `initialize()`, which consumers call only when they offer the
73 * licensing UI: the activation screen also renders from the Jetpack plugin's
74 * own route, so the illustrations need a URL there too.
75 *
76 * @since 4.1.0
77 *
78 * @return void
79 */
80 public static function configure() {
81 add_filter( 'jetpack_admin_js_script_data', array( self::class, 'add_script_data' ) );
82 }
83
84 /**
85 * Add the package's image base URL to the admin script data.
86 *
87 * Other plugins' bundles request the activation screen's images from this copy, so never rename one.
88 *
89 * @since 4.1.0
90 *
91 * @param array $data Script data.
92 * @return array
93 */
94 public static function add_script_data( $data ) {
95 $data['licensing']['assetsUrl'] = trailingslashit( plugins_url( 'assets/images/', __DIR__ ) );
96
97 return $data;
98 }
99
100 /**
101 * Initialize endpoints required for Licensing package.
102 *
103 * @since 1.7.0
104 *
105 * @return void
106 */
107 public function initialize_endpoints() {
108 $endpoints = new Endpoints();
109 $endpoints->register_endpoints();
110 }
111
112 /**
113 * Get Jetpack connection manager instance.
114 *
115 * @return Connection_Manager
116 */
117 protected function connection() {
118 static $connection;
119
120 if ( null === $connection ) {
121 $connection = new Connection_Manager();
122 }
123
124 return $connection;
125 }
126
127 /**
128 * Get the last license attach request error that has occurred, if any.
129 *
130 * @return string Human-readable error message or an empty string.
131 */
132 public function last_error() {
133 return Jetpack_Options::get_option( 'licensing_error', '' );
134 }
135
136 /**
137 * Log an error to be surfaced to the user at a later time.
138 *
139 * @param string $error Human-readable error message.
140 * @return void
141 */
142 public function log_error( $error ) {
143 $substr = function_exists( 'mb_substr' ) ? 'mb_substr' : 'substr';
144 Jetpack_Options::update_option( 'licensing_error', $substr( $error, 0, 1024 ) );
145 }
146
147 /**
148 * Get all stored licenses.
149 *
150 * @return string[] License keys.
151 */
152 public function stored_licenses() {
153 $licenses = (array) get_option( self::LICENSES_OPTION_NAME, array() );
154 $licenses = array_filter( $licenses, 'is_scalar' );
155 $licenses = array_map( 'strval', $licenses );
156 $licenses = array_filter( $licenses );
157
158 return $licenses;
159 }
160
161 /**
162 * Append a license
163 *
164 * @param string $license A jetpack license key.
165 * @return bool True if the option was updated with the new license, false otherwise.
166 */
167 public function append_license( $license ) {
168 $licenses = $this->stored_licenses();
169
170 array_push( $licenses, $license );
171
172 return update_option( self::LICENSES_OPTION_NAME, $licenses );
173 }
174
175 /**
176 * Make an authenticated WP.com XMLRPC multicall request to attach the provided license keys.
177 *
178 * @param string[] $licenses License keys to attach.
179 * @return Jetpack_IXR_ClientMulticall
180 */
181 protected function attach_licenses_request( array $licenses ) {
182 $xml = new Jetpack_IXR_ClientMulticall( array( 'timeout' => 30 ) );
183
184 foreach ( $licenses as $license ) {
185 $xml->addCall( 'jetpack.attachLicense', $license );
186 }
187
188 $xml->query();
189
190 return $xml;
191 }
192
193 /**
194 * Attach the given licenses.
195 *
196 * @param string[] $licenses Licenses to attach.
197 * @return array|WP_Error Results for each license (which may include WP_Error instances) or a WP_Error instance.
198 */
199 public function attach_licenses( array $licenses ) {
200 if ( ! $this->connection()->has_connected_owner() ) {
201 return new WP_Error( 'not_connected', __( 'Jetpack doesn\'t have a connected owner.', 'jetpack-licensing' ) );
202 }
203
204 if ( empty( $licenses ) ) {
205 return array();
206 }
207
208 $xml = $this->attach_licenses_request( $licenses );
209
210 if ( $xml->isError() ) {
211 $error = new WP_Error( 'request_failed', __( 'License attach request failed.', 'jetpack-licensing' ) );
212 $error->add( $xml->getErrorCode(), $xml->getErrorMessage() );
213 return $error;
214 }
215
216 $results = array_map(
217 function ( $response ) {
218 if ( isset( $response['faultCode'] ) || isset( $response['faultString'] ) ) {
219 return new WP_Error( $response['faultCode'], $response['faultString'] );
220 }
221
222 return $response;
223 },
224 (array) $xml->getResponse()
225 );
226
227 return $results;
228 }
229
230 /**
231 * Attach all stored licenses.
232 *
233 * @return array|WP_Error Results for each license (which may include WP_Error instances) or a WP_Error instance.
234 */
235 public function attach_stored_licenses() {
236 $licenses = $this->stored_licenses();
237 $results = $this->attach_licenses( $licenses );
238
239 if ( is_wp_error( $results ) ) {
240 if ( 'request_failed' === $results->get_error_code() ) {
241 $this->log_error(
242 __( 'Failed to attach your Jetpack license(s). Please try reconnecting Jetpack.', 'jetpack-licensing' )
243 );
244 }
245
246 return $results;
247 }
248
249 $failed = array();
250
251 foreach ( $results as $index => $result ) {
252 if ( isset( $licenses[ $index ] ) && is_wp_error( $result ) ) {
253 $failed[] = $licenses[ $index ];
254 }
255 }
256
257 if ( ! empty( $failed ) ) {
258 $this->log_error(
259 sprintf(
260 /* translators: %s is a comma-separated list of license keys. */
261 __( 'The following Jetpack licenses are invalid, already in use, or revoked: %s', 'jetpack-licensing' ),
262 implode( ', ', $failed )
263 )
264 );
265 }
266
267 return $results;
268 }
269
270 /**
271 * Attach all stored licenses during connection flow for the connection owner.
272 *
273 * @return void
274 */
275 public function attach_stored_licenses_on_connection() {
276 if ( $this->connection()->is_connection_owner() ) {
277 $this->attach_stored_licenses();
278 }
279 }
280
281 /**
282 * Is the current user allowed to use the Licensing Input UI?
283 *
284 * @since 1.4.0
285 * @return bool
286 */
287 public static function is_licensing_input_enabled() {
288 /**
289 * Filter that checks if the user is allowed to see the Licensing UI. `true` enables it.
290 *
291 * @since 1.4.0
292 *
293 * @param bool False by default.
294 */
295 return apply_filters( 'jetpack_licensing_ui_enabled', false ) && current_user_can( 'jetpack_connect_user' );
296 }
297
298 /**
299 * Gets the user-licensing activation notice dismissal info.
300 *
301 * @since 10.4.0
302 * @return array
303 */
304 public function get_license_activation_notice_dismiss() {
305
306 $default = array(
307 'last_detached_count' => null,
308 'last_dismissed_time' => null,
309 );
310
311 if ( $this->connection()->is_user_connected() && $this->connection()->is_connection_owner() ) {
312 return Jetpack_Options::get_option( 'licensing_activation_notice_dismiss', $default );
313 }
314
315 return $default;
316 }
317
318 /**
319 * Load current user's licenses.
320 *
321 * @param bool $unattached_only Only return unattached and not revoked licenses.
322 *
323 * @return array
324 */
325 public function get_user_licenses( $unattached_only = false ) {
326 $licenses = Endpoints::get_user_licenses();
327
328 if ( empty( $licenses->items ) ) {
329 return array();
330 }
331
332 $items = $licenses->items;
333
334 if ( $unattached_only ) {
335 $items = array_filter(
336 $items,
337 static function ( $item ) {
338 return $item->attached_at === null && $item->revoked_at === null;
339 }
340 );
341 }
342
343 return $items;
344 }
345
346 /**
347 * If the destination URL is checkout page,
348 * see if there are unattached licenses they could use instead of getting a new one.
349 * If found, redirect the user to license activation.
350 *
351 * @param string $dest_url User's destination URL.
352 *
353 * @return void
354 */
355 public function handle_user_connected_redirect( $dest_url ) {
356 if ( ! preg_match( '#^https://[^/]+/checkout/#i', $dest_url ) ) {
357 return;
358 }
359
360 $licenses = $this->get_user_licenses( true );
361 $plugin_slug = null;
362
363 $query_string = wp_parse_url( $dest_url, PHP_URL_QUERY );
364 if ( $query_string ) {
365 parse_str( $query_string, $query_args );
366
367 if ( $query_args['redirect_to']
368 && preg_match( '/^admin\.php\?page=(jetpack-\w+)/i', $query_args['redirect_to'], $matches )
369 ) {
370 $plugin_slug = $matches[1];
371 }
372 }
373
374 /**
375 * Check for the user's unattached licenses.
376 *
377 * @since 3.8.2
378 *
379 * @param bool $has_license Whether a license was already found.
380 * @param array $licenses Unattached licenses belonging to the user.
381 * @param string $plugin_slug Slug of the plugin that initiated the flow.
382 */
383 if ( $plugin_slug && count( $licenses )
384 && apply_filters( 'jetpack_connection_user_has_license', false, $licenses, $plugin_slug )
385 ) {
386 wp_safe_redirect( '/wp-admin/admin.php?page=my-jetpack#/add-license' );
387 exit( 0 );
388 }
389 }
390 }
391