PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.12.1
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.12.1
5.12.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 week ago Commands 2 years ago Db 3 weeks ago Ecommerce 1 week ago Report 1 week ago Site 1 week ago TrackingCode 1 week ago Updater 4 years ago User 1 week ago Workarounds 2 years ago WpStatistics 1 week ago views 1 week ago AIBotTracking.php 1 week ago API.php 3 weeks ago Access.php 1 week ago AjaxTracker.php 5 months ago Annotations.php 1 week ago Bootstrap.php 11 months ago Capabilities.php 1 week ago Compatibility.php 1 week ago Email.php 1 week ago ErrorNotice.php 1 week ago Feature.php 3 months ago Installer.php 1 week ago Logger.php 1 year ago MinimumRequirementsNotice.php 1 month ago OptOut.php 2 months ago Paths.php 1 week ago PluginActionLinks.php 3 months ago PluginAdminOverrides.php 1 week ago PluginInit.php 1 week ago PrivacyBadge.php 4 years ago RedirectOnActivation.php 3 months ago Referral.php 3 months ago Roles.php 3 months ago ScheduledTasks.php 1 week ago Settings.php 1 week ago Site.php 3 years ago TrackingCode.php 1 week ago Uninstaller.php 6 months ago Updater.php 1 week ago User.php 3 weeks ago
API.php
281 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 extends Feature {
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 'set' => 'POST',
174 'delete' => 'DELETE',
175 'remove' => 'DELETE',
176 ];
177 $starts_with_keep_prefix = [ 'anonymize', 'invalidate', 'run', 'send', 'publish' ];
178
179 $method = 'GET';
180 $wp_api_module = $this->to_snake_case( $api_module );
181 $wp_api_action = $this->to_snake_case( $api_method );
182
183 foreach ( $methods as $method_starts_with => $method_to_use ) {
184 if ( strpos( $api_method, $method_starts_with ) === 0 ) {
185 $method = $method_to_use;
186 if ( ! in_array( $method_starts_with, $starts_with_keep_prefix, true ) ) {
187 $new_action = trim( ltrim( substr( $wp_api_action, strlen( $method_starts_with ) ), '_' ) );
188 if ( ! empty( $new_action ) ) {
189 $wp_api_action = $new_action;
190 }
191 }
192 break;
193 }
194 }
195
196 $methods = [ $method ];
197
198 register_rest_route(
199 self::VERSION,
200 '/' . $wp_api_module . '/' . $wp_api_action . '/',
201 [
202 'methods' => $methods,
203 'callback' => [ $this, 'execute_api_method' ],
204 'permission_callback' => '__return_true', // permissions are checked in the method itself
205 'matomoModule' => $api_module,
206 'matomoMethod' => $api_method,
207 ]
208 );
209 }
210
211 private function execute_request( $api_method, $with_idsite, $params ) {
212 if ( $with_idsite ) {
213 $site = new Site();
214 $idsite = $site->get_current_matomo_site_id();
215
216 if ( ! $idsite ) {
217 return new WP_Error( 'Site not found. Make sure it is synced' );
218 }
219
220 $params['idSite'] = $idsite;
221 $params['idsite'] = $idsite;
222 $params['idsites'] = $idsite;
223 $params['idSites'] = $idsite;
224 }
225
226 // ensure user is authenticated through WordPress!
227 unset( $_GET['token_auth'] );
228 unset( $_POST['token_auth'] );
229 unset( $params['token_auth'] );
230
231 Bootstrap::do_bootstrap();
232
233 // refs https://github.com/matomo-org/matomo-for-wordpress/issues/370 ensuring segment will be used from default request when
234 // creating new request object and not the encoded segment
235 if ( isset( $params['segment'] ) ) {
236 if ( isset( $_GET['segment'] ) || isset( $_POST['segment'] ) ) {
237 unset( $params['segment'] ); // matomo will read the segment from default request
238 } elseif ( ! empty( $params['segment'] ) && is_string( $params['segment'] ) ) {
239 // manually unsanitize this value
240 $params['segment'] = Common::unsanitizeInputValue( $params['segment'] );
241 }
242 }
243
244 $output_format = empty( $params['format'] ) ? 'json' : $params['format'];
245 $params['format'] = 'original';
246
247 try {
248 $result = Request::processRequest( $api_method, $params );
249
250 $response_builder = new ResponseBuilder( $output_format, $params );
251 $response_builder->disableDataTablePostProcessor(); // done within Request, we don't need to use it again
252
253 $result = $response_builder->getResponse( $result );
254
255 if ( 'json' === $output_format ) {
256 // WordPress always JSON encodes the result of REST API methods, so sending format=json to Matomo
257 // results in double JSON encoding the result. so if format=json is detected, we have to parse the
258 // the JSON before handing it over to WordPress.
259 $result = json_decode( $result, true );
260
261 // scalar values are returned as is currently
262 if ( array_key_exists( 'value', $result ) && count( $result ) === 1 ) {
263 $result = $result['value'];
264 }
265 }
266 } catch ( Exception $e ) {
267 $code = 'matomo_error';
268 if ( $e->getCode() ) {
269 $code .= '_' . $code;
270 }
271 if ( get_class( $e ) !== 'Exception' ) {
272 $code = str_replace( 'piwik', 'matomo', $this->to_snake_case( get_class( $e ) ) );
273 }
274
275 return new WP_Error( $code, $e->getMessage() );
276 }
277
278 return $result;
279 }
280 }
281