Completed
Push — fix/cli-composer-install-vs-up... ( 629302...3e74be )
by
unknown
225:41 queued 216:20
created

Licensing::attach_stored_licenses_on_connection()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 0
dl 0
loc 5
rs 10
c 0
b 0
f 0
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 Jetpack_IXR_ClientMulticall;
12
use Jetpack_Options;
13
use WP_Error;
14
15
/**
16
 * Class Licensing.
17
 * Helper class that is responsible for attaching licenses to the current site.
18
 *
19
 * @since 9.0.0
20
 */
21
class Licensing {
22
	/**
23
	 * Name of the WordPress option that holds all known Jetpack licenses.
24
	 *
25
	 * @const string
26
	 */
27
	const LICENSES_OPTION_NAME = 'jetpack_licenses';
28
29
	/**
30
	 * Name of the WordPress transient that holds the last license attaching error, if any.
31
	 *
32
	 * @const string
33
	 */
34
	const ERROR_TRANSIENT_NAME = 'jetpack_licenses_error';
35
36
	/**
37
	 * Holds the singleton instance of this class.
38
	 *
39
	 * @var self
40
	 */
41
	protected static $instance = false;
42
43
	/**
44
	 * Singleton.
45
	 *
46
	 * @static
47
	 */
48
	public static function instance() {
49
		if ( ! self::$instance ) {
50
			self::$instance = new self();
51
		}
52
53
		return self::$instance;
54
	}
55
56
	/**
57
	 * Initialize.
58
	 *
59
	 * @return void
60
	 */
61
	public function initialize() {
62
		add_action( 'update_option_' . self::LICENSES_OPTION_NAME, array( $this, 'attach_stored_licenses' ) );
63
		add_action( 'jetpack_authorize_ending_authorized', array( $this, 'attach_stored_licenses_on_connection' ) );
64
	}
65
66
	/**
67
	 * Get Jetpack connection manager instance.
68
	 *
69
	 * @return Connection_Manager
70
	 */
71
	protected function connection() {
72
		static $connection;
73
74
		if ( null === $connection ) {
75
			$connection = new Connection_Manager();
76
		}
77
78
		return $connection;
79
	}
80
81
	/**
82
	 * Get the last license attach request error that has occurred, if any.
83
	 *
84
	 * @return string Human-readable error message or an empty string.
85
	 */
86
	public function last_error() {
87
		return Jetpack_Options::get_option( 'licensing_error', '' );
88
	}
89
90
	/**
91
	 * Log an error to be surfaced to the user at a later time.
92
	 *
93
	 * @param string $error Human-readable error message.
94
	 * @return void
95
	 */
96
	public function log_error( $error ) {
97
		$substr = function_exists( 'mb_substr' ) ? 'mb_substr' : 'substr';
98
		Jetpack_Options::update_option( 'licensing_error', $substr( $error, 0, 1024 ) );
99
	}
100
101
	/**
102
	 * Get all stored licenses.
103
	 *
104
	 * @return string[] License keys.
105
	 */
106
	public function stored_licenses() {
107
		$licenses = (array) get_option( self::LICENSES_OPTION_NAME, array() );
108
		$licenses = array_filter( $licenses, 'is_scalar' );
109
		$licenses = array_map( 'strval', $licenses );
110
		$licenses = array_filter( $licenses );
111
112
		return $licenses;
113
	}
114
115
	/**
116
	 * Append a license
117
	 *
118
	 * @param string $license A jetpack license key.
119
	 * @return bool True if the option was updated with the new license, false otherwise.
120
	 */
121
	public function append_license( $license ) {
122
		$licenses = $this->stored_licenses();
123
124
		array_push( $licenses, $license );
125
126
		return update_option( self::LICENSES_OPTION_NAME, $licenses );
127
	}
128
129
	/**
130
	 * Make an authenticated WP.com XMLRPC multicall request to attach the provided license keys.
131
	 *
132
	 * @param string[] $licenses License keys to attach.
133
	 * @return Jetpack_IXR_ClientMulticall
134
	 */
135
	protected function attach_licenses_request( array $licenses ) {
136
		$xml = new Jetpack_IXR_ClientMulticall();
137
138
		foreach ( $licenses as $license ) {
139
			$xml->addCall( 'jetpack.attachLicense', $license );
140
		}
141
142
		$xml->query();
143
144
		return $xml;
145
	}
146
147
	/**
148
	 * Attach the given licenses.
149
	 *
150
	 * @param string[] $licenses Licenses to attach.
151
	 * @return array|WP_Error Results for each license (which may include WP_Error instances) or a WP_Error instance.
152
	 */
153
	public function attach_licenses( array $licenses ) {
154
		if ( ! $this->connection()->is_active() ) {
155
			return new WP_Error( 'not_connected', __( 'Jetpack is not connected.', 'jetpack' ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'not_connected'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
156
		}
157
158
		if ( empty( $licenses ) ) {
159
			return array();
160
		}
161
162
		$xml = $this->attach_licenses_request( $licenses );
163
164 View Code Duplication
		if ( $xml->isError() ) {
165
			$error = new WP_Error( 'request_failed', __( 'License attach request failed.', 'jetpack' ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'request_failed'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
166
			$error->add( $xml->getErrorCode(), $xml->getErrorMessage() );
0 ignored issues
show
Bug introduced by
The method add() does not seem to exist on object<WP_Error>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
167
			return $error;
168
		}
169
170
		$results = array_map(
171
			function ( $response ) {
172
				if ( isset( $response['faultCode'] ) || isset( $response['faultString'] ) ) {
173
					return new WP_Error( $response['faultCode'], $response['faultString'] );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with $response['faultCode'].

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
174
				}
175
176
				return $response;
177
			},
178
			(array) $xml->getResponse()
179
		);
180
181
		return $results;
182
	}
183
184
	/**
185
	 * Attach all stored licenses.
186
	 *
187
	 * @return array|WP_Error Results for each license (which may include WP_Error instances) or a WP_Error instance.
188
	 */
189
	public function attach_stored_licenses() {
190
		$licenses = $this->stored_licenses();
191
		$results  = $this->attach_licenses( $licenses );
192
193
		if ( is_wp_error( $results ) ) {
194
			if ( 'request_failed' === $results->get_error_code() ) {
0 ignored issues
show
Bug introduced by
The method get_error_code() does not seem to exist on object<WP_Error>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
195
				$this->log_error(
196
					__( 'Failed to attach your Jetpack license(s). Please try reconnecting Jetpack.', 'jetpack' )
197
				);
198
			}
199
200
			return $results;
201
		}
202
203
		$failed = array();
204
205
		foreach ( $results as $index => $result ) {
0 ignored issues
show
Bug introduced by
The expression $results of type object<WP_Error>|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
206
			if ( isset( $licenses[ $index ] ) && is_wp_error( $result ) ) {
207
				$failed[] = $licenses[ $index ];
208
			}
209
		}
210
211
		if ( ! empty( $failed ) ) {
212
			$this->log_error(
213
				sprintf(
214
					/* translators: %s is a comma-separated list of license keys. */
215
					__( 'The following Jetpack licenses are invalid, already in use, or revoked: %s', 'jetpack' ),
216
					implode( ', ', $failed )
217
				)
218
			);
219
		}
220
221
		return $results;
222
	}
223
224
	/**
225
	 * Attach all stored licenses during connection flow for the connection owner.
226
	 *
227
	 * @return void
228
	 */
229
	public function attach_stored_licenses_on_connection() {
230
		if ( $this->connection()->is_connection_owner() ) {
231
			$this->attach_stored_licenses();
232
		}
233
	}
234
235
	/**
236
	 * Is the current user allowed to use the Licensing Input UI?
237
	 *
238
	 * @since 9.6.0
239
	 * @return bool
240
	 */
241
	public static function is_licensing_input_enabled() {
242
		/**
243
		 * Filter that checks if the user is allowed to see the Licensing UI. `true` enables it.
244
		 *
245
		 * @since 9.6.0
246
		 *
247
		 * @param bool False by default.
248
		 */
249
		return apply_filters( 'jetpack_licensing_ui_enabled', false ) && current_user_can( 'jetpack_connect_user' );
250
	}
251
}
252