PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / trunk
Matomo Analytics – Powerful, Privacy-First Insights for WordPress vtrunk
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 11 hours ago Commands 2 years ago Db 11 hours ago Ecommerce 1 month ago Report 3 months ago Site 3 months ago TrackingCode 4 months ago Updater 4 years ago User 11 hours ago Workarounds 2 years ago WpStatistics 6 months ago views 2 months ago AIBotTracking.php 4 months ago API.php 11 hours ago Access.php 4 years ago AjaxTracker.php 4 months ago Annotations.php 3 months ago Bootstrap.php 10 months ago Capabilities.php 1 month ago Compatibility.php 3 months ago Email.php 2 years ago ErrorNotice.php 2 months ago Feature.php 3 months ago Installer.php 2 months ago Logger.php 1 year ago MinimumRequirementsNotice.php 1 month ago OptOut.php 1 month ago Paths.php 6 months ago PluginActionLinks.php 3 months ago PluginAdminOverrides.php 3 months ago PluginInit.php 3 months ago PrivacyBadge.php 4 years ago RedirectOnActivation.php 3 months ago Referral.php 3 months ago Roles.php 3 months ago ScheduledTasks.php 3 months ago Settings.php 4 months ago Site.php 3 years ago TrackingCode.php 3 months ago Uninstaller.php 6 months ago Updater.php 10 months ago User.php 11 hours 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