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