PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3
Jetpack – WP Security, Backup, Speed, & Growth v16.3
16.3 16.3-beta 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 All 508 releases
← All changes | jetpack_vendor/automattic/jetpack-licensing/src/class-licensing.php +390 -0 16.2-beta → 16.3 View file →
@@ -1,0 +1,390 @@
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 +}