1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Jetpack XMLRPC Methods. |
4
|
|
|
* |
5
|
|
|
* Registers the Jetpack specific XMLRPC methods |
6
|
|
|
* |
7
|
|
|
* @package jetpack |
8
|
|
|
*/ |
9
|
|
|
|
10
|
|
|
use Automattic\Jetpack\Connection\Manager as Connection_Manager; |
11
|
|
|
use Automattic\Jetpack\Connection\Tokens; |
12
|
|
|
|
13
|
|
|
/** |
14
|
|
|
* XMLRPC Methods registration and callbacks |
15
|
|
|
*/ |
16
|
|
|
class Jetpack_XMLRPC_Methods { |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* Initialize the main hooks. |
20
|
|
|
*/ |
21
|
|
|
public static function init() { |
22
|
|
|
add_filter( 'jetpack_xmlrpc_unauthenticated_methods', array( __CLASS__, 'xmlrpc_methods' ) ); |
23
|
|
|
add_filter( 'jetpack_xmlrpc_test_connection_response', array( __CLASS__, 'test_connection' ) ); |
24
|
|
|
} |
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* Adds Jetpack specific methods to the methods added by the Connection package. |
28
|
|
|
* |
29
|
|
|
* @param array $methods Methods added by the Connection package. |
30
|
|
|
*/ |
31
|
|
|
public static function xmlrpc_methods( $methods ) { |
32
|
|
|
|
33
|
|
|
$methods['jetpack.featuresAvailable'] = array( __CLASS__, 'features_available' ); |
34
|
|
|
$methods['jetpack.featuresEnabled'] = array( __CLASS__, 'features_enabled' ); |
35
|
|
|
$methods['jetpack.disconnectBlog'] = array( __CLASS__, 'disconnect_blog' ); |
36
|
|
|
$methods['jetpack.getPurchaseToken'] = array( __CLASS__, 'get_purchase_token' ); |
37
|
|
|
$methods['jetpack.deletePurchaseToken'] = array( __CLASS__, 'delete_purchase_token' ); |
38
|
|
|
$methods['jetpack.jsonAPI'] = array( __CLASS__, 'json_api' ); |
39
|
|
|
|
40
|
|
|
return $methods; |
41
|
|
|
} |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* Returns what features are available. Uses the slug of the module files. |
45
|
|
|
* |
46
|
|
|
* @return array |
47
|
|
|
*/ |
48
|
|
View Code Duplication |
public static function features_available() { |
49
|
|
|
$raw_modules = Jetpack::get_available_modules(); |
50
|
|
|
$modules = array(); |
51
|
|
|
foreach ( $raw_modules as $module ) { |
52
|
|
|
$modules[] = Jetpack::get_module_slug( $module ); |
53
|
|
|
} |
54
|
|
|
|
55
|
|
|
return $modules; |
56
|
|
|
} |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* Returns what features are enabled. Uses the slug of the modules files. |
60
|
|
|
* |
61
|
|
|
* @return array |
62
|
|
|
*/ |
63
|
|
View Code Duplication |
public static function features_enabled() { |
64
|
|
|
$raw_modules = Jetpack::get_active_modules(); |
65
|
|
|
$modules = array(); |
66
|
|
|
foreach ( $raw_modules as $module ) { |
67
|
|
|
$modules[] = Jetpack::get_module_slug( $module ); |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
return $modules; |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* Filters the result of test_connection XMLRPC method |
75
|
|
|
* |
76
|
|
|
* @return string The current Jetpack version number |
77
|
|
|
*/ |
78
|
|
|
public static function test_connection() { |
79
|
|
|
return JETPACK__VERSION; |
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
/** |
83
|
|
|
* Disconnect this blog from the connected wordpress.com account |
84
|
|
|
* |
85
|
|
|
* @return boolean |
86
|
|
|
*/ |
87
|
|
|
public static function disconnect_blog() { |
88
|
|
|
|
89
|
|
|
/** |
90
|
|
|
* Fired when we want to log an event to the Jetpack event log. |
91
|
|
|
* |
92
|
|
|
* @since 7.7.0 |
93
|
|
|
* |
94
|
|
|
* @param string $code Unique name for the event. |
95
|
|
|
* @param string $data Optional data about the event. |
96
|
|
|
*/ |
97
|
|
|
do_action( 'jetpack_event_log', 'disconnect' ); |
98
|
|
|
Jetpack::disconnect(); |
99
|
|
|
|
100
|
|
|
return true; |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
/** |
104
|
|
|
* Returns a purchase token used for site-connected (non user-authenticated) checkout. |
105
|
|
|
* |
106
|
|
|
* @return array The current purchase token |
107
|
|
|
*/ |
108
|
|
|
public static function get_purchase_token() { |
109
|
|
|
$blog_id = Jetpack_Options::get_option( 'id' ); |
110
|
|
|
if ( ! $blog_id ) { |
111
|
|
|
return new WP_Error( 'site_not_registered', esc_html__( 'Site not registered.', 'jetpack' ) ); |
|
|
|
|
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
$purchase_token = Jetpack_Options::get_option( 'purchase_token', false ); |
115
|
|
|
$response = array( |
116
|
|
|
'purchaseToken' => $purchase_token, |
117
|
|
|
); |
118
|
|
|
|
119
|
|
|
return $response; |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
/** |
123
|
|
|
* Deletes the purchaseToken Jetpack_Option |
124
|
|
|
* |
125
|
|
|
* @return boolean |
126
|
|
|
*/ |
127
|
|
|
public static function delete_purchase_token() { |
128
|
|
|
$blog_id = Jetpack_Options::get_option( 'id' ); |
129
|
|
|
if ( ! $blog_id ) { |
130
|
|
|
return new WP_Error( 'site_not_registered', esc_html__( 'Site not registered.', 'jetpack' ) ); |
|
|
|
|
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
return Jetpack_Options::delete_option( 'purchase_token' ); |
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
/** |
137
|
|
|
* Serve a JSON API request. |
138
|
|
|
* |
139
|
|
|
* @param array $args request arguments. |
140
|
|
|
*/ |
141
|
|
|
public static function json_api( $args = array() ) { |
142
|
|
|
$json_api_args = $args[0]; |
143
|
|
|
$verify_api_user_args = $args[1]; |
144
|
|
|
|
145
|
|
|
$method = (string) $json_api_args[0]; |
146
|
|
|
$url = (string) $json_api_args[1]; |
147
|
|
|
$post_body = is_null( $json_api_args[2] ) ? null : (string) $json_api_args[2]; |
148
|
|
|
$user_details = (array) $json_api_args[4]; |
149
|
|
|
$locale = (string) $json_api_args[5]; |
150
|
|
|
|
151
|
|
|
if ( ! $verify_api_user_args ) { |
152
|
|
|
$user_id = 0; |
153
|
|
|
} elseif ( 'internal' === $verify_api_user_args[0] ) { |
154
|
|
|
$user_id = (int) $verify_api_user_args[1]; |
155
|
|
|
if ( $user_id ) { |
156
|
|
|
$user = get_user_by( 'id', $user_id ); |
157
|
|
|
if ( ! $user || is_wp_error( $user ) ) { |
158
|
|
|
return false; |
159
|
|
|
} |
160
|
|
|
} |
161
|
|
|
} else { |
162
|
|
|
$user_id = call_user_func( array( new Jetpack_XMLRPC_Server(), 'test_api_user_code' ), $verify_api_user_args ); |
163
|
|
|
if ( ! $user_id ) { |
164
|
|
|
return false; |
165
|
|
|
} |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
if ( 'en' !== $locale ) { |
169
|
|
|
// .org mo files are named slightly different from .com, and all we have is this the locale -- try to guess them. |
170
|
|
|
$new_locale = $locale; |
171
|
|
|
if ( strpos( $locale, '-' ) !== false ) { |
172
|
|
|
$locale_pieces = explode( '-', $locale ); |
173
|
|
|
$new_locale = $locale_pieces[0]; |
174
|
|
|
$new_locale .= ( ! empty( $locale_pieces[1] ) ) ? '_' . strtoupper( $locale_pieces[1] ) : ''; |
175
|
|
|
} else { |
176
|
|
|
// .com might pass 'fr' because thats what our language files are named as, where core seems |
177
|
|
|
// to do fr_FR - so try that if we don't think we can load the file. |
178
|
|
|
if ( ! file_exists( WP_LANG_DIR . '/' . $locale . '.mo' ) ) { |
179
|
|
|
$new_locale = $locale . '_' . strtoupper( $locale ); |
180
|
|
|
} |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
if ( file_exists( WP_LANG_DIR . '/' . $new_locale . '.mo' ) ) { |
184
|
|
|
unload_textdomain( 'default' ); |
185
|
|
|
load_textdomain( 'default', WP_LANG_DIR . '/' . $new_locale . '.mo' ); |
186
|
|
|
} |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
$old_user = wp_get_current_user(); |
190
|
|
|
wp_set_current_user( $user_id ); |
191
|
|
|
|
192
|
|
|
if ( $user_id ) { |
193
|
|
|
$token_key = false; |
194
|
|
|
} else { |
195
|
|
|
$verified = ( new Connection_Manager() )->verify_xml_rpc_signature(); |
196
|
|
|
$token_key = $verified['token_key']; |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
$token = ( new Tokens() )->get_access_token( $user_id, $token_key ); |
200
|
|
|
if ( ! $token || is_wp_error( $token ) ) { |
201
|
|
|
return false; |
202
|
|
|
} |
203
|
|
|
|
204
|
|
|
define( 'REST_API_REQUEST', true ); |
205
|
|
|
define( 'WPCOM_JSON_API__BASE', 'public-api.wordpress.com/rest/v1' ); |
206
|
|
|
|
207
|
|
|
// needed? |
208
|
|
|
require_once ABSPATH . 'wp-admin/includes/admin.php'; |
209
|
|
|
|
210
|
|
|
require_once JETPACK__PLUGIN_DIR . 'class.json-api.php'; |
211
|
|
|
$api = WPCOM_JSON_API::init( $method, $url, $post_body ); |
212
|
|
|
$api->token_details['user'] = $user_details; |
213
|
|
|
require_once JETPACK__PLUGIN_DIR . 'class.json-api-endpoints.php'; |
214
|
|
|
|
215
|
|
|
$display_errors = ini_set( 'display_errors', 0 ); // phpcs:ignore WordPress.PHP.IniSet |
216
|
|
|
ob_start(); |
217
|
|
|
$api->serve( false ); |
218
|
|
|
$output = ob_get_clean(); |
219
|
|
|
ini_set( 'display_errors', $display_errors ); // phpcs:ignore WordPress.PHP.IniSet |
220
|
|
|
|
221
|
|
|
$nonce = wp_generate_password( 10, false ); |
222
|
|
|
$hmac = hash_hmac( 'md5', $nonce . $output, $token->secret ); |
223
|
|
|
|
224
|
|
|
wp_set_current_user( isset( $old_user->ID ) ? $old_user->ID : 0 ); |
225
|
|
|
|
226
|
|
|
return array( |
227
|
|
|
(string) $output, |
228
|
|
|
(string) $nonce, |
229
|
|
|
(string) $hmac, |
230
|
|
|
); |
231
|
|
|
} |
232
|
|
|
} |
233
|
|
|
|
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.