Completed
Push — add/publishing-tweetstorms ( 53ba9c...ada03e )
by Gary
08:38
created

Licensing::attach_licenses_request()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 1
dl 0
loc 11
rs 9.9
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;
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 9.0.0
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( 'update_option_' . self::LICENSES_OPTION_NAME, array( $this, 'attach_stored_licenses' ) );
64
		add_action( 'jetpack_authorize_ending_authorized', array( $this, 'attach_stored_licenses_on_connection' ) );
65
	}
66
67
	/**
68
	 * Get Jetpack connection manager instance.
69
	 *
70
	 * @return Connection_Manager
71
	 */
72
	protected function connection() {
73
		static $connection;
74
75
		if ( null === $connection ) {
76
			$connection = new Connection_Manager();
77
		}
78
79
		return $connection;
80
	}
81
82
	/**
83
	 * Get the last license attach request error that has occurred, if any.
84
	 *
85
	 * @return string Human-readable error message or an empty string.
86
	 */
87
	public function last_error() {
88
		return Jetpack_Options::get_option( 'licensing_error', '' );
89
	}
90
91
	/**
92
	 * Log an error to be surfaced to the user at a later time.
93
	 *
94
	 * @param string $error Human-readable error message.
95
	 * @return void
96
	 */
97
	public function log_error( $error ) {
98
		$substr = function_exists( 'mb_substr' ) ? 'mb_substr' : 'substr';
99
		Jetpack_Options::update_option( 'licensing_error', $substr( $error, 0, 1024 ) );
100
	}
101
102
	/**
103
	 * Get all stored licenses.
104
	 *
105
	 * @return string[] License keys.
106
	 */
107
	public function stored_licenses() {
108
		$licenses = (array) get_option( self::LICENSES_OPTION_NAME, array() );
109
		$licenses = array_filter( $licenses, 'is_scalar' );
110
		$licenses = array_map( 'strval', $licenses );
111
		$licenses = array_filter( $licenses );
112
113
		return $licenses;
114
	}
115
116
	/**
117
	 * Make an authenticated WP.com XMLRPC multicall request to attach the provided license keys.
118
	 *
119
	 * @param string[] $licenses License keys to attach.
120
	 * @return Jetpack_IXR_ClientMulticall
121
	 */
122
	protected function attach_licenses_request( array $licenses ) {
123
		$xml = new Jetpack_IXR_ClientMulticall();
124
125
		foreach ( $licenses as $license ) {
126
			$xml->addCall( 'jetpack.attachLicense', $license );
127
		}
128
129
		$xml->query();
130
131
		return $xml;
132
	}
133
134
	/**
135
	 * Attach the given licenses.
136
	 *
137
	 * @param string[] $licenses Licenses to attach.
138
	 * @return array|WP_Error Results for each license (which may include WP_Error instances) or a WP_Error instance.
139
	 */
140
	public function attach_licenses( array $licenses ) {
141
		if ( ! $this->connection()->is_active() ) {
142
			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...
143
		}
144
145
		if ( empty( $licenses ) ) {
146
			return array();
147
		}
148
149
		$xml = $this->attach_licenses_request( $licenses );
150
151 View Code Duplication
		if ( $xml->isError() ) {
152
			$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...
153
			$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...
154
			return $error;
155
		}
156
157
		$results = array_map(
158
			function ( $response ) {
159
				if ( isset( $response['faultCode'] ) || isset( $response['faultString'] ) ) {
160
					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...
161
				}
162
163
				return $response;
164
			},
165
			(array) $xml->getResponse()
166
		);
167
168
		return $results;
169
	}
170
171
	/**
172
	 * Attach all stored licenses.
173
	 *
174
	 * @return array|WP_Error Results for each license (which may include WP_Error instances) or a WP_Error instance.
175
	 */
176
	public function attach_stored_licenses() {
177
		$licenses = $this->stored_licenses();
178
		$results  = $this->attach_licenses( $licenses );
179
180
		if ( is_wp_error( $results ) ) {
181
			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...
182
				$this->log_error(
183
					__( 'Failed to attach your Jetpack license(s). Please try reconnecting Jetpack.', 'jetpack' )
184
				);
185
			}
186
187
			return $results;
188
		}
189
190
		$failed = array();
191
192
		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...
193
			if ( isset( $licenses[ $index ] ) && is_wp_error( $result ) ) {
194
				$failed[] = $licenses[ $index ];
195
			}
196
		}
197
198
		if ( ! empty( $failed ) ) {
199
			$this->log_error(
200
				sprintf(
201
					/* translators: %s is a comma-separated list of license keys. */
202
					__( 'The following Jetpack licenses are invalid, already in use or revoked: %s', 'jetpack' ),
203
					implode( ', ', $failed )
204
				)
205
			);
206
		}
207
208
		return $results;
209
	}
210
211
	/**
212
	 * Attach all stored licenses during connection flow for the connection owner.
213
	 *
214
	 * @return void
215
	 */
216
	public function attach_stored_licenses_on_connection() {
217
		if ( $this->connection()->is_connection_owner() ) {
218
			$this->attach_stored_licenses();
219
		}
220
	}
221
}
222