PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.0.7
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.0.7
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 years ago Commands 2 years ago Db 2 years ago Ecommerce 2 years ago Report 2 years ago Site 2 years ago TrackingCode 2 years ago Updater 4 years ago User 2 years ago Workarounds 2 years ago WpStatistics 2 years ago views 4 years ago API.php 2 years ago Access.php 4 years ago AjaxTracker.php 2 years ago Annotations.php 4 years ago Bootstrap.php 2 years ago Capabilities.php 4 years ago Compatibility.php 2 years ago Email.php 2 years ago ErrorNotice.php 2 years ago Installer.php 2 years ago Logger.php 2 years 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 4 years ago Roles.php 4 years ago ScheduledTasks.php 2 years ago Settings.php 2 years ago Site.php 3 years ago TrackingCode.php 4 years ago Uninstaller.php 2 years ago Updater.php 2 years ago User.php 4 years ago
API.php
275 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( 'UsersManager', 'getUsers' );
83 $this->register_route( 'UsersManager', 'getUsersLogin' );
84 $this->register_route( 'UsersManager', 'getUser' );
85 $this->register_route( 'Goals', 'getGoals' );
86
87 // todo ideally we would make here work /goal/12345 to get goalId 12345
88 $this->register_route( 'Goals', 'getGoal' );
89 $this->register_route( 'Goals', 'addGoal' );
90 $this->register_route( 'Goals', 'updateGoal' );
91 $this->register_route( 'Goals', 'deleteGoal' );
92
93 $this->register_route( 'TagManager', 'getContainerTags' );
94 $this->register_route( 'TagManager', 'addContainerTag' );
95 $this->register_route( 'TagManager', 'getContainerTriggers' );
96 $this->register_route( 'TagManager', 'addContainerTrigger' );
97 $this->register_route( 'TagManager', 'getContainerVariables' );
98 $this->register_route( 'TagManager', 'addContainerVariable' );
99 $this->register_route( 'TagManager', 'addContainer' );
100 $this->register_route( 'TagManager', 'getContainer' );
101 $this->register_route( 'TagManager', 'getContainers' );
102 $this->register_route( 'TagManager', 'getContainerVersions' );
103 $this->register_route( 'TagManager', 'createContainerVersion' );
104 $this->register_route( 'TagManager', 'publishContainerVersion' );
105 }
106
107 public function hit() {
108 if ( ( empty( $_GET ) || isset( $_GET['rest_route'] ) ) && empty( $_POST ) && empty( $_POST['idsite'] ) && empty( $_GET['idsite'] ) ) {
109 // todo if uploads dir is not writable, we may want to generate the matomo.js here and save it as an
110 // option... then we could also save it compressed
111 $paths = new Paths();
112 $path = $paths->get_matomo_js_upload_path();
113 $wp_filesystem = $paths->get_file_system();
114 header( 'Content-Type: application/javascript' );
115 header( 'Content-Length: ' . ( filesize( $path ) ) );
116 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
117 echo $wp_filesystem->get_contents( $paths->get_upload_base_dir() . '/matomo.js' ); // Reading the file into the output buffer
118 exit;
119 }
120 include_once plugin_dir_path( MATOMO_ANALYTICS_FILE ) . 'app/piwik.php';
121 exit;
122 }
123
124 public function execute_api_method( WP_REST_Request $request ) {
125 $attributes = $request->get_attributes();
126 $method = $attributes['matomoModule'] . '.' . $attributes['matomoMethod'];
127
128 $with_idsite = true;
129
130 return $this->execute_request( $method, $with_idsite, $request->get_params() );
131 }
132
133 /**
134 * @param string $method
135 *
136 * @return string
137 * @internal
138 * for tests only
139 */
140 public function to_snake_case( $method ) {
141 preg_match_all( '!([A-Z][A-Z0-9]*(?=$|[A-Z][a-z0-9])|[A-Za-z][a-z0-9]+)!', $method, $matches );
142
143 $snake_case = $matches[0];
144
145 foreach ( $snake_case as &$match ) {
146 if ( strtoupper( $match ) === $match ) {
147 $match = strtolower( $match );
148 } else {
149 $match = lcfirst( $match );
150 }
151 }
152
153 return implode( '_', $snake_case );
154 }
155
156 /**
157 * @api
158 */
159 public function register_route( $api_module, $api_method ) {
160 $methods = [
161 'get' => 'GET',
162 'edit' => 'PUT',
163 'update' => 'PUT',
164 'create' => 'POST',
165 'add' => 'POST',
166 'anonymize' => 'POST',
167 'invalidate' => 'POST',
168 'run' => 'POST',
169 'send' => 'POST',
170 'publish' => 'POST',
171 'delete' => 'DELETE',
172 'remove' => 'DELETE',
173 ];
174 $starts_with_keep_prefix = [ 'anonymize', 'invalidate', 'run', 'send', 'publish' ];
175
176 $method = 'GET';
177 $wp_api_module = $this->to_snake_case( $api_module );
178 $wp_api_action = $this->to_snake_case( $api_method );
179
180 foreach ( $methods as $method_starts_with => $method_to_use ) {
181 if ( strpos( $api_method, $method_starts_with ) === 0 ) {
182 $method = $method_to_use;
183 if ( ! in_array( $method_starts_with, $starts_with_keep_prefix, true ) ) {
184 $new_action = trim( ltrim( substr( $wp_api_action, strlen( $method_starts_with ) ), '_' ) );
185 if ( ! empty( $new_action ) ) {
186 $wp_api_action = $new_action;
187 }
188 }
189 break;
190 }
191 }
192
193 register_rest_route(
194 self::VERSION,
195 '/' . $wp_api_module . '/' . $wp_api_action . '/',
196 [
197 'methods' => $method,
198 'callback' => [ $this, 'execute_api_method' ],
199 'permission_callback' => '__return_true', // permissions are checked in the method itself
200 'matomoModule' => $api_module,
201 'matomoMethod' => $api_method,
202 ]
203 );
204 }
205
206 private function execute_request( $api_method, $with_idsite, $params ) {
207 if ( $with_idsite ) {
208 $site = new Site();
209 $idsite = $site->get_current_matomo_site_id();
210
211 if ( ! $idsite ) {
212 return new WP_Error( 'Site not found. Make sure it is synced' );
213 }
214
215 $params['idSite'] = $idsite;
216 $params['idsite'] = $idsite;
217 $params['idsites'] = $idsite;
218 $params['idSites'] = $idsite;
219 }
220
221 // ensure user is authenticated through WordPress!
222 unset( $_GET['token_auth'] );
223 unset( $_POST['token_auth'] );
224
225 Bootstrap::do_bootstrap();
226
227 // refs https://github.com/matomo-org/matomo-for-wordpress/issues/370 ensuring segment will be used from default request when
228 // creating new request object and not the encoded segment
229 if ( isset( $params['segment'] ) ) {
230 if ( isset( $_GET['segment'] ) || isset( $_POST['segment'] ) ) {
231 unset( $params['segment'] ); // matomo will read the segment from default request
232 } elseif ( ! empty( $params['segment'] ) && is_string( $params['segment'] ) ) {
233 // manually unsanitize this value
234 $params['segment'] = Common::unsanitizeInputValue( $params['segment'] );
235 }
236 }
237
238 $output_format = empty( $params['format'] ) ? 'json' : $params['format'];
239 $params['format'] = 'original';
240
241 try {
242 $result = Request::processRequest( $api_method, $params );
243
244 $response_builder = new ResponseBuilder( $output_format, $params );
245 $response_builder->disableDataTablePostProcessor(); // done within Request, we don't need to use it again
246
247 $result = $response_builder->getResponse( $result );
248
249 if ( 'json' === $output_format ) {
250 // WordPress always JSON encodes the result of REST API methods, so sending format=json to Matomo
251 // results in double JSON encoding the result. so if format=json is detected, we have to parse the
252 // the JSON before handing it over to WordPress.
253 $result = json_decode( $result, true );
254
255 // scalar values are returned as is currently
256 if ( array_key_exists( 'value', $result ) && count( $result ) === 1 ) {
257 $result = $result['value'];
258 }
259 }
260 } catch ( Exception $e ) {
261 $code = 'matomo_error';
262 if ( $e->getCode() ) {
263 $code .= '_' . $code;
264 }
265 if ( get_class( $e ) !== 'Exception' ) {
266 $code = str_replace( 'piwik', 'matomo', $this->to_snake_case( get_class( $e ) ) );
267 }
268
269 return new WP_Error( $code, $e->getMessage() );
270 }
271
272 return $result;
273 }
274 }
275