PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.2.1
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.2.1
5.12.0 5.11.1 5.11.0 5.10.2 5.10.1 trunk 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.3.2 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.1.0 4.1.1 4.1.2 4.1.3 4.10.0 4.11.0 4.12.0 4.13.0 4.13.2 4.13.3 4.13.4 4.13.5 4.14.0 4.14.1 4.14.2 4.15.0 4.15.1 4.15.2 4.15.3 4.2.0 4.3.0 4.3.1 4.4.1 4.4.2 4.5.0 4.6.0 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.0.6 5.0.7 5.0.8 5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.10.0 5.2.0 5.2.1 5.2.2 5.3.0 5.3.1 5.3.2 5.3.3 5.6.0 5.6.1 5.7.0 5.7.1 5.8.0 5.8.1 5.8.2
matomo / classes / WpMatomo / API.php
matomo / classes / WpMatomo Last commit date
Admin 1 year ago Commands 2 years ago Db 1 year ago Ecommerce 1 year ago Report 1 year ago Site 2 years ago TrackingCode 1 year ago Updater 4 years ago User 1 year ago Workarounds 2 years ago WpStatistics 1 year ago views 4 years ago API.php 1 year ago Access.php 4 years ago AjaxTracker.php 1 year ago Annotations.php 4 years ago Bootstrap.php 1 year ago Capabilities.php 4 years ago Compatibility.php 2 years ago Email.php 2 years ago ErrorNotice.php 2 years ago Installer.php 1 year ago Logger.php 1 year ago OptOut.php 4 years ago Paths.php 2 years ago PluginAdminOverrides.php 2 years ago PrivacyBadge.php 4 years ago RedirectOnActivation.php 4 years ago Referral.php 2 years ago Roles.php 1 year ago ScheduledTasks.php 1 year ago Settings.php 1 year ago Site.php 3 years ago TrackingCode.php 1 year ago Uninstaller.php 1 year ago Updater.php 1 year ago User.php 4 years ago
API.php
284 lines
1 <?php
2 /**
3 * Matomo - free/libre analytics platform
4 *
5 * @link https://matomo.org
6 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
7 * @package matomo
8 */
9
10 namespace WpMatomo;
11
12 use Exception;
13 use Piwik\API\Request;
14 use Piwik\API\ResponseBuilder;
15 use Piwik\Common;
16 use WP_Error;
17 use WP_REST_Request;
18
19 if ( ! defined( 'ABSPATH' ) ) {
20 exit; // if accessed directly
21 }
22 /**
23 * phpcs:disable WordPress.Security.NonceVerification.Missing
24 */
25 class API {
26 const VERSION = 'matomo/v1';
27
28 const ROUTE_HIT = 'hit';
29
30 public function register_hooks() {
31 add_action( 'rest_api_init', [ $this, 'register_routes' ] );
32 }
33
34 public function register_routes() {
35 register_rest_route(
36 self::VERSION,
37 '/' . self::ROUTE_HIT . '/',
38 [
39 'methods' => [ 'GET', 'POST' ],
40 'permission_callback' => '__return_true',
41 'callback' => [ $this, 'hit' ],
42 ]
43 );
44 $this->register_route( 'API', 'getProcessedReport' );
45 $this->register_route( 'API', 'getReportMetadata' );
46 $this->register_route( 'API', 'getMatomoVersion' );
47 $this->register_route( 'API', 'getMetadata' );
48 $this->register_route( 'API', 'getSegmentsMetadata' );
49 $this->register_route( 'API', 'getWidgetMetadata' );
50 $this->register_route( 'API', 'getRowEvolution' );
51 $this->register_route( 'API', 'getSuggestedValuesForSegment' );
52 $this->register_route( 'API', 'getSettings' );
53 $this->register_route( 'Annotations', 'add' );
54 $this->register_route( 'Annotations', 'getAll' );
55 $this->register_route( 'CoreAdminHome', 'invalidateArchivedReports' );
56 $this->register_route( 'CoreAdminHome', 'runScheduledTasks' );
57 $this->register_route( 'CoreAdminHome', 'runCronArchiving' );
58 $this->register_route( 'Dashboard', 'getDashboards' );
59 $this->register_route( 'ImageGraph', 'get' );
60 $this->register_route( 'VisitsSummary', 'getVisits' );
61 $this->register_route( 'VisitsSummary', 'getUniqueVisitors' );
62 $this->register_route( 'LanguagesManager', 'getAvailableLanguages' );
63 $this->register_route( 'LanguagesManager', 'getAvailableLanguagesInfo' );
64 $this->register_route( 'LanguagesManager', 'getAvailableLanguageNames' );
65 $this->register_route( 'LanguagesManager', 'getLanguageForUser' );
66 $this->register_route( 'Live', 'getCounters' );
67 $this->register_route( 'Live', 'getLastVisitsDetails' );
68 $this->register_route( 'Live', 'getVisitorProfile' );
69 $this->register_route( 'Live', 'getMostRecentVisitorId' );
70 $this->register_route( 'PrivacyManager', 'deleteDataSubjects' );
71 $this->register_route( 'PrivacyManager', 'exportDataSubjects' );
72 $this->register_route( 'PrivacyManager', 'anonymizeSomeRawData' );
73 $this->register_route( 'ScheduledReports', 'getReports' );
74 $this->register_route( 'ScheduledReports', 'sendReport' );
75 $this->register_route( 'SegmentEditor', 'add' );
76 $this->register_route( 'SegmentEditor', 'update' );
77 $this->register_route( 'SegmentEditor', 'delete' );
78 $this->register_route( 'SegmentEditor', 'get' );
79 $this->register_route( 'SegmentEditor', 'getAll' );
80 $this->register_route( 'SitesManager', 'getAllSites' );
81 $this->register_route( 'SitesManager', 'getAllSitesId' );
82 $this->register_route( 'SitesManager', 'getSitesIdWithAtLeastViewAccess' );
83 $this->register_route( 'UsersManager', 'getUsers' );
84 $this->register_route( 'UsersManager', 'getUsersLogin' );
85 $this->register_route( 'UsersManager', 'getUser' );
86 $this->register_route( 'Goals', 'getGoals' );
87 $this->register_route( 'VisitsSummary', 'get' );
88
89 // todo ideally we would make here work /goal/12345 to get goalId 12345
90 $this->register_route( 'Goals', 'getGoal' );
91 $this->register_route( 'Goals', 'addGoal' );
92 $this->register_route( 'Goals', 'updateGoal' );
93 $this->register_route( 'Goals', 'deleteGoal' );
94
95 $this->register_route( 'TagManager', 'getContainerTags' );
96 $this->register_route( 'TagManager', 'addContainerTag' );
97 $this->register_route( 'TagManager', 'getContainerTriggers' );
98 $this->register_route( 'TagManager', 'addContainerTrigger' );
99 $this->register_route( 'TagManager', 'getContainerVariables' );
100 $this->register_route( 'TagManager', 'addContainerVariable' );
101 $this->register_route( 'TagManager', 'addContainer' );
102 $this->register_route( 'TagManager', 'getContainer' );
103 $this->register_route( 'TagManager', 'getContainers' );
104 $this->register_route( 'TagManager', 'getContainerVersions' );
105 $this->register_route( 'TagManager', 'createContainerVersion' );
106 $this->register_route( 'TagManager', 'publishContainerVersion' );
107 }
108
109 public function hit() {
110 if ( ( empty( $_GET ) || isset( $_GET['rest_route'] ) ) && empty( $_POST ) && empty( $_POST['idsite'] ) && empty( $_GET['idsite'] ) ) {
111 // todo if uploads dir is not writable, we may want to generate the matomo.js here and save it as an
112 // option... then we could also save it compressed
113 $paths = new Paths();
114 $path = $paths->get_matomo_js_upload_path();
115 $wp_filesystem = $paths->get_file_system();
116 header( 'Content-Type: application/javascript' );
117 header( 'Content-Length: ' . ( filesize( $path ) ) );
118 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
119 echo $wp_filesystem->get_contents( $paths->get_upload_base_dir() . '/matomo.js' ); // Reading the file into the output buffer
120 exit;
121 }
122 include_once plugin_dir_path( MATOMO_ANALYTICS_FILE ) . 'app/piwik.php';
123 exit;
124 }
125
126 public function execute_api_method( WP_REST_Request $request ) {
127 $attributes = $request->get_attributes();
128 $method = $attributes['matomoModule'] . '.' . $attributes['matomoMethod'];
129
130 $with_idsite = true;
131
132 return $this->execute_request( $method, $with_idsite, $request->get_params() );
133 }
134
135 /**
136 * @param string $method
137 *
138 * @return string
139 * @internal
140 * for tests only
141 */
142 public function to_snake_case( $method ) {
143 preg_match_all( '!([A-Z][A-Z0-9]*(?=$|[A-Z][a-z0-9])|[A-Za-z][a-z0-9]+)!', $method, $matches );
144
145 $snake_case = $matches[0];
146
147 foreach ( $snake_case as &$match ) {
148 if ( strtoupper( $match ) === $match ) {
149 $match = strtolower( $match );
150 } else {
151 $match = lcfirst( $match );
152 }
153 }
154
155 return implode( '_', $snake_case );
156 }
157
158 /**
159 * @api
160 */
161 public function register_route( $api_module, $api_method ) {
162 $methods = [
163 'get' => 'GET',
164 'edit' => 'PUT',
165 'update' => 'PUT',
166 'create' => 'POST',
167 'add' => 'POST',
168 'anonymize' => 'POST',
169 'invalidate' => 'POST',
170 'run' => 'POST',
171 'send' => 'POST',
172 'publish' => 'POST',
173 'delete' => 'DELETE',
174 'remove' => 'DELETE',
175 ];
176 $starts_with_keep_prefix = [ 'anonymize', 'invalidate', 'run', 'send', 'publish' ];
177
178 $method = 'GET';
179 $wp_api_module = $this->to_snake_case( $api_module );
180 $wp_api_action = $this->to_snake_case( $api_method );
181
182 foreach ( $methods as $method_starts_with => $method_to_use ) {
183 if ( strpos( $api_method, $method_starts_with ) === 0 ) {
184 $method = $method_to_use;
185 if ( ! in_array( $method_starts_with, $starts_with_keep_prefix, true ) ) {
186 $new_action = trim( ltrim( substr( $wp_api_action, strlen( $method_starts_with ) ), '_' ) );
187 if ( ! empty( $new_action ) ) {
188 $wp_api_action = $new_action;
189 }
190 }
191 break;
192 }
193 }
194
195 $methods = [ $method ];
196 if ( ! in_array( 'POST', $methods, true ) ) {
197 // we allow posting to all methods so users can pass the app password as the token_auth
198 // instead of as a header if needed
199 $methods[] = 'POST';
200 }
201
202 register_rest_route(
203 self::VERSION,
204 '/' . $wp_api_module . '/' . $wp_api_action . '/',
205 [
206 'methods' => $methods,
207 'callback' => [ $this, 'execute_api_method' ],
208 'permission_callback' => '__return_true', // permissions are checked in the method itself
209 'matomoModule' => $api_module,
210 'matomoMethod' => $api_method,
211 ]
212 );
213 }
214
215 private function execute_request( $api_method, $with_idsite, $params ) {
216 if ( $with_idsite ) {
217 $site = new Site();
218 $idsite = $site->get_current_matomo_site_id();
219
220 if ( ! $idsite ) {
221 return new WP_Error( 'Site not found. Make sure it is synced' );
222 }
223
224 $params['idSite'] = $idsite;
225 $params['idsite'] = $idsite;
226 $params['idsites'] = $idsite;
227 $params['idSites'] = $idsite;
228 }
229
230 // ensure user is authenticated through WordPress!
231 unset( $_GET['token_auth'] );
232 unset( $_POST['token_auth'] );
233
234 Bootstrap::do_bootstrap();
235
236 // refs https://github.com/matomo-org/matomo-for-wordpress/issues/370 ensuring segment will be used from default request when
237 // creating new request object and not the encoded segment
238 if ( isset( $params['segment'] ) ) {
239 if ( isset( $_GET['segment'] ) || isset( $_POST['segment'] ) ) {
240 unset( $params['segment'] ); // matomo will read the segment from default request
241 } elseif ( ! empty( $params['segment'] ) && is_string( $params['segment'] ) ) {
242 // manually unsanitize this value
243 $params['segment'] = Common::unsanitizeInputValue( $params['segment'] );
244 }
245 }
246
247 $output_format = empty( $params['format'] ) ? 'json' : $params['format'];
248 $params['format'] = 'original';
249
250 try {
251 $result = Request::processRequest( $api_method, $params );
252
253 $response_builder = new ResponseBuilder( $output_format, $params );
254 $response_builder->disableDataTablePostProcessor(); // done within Request, we don't need to use it again
255
256 $result = $response_builder->getResponse( $result );
257
258 if ( 'json' === $output_format ) {
259 // WordPress always JSON encodes the result of REST API methods, so sending format=json to Matomo
260 // results in double JSON encoding the result. so if format=json is detected, we have to parse the
261 // the JSON before handing it over to WordPress.
262 $result = json_decode( $result, true );
263
264 // scalar values are returned as is currently
265 if ( array_key_exists( 'value', $result ) && count( $result ) === 1 ) {
266 $result = $result['value'];
267 }
268 }
269 } catch ( Exception $e ) {
270 $code = 'matomo_error';
271 if ( $e->getCode() ) {
272 $code .= '_' . $code;
273 }
274 if ( get_class( $e ) !== 'Exception' ) {
275 $code = str_replace( 'piwik', 'matomo', $this->to_snake_case( get_class( $e ) ) );
276 }
277
278 return new WP_Error( $code, $e->getMessage() );
279 }
280
281 return $result;
282 }
283 }
284