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 / app / core / Plugin / Controller.php
matomo / app / core / Plugin Last commit date
ConsoleCommand 2 years ago Dimension 3 months ago API.php 6 months ago AggregatedMetric.php 2 years ago ArchivedMetric.php 1 year ago Archiver.php 6 months ago Categories.php 2 years ago ComponentFactory.php 3 months ago ComputedMetric.php 1 year ago ConsoleCommand.php 3 months ago Controller.php 3 months ago ControllerAdmin.php 3 months ago Dependency.php 1 year ago LogTablesProvider.php 2 years ago Manager.php 3 months ago Menu.php 3 months ago MetadataLoader.php 1 year ago Metric.php 3 months ago PluginException.php 1 year ago ProcessedMetric.php 3 months ago ReleaseChannels.php 3 months ago Report.php 3 months ago ReportsProvider.php 2 years ago RequestProcessors.php 4 months ago Segment.php 3 months ago SettingsProvider.php 3 months ago Tasks.php 3 months ago ThemeStyles.php 6 months ago ViewDataTable.php 3 months ago Visualization.php 1 year ago WidgetsProvider.php 3 months ago
Controller.php
935 lines
1 <?php
2
3 /**
4 * Matomo - free/libre analytics platform
5 *
6 * @link https://matomo.org
7 * @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
8 */
9 namespace Piwik\Plugin;
10
11 use Exception;
12 use Piwik\Access;
13 use Piwik\API\Proxy;
14 use Piwik\API\Request;
15 use Piwik\Request\AuthenticationToken;
16 use Piwik\Changes\Model as ChangesModel;
17 use Piwik\Changes\UserChanges;
18 use Piwik\Common;
19 use Piwik\Config as PiwikConfig;
20 use Piwik\Config\GeneralConfig;
21 use Piwik\Container\StaticContainer;
22 use Piwik\Date;
23 use Piwik\Exception\NoPrivilegesException;
24 use Piwik\Exception\NoWebsiteFoundException;
25 use Piwik\FrontController;
26 use Piwik\Menu\MenuAdmin;
27 use Piwik\Menu\MenuTop;
28 use Piwik\NoAccessException;
29 use Piwik\Notification\Manager as NotificationManager;
30 use Piwik\Period\Month;
31 use Piwik\Period;
32 use Piwik\Period\PeriodValidator;
33 use Piwik\Period\Range;
34 use Piwik\Piwik;
35 use Piwik\Plugins\CoreAdminHome\CustomLogo;
36 use Piwik\Plugins\CoreVisualizations\Visualizations\JqplotGraph\Evolution;
37 use Piwik\Plugins\LanguagesManager\LanguagesManager;
38 use Piwik\Plugins\UsersManager\Model as UsersModel;
39 use Piwik\SettingsPiwik;
40 use Piwik\Site;
41 use Piwik\Url;
42 use Piwik\Plugin;
43 use Piwik\View;
44 use Piwik\View\ViewInterface;
45 use Piwik\ViewDataTable\Factory as ViewDataTableFactory;
46 /**
47 * Base class of all plugin Controllers.
48 *
49 * Plugins that wish to add display HTML should create a Controller that either
50 * extends from this class or from {@link ControllerAdmin}. Every public method in
51 * the controller will be exposed as a controller method and can be invoked via
52 * an HTTP request.
53 *
54 * Learn more about Piwik's MVC system [here](/guides/mvc-in-piwik).
55 *
56 * ### Examples
57 *
58 * **Defining a controller**
59 *
60 * class Controller extends \Piwik\Plugin\Controller
61 * {
62 * public function index()
63 * {
64 * $view = new View("@MyPlugin/index.twig");
65 * // ... setup view ...
66 * return $view->render();
67 * }
68 * }
69 *
70 * **Linking to a controller action**
71 *
72 * <a href="?module=MyPlugin&action=index&idSite=1&period=day&date=2013-10-10">Link</a>
73 *
74 */
75 abstract class Controller
76 {
77 /**
78 * The plugin name, eg. `'Referrers'`.
79 *
80 * @var string
81 * @api
82 */
83 protected $pluginName;
84 /**
85 * The value of the **date** query parameter.
86 *
87 * @var string
88 * @api
89 */
90 protected $strDate;
91 /**
92 * The Date object created with ($strDate)[#strDate] or null if the requested date is a range.
93 *
94 * @var Date|null
95 * @api
96 */
97 protected $date;
98 /**
99 * The value of the **idSite** query parameter.
100 *
101 * @var int
102 * @api
103 */
104 protected $idSite;
105 /**
106 * The Site object created with {@link $idSite}.
107 *
108 * @var Site
109 * @api
110 */
111 protected $site = null;
112 /**
113 * The SecurityPolicy object.
114 *
115 * @var \Piwik\View\SecurityPolicy
116 * @api
117 */
118 protected $securityPolicy = null;
119 /**
120 * Constructor.
121 *
122 * @api
123 */
124 public function __construct()
125 {
126 $this->init();
127 }
128 protected function init()
129 {
130 $aPluginName = explode('\\', get_class($this));
131 $this->pluginName = $aPluginName[2];
132 $this->securityPolicy = StaticContainer::get(View\SecurityPolicy::class);
133 $date = Common::getRequestVar('date', 'yesterday', 'string');
134 try {
135 $this->idSite = Common::getRequestVar('idSite', \false, 'int');
136 $this->site = new Site($this->idSite);
137 $date = $this->getDateParameterInTimezone($date, $this->site->getTimezone());
138 $this->setDate($date);
139 } catch (Exception $e) {
140 // the date looks like YYYY-MM-DD,YYYY-MM-DD or other format
141 $this->date = null;
142 }
143 }
144 /**
145 * Helper method that converts `"today"` or `"yesterday"` to the specified timezone.
146 * If the date is absolute, ie. YYYY-MM-DD, it will not be converted to the timezone.
147 *
148 * @param string $date `'today'`, `'yesterday'`, `'YYYY-MM-DD'`
149 * @param string $timezone The timezone to use.
150 * @return Date
151 * @api
152 */
153 protected function getDateParameterInTimezone($date, $timezone)
154 {
155 $timezoneToUse = null;
156 // if the requested date is not YYYY-MM-DD, we need to ensure
157 // it is relative to the website's timezone
158 if (in_array($date, array('today', 'yesterday'))) {
159 // today is at midnight; we really want to get the time now, so that
160 // * if the website is UTC+12 and it is 5PM now in UTC, the calendar will allow to select the UTC "tomorrow"
161 // * if the website is UTC-12 and it is 5AM now in UTC, the calendar will allow to select the UTC "yesterday"
162 if ($date === 'today') {
163 $date = 'now';
164 } elseif ($date === 'yesterday') {
165 $date = 'yesterdaySameTime';
166 }
167 $timezoneToUse = $timezone;
168 }
169 return Date::factory($date, $timezoneToUse);
170 }
171 /**
172 * Sets the date to be used by all other methods in the controller.
173 * If the date has to be modified, this method should be called just after
174 * construction.
175 *
176 * @param Date $date The new Date.
177 * @return void
178 * @api
179 */
180 protected function setDate(Date $date)
181 {
182 $this->date = $date;
183 $this->strDate = $date->toString();
184 }
185 /**
186 * Returns values that are enabled for the parameter &period=
187 * @return array eg. array('day', 'week', 'month', 'year', 'range')
188 */
189 protected static function getEnabledPeriodsInUI()
190 {
191 $periodValidator = new PeriodValidator();
192 return $periodValidator->getPeriodsAllowedForUI();
193 }
194 /**
195 * @return array
196 */
197 private static function getEnabledPeriodsNames()
198 {
199 $availablePeriods = self::getEnabledPeriodsInUI();
200 $periodNames = array(
201 'day' => array('singular' => Piwik::translate('Intl_PeriodDay'), 'plural' => Piwik::translate('Intl_PeriodDays')),
202 'week' => array('singular' => Piwik::translate('Intl_PeriodWeek'), 'plural' => Piwik::translate('Intl_PeriodWeeks')),
203 'month' => array('singular' => Piwik::translate('Intl_PeriodMonth'), 'plural' => Piwik::translate('Intl_PeriodMonths')),
204 'year' => array('singular' => Piwik::translate('Intl_PeriodYear'), 'plural' => Piwik::translate('Intl_PeriodYears')),
205 // Note: plural is not used for date range
206 'range' => array('singular' => Piwik::translate('General_DateRangeInPeriodList'), 'plural' => Piwik::translate('General_DateRangeInPeriodList')),
207 );
208 $periodNames = array_intersect_key($periodNames, array_fill_keys($availablePeriods, \true));
209 return $periodNames;
210 }
211 /**
212 * Returns the name of the default method that will be called
213 * when visiting: index.php?module=PluginName without the action parameter.
214 *
215 * @return string
216 * @api
217 */
218 public function getDefaultAction()
219 {
220 return 'index';
221 }
222 /**
223 * A helper method that renders a view either to the screen or to a string.
224 *
225 * @param ViewInterface $view The view to render.
226 * @return string|void
227 */
228 protected function renderView(ViewInterface $view)
229 {
230 return $view->render();
231 }
232 /**
233 * Assigns the given variables to the template and renders it.
234 *
235 * Example:
236 *
237 * public function myControllerAction () {
238 * return $this->renderTemplate('index', array(
239 * 'answerToLife' => '42'
240 * ));
241 * }
242 *
243 * This will render the 'index.twig' file within the plugin templates folder and assign the view variable
244 * `answerToLife` to `42`.
245 *
246 * @param string $template The name of the template file. If only a name is given it will automatically use
247 * the template within the plugin folder. For instance 'myTemplate' will result in
248 * '@$pluginName/myTemplate.twig'. Alternatively you can include the full path:
249 * '@anyOtherFolder/otherTemplate'. The trailing '.twig' is not needed.
250 * @param array $variables For instance array('myViewVar' => 'myValue'). In template you can use {{ myViewVar }}
251 * @return string
252 * @since 2.5.0
253 * @api
254 */
255 protected function renderTemplate($template, array $variables = [])
256 {
257 return $this->renderTemplateAs($template, $variables);
258 }
259 /**
260 * @see {self::renderTemplate()}
261 *
262 * @param $template
263 * @param array $variables
264 * @param string|null $viewType 'basic' or 'admin'. If null, determined based on the controller instance type.
265 * @return string
266 * @throws Exception
267 */
268 protected function renderTemplateAs($template, array $variables = array(), $viewType = null)
269 {
270 if (\false === strpos($template, '@') || \false === strpos($template, '/')) {
271 $template = '@' . $this->pluginName . '/' . $template;
272 }
273 $view = new View($template);
274 $this->checkViewType($viewType);
275 if (empty($viewType)) {
276 $viewType = $this instanceof \Piwik\Plugin\ControllerAdmin ? 'admin' : 'basic';
277 }
278 // Set early so it is available for setGeneralVariables method calls
279 if (isset($variables['hideWhatIsNew'])) {
280 $view->hideWhatIsNew = $variables['hideWhatIsNew'];
281 }
282 // alternatively we could check whether the templates extends either admin.twig or dashboard.twig and based on
283 // that call the correct method. This will be needed once we unify Controller and ControllerAdmin see
284 // https://github.com/piwik/piwik/issues/6151
285 if ($this instanceof \Piwik\Plugin\ControllerAdmin && $viewType === 'admin') {
286 $this->setBasicVariablesViewAs($view, $viewType);
287 } elseif (empty($this->site) || empty($this->idSite)) {
288 $this->setBasicVariablesViewAs($view, $viewType);
289 } else {
290 $this->setGeneralVariablesViewAs($view, $viewType);
291 }
292 foreach ($variables as $key => $value) {
293 $view->{$key} = $value;
294 }
295 if (isset($view->siteName)) {
296 $view->siteNameDecoded = Common::unsanitizeInputValue($view->siteName);
297 }
298 return $view->render();
299 }
300 /**
301 * Convenience method that creates and renders a ViewDataTable for a API method.
302 *
303 * @param string|\Piwik\Plugin\Report $apiAction The name of the API action (eg, `'getResolution'`) or
304 * an instance of an report.
305 * @param bool $controllerAction The name of the Controller action name that is rendering the report. Defaults
306 * to the `$apiAction`.
307 * @throws \Exception if `$pluginName` is not an existing plugin or if `$apiAction` is not an
308 * existing method of the plugin's API.
309 * @return string|void See `$fetch`.
310 * @api
311 */
312 protected function renderReport($apiAction, $controllerAction = \false)
313 {
314 if (empty($controllerAction) && is_string($apiAction)) {
315 $report = \Piwik\Plugin\ReportsProvider::factory($this->pluginName, $apiAction);
316 if (!empty($report)) {
317 $apiAction = $report;
318 }
319 }
320 if ($apiAction instanceof \Piwik\Plugin\Report) {
321 $this->checkSitePermission();
322 $apiAction->checkIsEnabled();
323 return $apiAction->render();
324 }
325 $pluginName = $this->pluginName;
326 /** @var Proxy $apiProxy */
327 $apiProxy = Proxy::getInstance();
328 if (!$apiProxy->isExistingApiAction($pluginName, $apiAction)) {
329 throw new \Exception("Invalid action name '{$apiAction}' for '{$pluginName}' plugin.");
330 }
331 $apiAction = $apiProxy->buildApiActionName($pluginName, $apiAction);
332 if ($controllerAction !== \false) {
333 $controllerAction = $pluginName . '.' . $controllerAction;
334 }
335 $view = ViewDataTableFactory::build(null, $apiAction, $controllerAction);
336 $rendered = $view->render();
337 return $rendered;
338 }
339 /**
340 * Returns a ViewDataTable object that will render a jqPlot evolution graph
341 * for the last30 days/weeks/etc. of the current period, relative to the current date.
342 *
343 * @param string $currentModuleName The name of the current plugin.
344 * @param string $currentControllerAction The name of the action that renders the desired
345 * report.
346 * @param string $apiMethod The API method that the ViewDataTable will use to get
347 * graph data.
348 * @return ViewDataTable
349 * @api
350 */
351 protected function getLastUnitGraph($currentModuleName, $currentControllerAction, $apiMethod)
352 {
353 $view = ViewDataTableFactory::build(Evolution::ID, $apiMethod, $currentModuleName . '.' . $currentControllerAction, $forceDefault = \true);
354 $view->config->show_goals = \false;
355 return $view;
356 }
357 /**
358 * Same as {@link getLastUnitGraph()}, but will set some properties of the ViewDataTable
359 * object based on the arguments supplied.
360 *
361 * @param string $currentModuleName The name of the current plugin.
362 * @param string $currentControllerAction The name of the action that renders the desired
363 * report.
364 * @param array $columnsToDisplay The value to use for the ViewDataTable's columns_to_display config
365 * property.
366 * @param array $selectableColumns The value to use for the ViewDataTable's selectable_columns config
367 * property.
368 * @param bool|string $reportDocumentation The value to use for the ViewDataTable's documentation config
369 * property.
370 * @param string $apiMethod The API method that the ViewDataTable will use to get graph data.
371 * @return ViewDataTable
372 * @api
373 */
374 protected function getLastUnitGraphAcrossPlugins($currentModuleName, $currentControllerAction, $columnsToDisplay = \false, $selectableColumns = array(), $reportDocumentation = \false, $apiMethod = 'API.get')
375 {
376 // load translations from meta data
377 $idSite = Common::getRequestVar('idSite');
378 $period = Piwik::getPeriod();
379 $date = Piwik::getDate();
380 $meta = \Piwik\Plugins\API\API::getInstance()->getReportMetadata($idSite, $period, $date);
381 $columns = array_merge($columnsToDisplay ? $columnsToDisplay : array(), $selectableColumns);
382 $translations = array_combine($columns, $columns);
383 foreach ($meta as $reportMeta) {
384 if ($reportMeta['action'] === 'get' && !isset($reportMeta['parameters'])) {
385 foreach ($columns as $column) {
386 if (isset($reportMeta['metrics'][$column])) {
387 $translations[$column] = $reportMeta['metrics'][$column];
388 }
389 }
390 }
391 }
392 // initialize the graph and load the data
393 $view = $this->getLastUnitGraph($currentModuleName, $currentControllerAction, $apiMethod);
394 if ($columnsToDisplay !== \false) {
395 $view->config->columns_to_display = $columnsToDisplay;
396 }
397 if (property_exists($view->config, 'selectable_columns')) {
398 $view->config->selectable_columns = array_merge($view->config->selectable_columns ?: array(), $selectableColumns);
399 }
400 $view->config->translations += $translations;
401 if ($reportDocumentation) {
402 $view->config->documentation = $reportDocumentation;
403 }
404 return $view;
405 }
406 /**
407 * Returns the array of new processed parameters once the parameters are applied.
408 * For example: if you set range=last30 and date=2008-03-10,
409 * the date element of the returned array will be "2008-02-10,2008-03-10"
410 *
411 * Parameters you can set:
412 * - range: last30, previous10, etc.
413 * - date: YYYY-MM-DD, today, yesterday
414 * - period: day, week, month, year
415 *
416 * @param array $paramsToSet array( 'date' => 'last50', 'viewDataTable' =>'sparkline' )
417 * @throws \Piwik\NoAccessException
418 * @return array
419 */
420 protected function getGraphParamsModified($paramsToSet = array())
421 {
422 $period = $paramsToSet['period'] ?? Piwik::getPeriod();
423 if ($period === 'range') {
424 return $paramsToSet;
425 }
426 $range = isset($paramsToSet['range']) ? $paramsToSet['range'] : 'last30';
427 $endDate = isset($paramsToSet['date']) ? $paramsToSet['date'] : $this->strDate;
428 if (is_null($this->site)) {
429 throw new NoAccessException("Website not initialized, check that you are logged in and/or using the correct token_auth.");
430 }
431 $paramDate = Range::getRelativeToEndDate($period, $range, $endDate, $this->site);
432 $params = array_merge($paramsToSet, array('date' => $paramDate));
433 return $params;
434 }
435 /**
436 * Returns a numeric value from the API.
437 * Works only for API methods that originally returns numeric values (there is no cast here)
438 *
439 * @param string $methodToCall Name of method to call, eg. Referrers.getNumberOfDistinctSearchEngines
440 * @param bool|string $date A custom date to use when getting the value. If false, the 'date' query
441 * parameter is used.
442 *
443 * @return int|float
444 */
445 protected function getNumericValue($methodToCall, $date = \false)
446 {
447 $params = $date === \false ? array() : array('date' => $date);
448 $return = Request::processRequest($methodToCall, $params);
449 $columns = $return->getFirstRow()->getColumns();
450 return reset($columns);
451 }
452 /**
453 * Returns a URL to a sparkline image for a report served by the current plugin.
454 *
455 * The result of this URL should be used with the [sparkline()](/api-reference/Piwik/View#twig) twig function.
456 *
457 * The current site ID and period will be used.
458 *
459 * @param string $action Method name of the controller that serves the report.
460 * @param array $customParameters The array of query parameter name/value pairs that
461 * should be set in result URL.
462 * @return string The generated URL.
463 * @api
464 */
465 protected function getUrlSparkline($action, $customParameters = array())
466 {
467 $params = $this->getGraphParamsModified(array('viewDataTable' => 'sparkline', 'action' => $action, 'module' => $this->pluginName) + $customParameters);
468 // convert array values to comma separated
469 foreach ($params as &$value) {
470 if (is_array($value)) {
471 $value = rawurlencode(implode(',', $value));
472 }
473 }
474 $url = Url::getCurrentQueryStringWithParametersModified($params);
475 return $url;
476 }
477 /**
478 * Sets the first date available in the period selector's calendar.
479 *
480 * @param Date $minDate The min date.
481 * @param View $view The view that contains the period selector.
482 * @api
483 */
484 protected function setMinDateView(Date $minDate, $view)
485 {
486 $view->minDateYear = $minDate->toString('Y');
487 $view->minDateMonth = $minDate->toString('m');
488 $view->minDateDay = $minDate->toString('d');
489 }
490 /**
491 * Sets the last date available in the period selector's calendar. Usually this is just the "today" date
492 * for a site (which varies based on the timezone of a site).
493 *
494 * @param Date $maxDate The max date.
495 * @param View $view The view that contains the period selector.
496 * @api
497 */
498 protected function setMaxDateView(Date $maxDate, $view)
499 {
500 $view->maxDateYear = $maxDate->toString('Y');
501 $view->maxDateMonth = $maxDate->toString('m');
502 $view->maxDateDay = $maxDate->toString('d');
503 }
504 /**
505 * Assigns variables to {@link Piwik\View} instances that display an entire page.
506 *
507 * The following variables assigned:
508 *
509 * **date** - The value of the **date** query parameter.
510 * **idSite** - The value of the **idSite** query parameter.
511 * **rawDate** - The value of the **date** query parameter.
512 * **prettyDate** - A pretty string description of the current period.
513 * **siteName** - The current site's name.
514 * **siteMainUrl** - The URL of the current site.
515 * **startDate** - The start date of the current period. A {@link Piwik\Date} instance.
516 * **endDate** - The end date of the current period. A {@link Piwik\Date} instance.
517 * **language** - The current language's language code.
518 * **config_action_url_category_delimiter** - The value of the `[General] action_url_category_delimiter`
519 * INI config option.
520 * **topMenu** - The result of `MenuTop::getInstance()->getMenu()`.
521 *
522 * As well as the variables set by {@link setPeriodVariablesView()}.
523 *
524 * Will exit on error.
525 *
526 * @param View $view
527 * @return void
528 * @api
529 */
530 protected function setGeneralVariablesView($view)
531 {
532 $this->setGeneralVariablesViewAs($view, $viewType = null);
533 }
534 protected function setGeneralVariablesViewAs($view, $viewType)
535 {
536 $this->checkViewType($viewType);
537 if ($viewType === null) {
538 $viewType = $this instanceof \Piwik\Plugin\ControllerAdmin ? 'admin' : 'basic';
539 }
540 $view->idSite = $this->idSite;
541 $this->checkSitePermission();
542 $this->setPeriodVariablesView($view);
543 $view->siteName = $this->site->getName();
544 $view->siteMainUrl = $this->site->getMainUrl();
545 $siteTimezone = $this->site->getTimezone();
546 $datetimeMinDate = $this->site->getCreationDate()->getDatetime();
547 $minDate = Date::factory($datetimeMinDate, $siteTimezone);
548 $this->setMinDateView($minDate, $view);
549 $maxDate = Date::factory('now', $siteTimezone);
550 $this->setMaxDateView($maxDate, $view);
551 $rawDate = Piwik::getDate(GeneralConfig::getConfigValue('default_day'));
552 Period::checkDateFormat($rawDate);
553 $periodStr = Piwik::getPeriod(GeneralConfig::getConfigValue('default_period'));
554 if ($periodStr !== 'range') {
555 $date = Date::factory($this->strDate);
556 $validDate = $this->getValidDate($date, $minDate, $maxDate);
557 $period = Period\Factory::build($periodStr, $validDate);
558 if ($date->toString() !== $validDate->toString()) {
559 // we to not always change date since it could convert a strDate "today" to "YYYY-MM-DD"
560 // only change $this->strDate if it was not valid before
561 $this->setDate($validDate);
562 }
563 } else {
564 $period = new Range($periodStr, $rawDate, $siteTimezone);
565 }
566 // Setting current period start & end dates, for pre-setting the calendar when "Date Range" is selected
567 $dateStart = $period->getDateStart();
568 $dateStart = $this->getValidDate($dateStart, $minDate, $maxDate);
569 $dateEnd = $period->getDateEnd();
570 $dateEnd = $this->getValidDate($dateEnd, $minDate, $maxDate);
571 if ($periodStr === 'range') {
572 // make sure we actually display the correct calendar pretty date
573 $newRawDate = $dateStart->toString() . ',' . $dateEnd->toString();
574 $period = new Range($periodStr, $newRawDate, $siteTimezone);
575 }
576 $view->date = $this->strDate;
577 $view->prettyDate = self::getCalendarPrettyDate($period);
578 // prettyDateLong is not used by core, leaving in case plugins may be using it
579 $view->prettyDateLong = $period->getLocalizedLongString();
580 $view->rawDate = $rawDate;
581 $view->startDate = $dateStart;
582 $view->endDate = $dateEnd;
583 $timezoneOffsetInSeconds = Date::getUtcOffset($siteTimezone);
584 $view->timezoneOffset = $timezoneOffsetInSeconds;
585 $language = LanguagesManager::getLanguageForSession();
586 $view->language = !empty($language) ? $language : LanguagesManager::getLanguageCodeForCurrentUser();
587 $this->setBasicVariablesViewAs($view, $viewType);
588 $view->topMenu = MenuTop::getInstance()->getMenu();
589 $view->adminMenu = MenuAdmin::getInstance()->getMenu();
590 $notifications = $view->notifications;
591 if (empty($notifications)) {
592 $view->notifications = NotificationManager::getAllNotificationsToDisplay();
593 NotificationManager::cancelAllNonPersistent();
594 }
595 }
596 private function getValidDate(Date $date, Date $minDate, Date $maxDate)
597 {
598 if ($date->isEarlier($minDate)) {
599 $date = $minDate;
600 }
601 if ($date->isLater($maxDate)) {
602 $date = $maxDate;
603 }
604 return $date;
605 }
606 /**
607 * Needed when a controller extends ControllerAdmin but you don't want to call the controller admin basic variables
608 * view. Solves a problem when a controller has regular controller and admin controller views.
609 * @param View $view
610 */
611 protected function setBasicVariablesNoneAdminView($view)
612 {
613 $view->clientSideConfig = PiwikConfig::getInstance()->getClientSideOptions();
614 $view->isSuperUser = Access::getInstance()->hasSuperUserAccess();
615 $view->userCurrentRole = Access::getInstance()->getRoleForSite($this->idSite);
616 $view->hasSomeAdminAccess = Piwik::isUserHasSomeAdminAccess();
617 $view->hasSomeViewAccess = Piwik::isUserHasSomeViewAccess();
618 $view->isUserIsAnonymous = Piwik::isUserIsAnonymous();
619 $view->hasSuperUserAccess = Piwik::hasUserSuperUserAccess();
620 $view->disableTrackingMatomoAppLinks = PiwikConfig::getInstance()->General['disable_tracking_matomo_app_links'];
621 if (!Piwik::isUserIsAnonymous()) {
622 $this->showWhatIsNew($view);
623 $view->contactEmail = implode(',', Piwik::getContactEmailAddresses());
624 // for BC only. Use contactEmail instead
625 $view->emailSuperUser = implode(',', Piwik::getAllSuperUserAccessEmailAddresses());
626 }
627 $capabilities = array();
628 if ($this->idSite && $this->site) {
629 $capabilityProvider = StaticContainer::get(Access\CapabilitiesProvider::class);
630 foreach ($capabilityProvider->getAllCapabilities() as $capability) {
631 if (Piwik::isUserHasCapability($this->idSite, $capability->getId())) {
632 $capabilities[] = $capability->getId();
633 }
634 }
635 }
636 $view->userCapabilities = $capabilities;
637 $this->addCustomLogoInfo($view);
638 $customLogo = new CustomLogo();
639 $view->logoHeader = $customLogo->getHeaderLogoUrl();
640 $view->logoLarge = $customLogo->getLogoUrl();
641 $view->logoSVG = $customLogo->getSVGLogoUrl();
642 $view->hasSVGLogo = $customLogo->hasSVGLogo();
643 $view->contactEmail = implode(',', Piwik::getContactEmailAddresses());
644 $view->themeStyles = \Piwik\Plugin\ThemeStyles::get();
645 $general = PiwikConfig::getInstance()->General;
646 $view->enableFrames = $general['enable_framed_pages'] || isset($general['enable_framed_logins']) && $general['enable_framed_logins'];
647 $embeddedAsIframe = Common::getRequestVar('module', '', 'string') === 'Widgetize';
648 if (!$view->enableFrames && !$embeddedAsIframe) {
649 $view->setXFrameOptions('sameorigin');
650 }
651 $pluginManager = Plugin\Manager::getInstance();
652 $view->relativePluginWebDirs = (object) $pluginManager->getWebRootDirectoriesForCustomPluginDirs();
653 $view->pluginsToLoadOnDemand = $pluginManager->getPluginUmdsToLoadOnDemand();
654 $view->isMultiSitesEnabled = $pluginManager->isPluginActivated('MultiSites');
655 /*
656 * Executed as super user, so we are able to check if there are other sites (the current user might not have access to)
657 */
658 $view->isSingleSite = Access::doAsSuperUser(function () {
659 $allSites = Request::processRequest('SitesManager.getAllSitesId', [], []);
660 return count($allSites) === 1;
661 });
662 if (isset($this->site) && is_object($this->site) && $this->site instanceof Site) {
663 $view->siteName = $this->site->getName();
664 }
665 self::setHostValidationVariablesView($view);
666 }
667 /**
668 * Assigns a set of generally useful variables to a {@link Piwik\View} instance.
669 *
670 * The following variables assigned:
671 *
672 * **isSuperUser** - True if the current user is the Super User, false if otherwise.
673 * **hasSomeAdminAccess** - True if the current user has admin access to at least one site,
674 * false if otherwise.
675 * **isCustomLogo** - The value of the `branding_use_custom_logo` option.
676 * **logoHeader** - The header logo URL to use.
677 * **logoLarge** - The large logo URL to use.
678 * **logoSVG** - The SVG logo URL to use.
679 * **hasSVGLogo** - True if there is a SVG logo, false if otherwise.
680 * **enableFrames** - The value of the `[General] enable_framed_pages` INI config option. If
681 * true, {@link Piwik\View::setXFrameOptions()} is called on the view.
682 *
683 * Also calls {@link setHostValidationVariablesView()}.
684 *
685 * @param View $view
686 * @api
687 */
688 protected function setBasicVariablesView($view)
689 {
690 $this->setBasicVariablesViewAs($view);
691 }
692 protected function setBasicVariablesViewAs($view, $viewType = null)
693 {
694 $this->checkViewType($viewType);
695 // param is not used here, but the check can be useful for a developer
696 $this->setBasicVariablesNoneAdminView($view);
697 }
698 protected function addCustomLogoInfo($view)
699 {
700 $customLogo = new CustomLogo();
701 $view->isCustomLogo = $customLogo->isEnabled();
702 $view->customFavicon = $customLogo->getPathUserFavicon();
703 $view->hasCustomLogo = CustomLogo::hasUserLogo();
704 $view->hasCustomFavicon = CustomLogo::hasUserFavicon();
705 }
706 /**
707 * Set the template variables to show the what's new popup if appropriate
708 *
709 */
710 protected function showWhatIsNew(View $view) : void
711 {
712 $view->whatisnewShow = \false;
713 if (isset($view->hideWhatIsNew) && $view->hideWhatIsNew) {
714 return;
715 }
716 $model = new UsersModel();
717 $user = $model->getUser(Piwik::getCurrentUserLogin());
718 if (!$user) {
719 return;
720 }
721 $userChanges = new UserChanges($user);
722 $newChangesStatus = $userChanges->getNewChangesStatus();
723 $shownRecently = $userChanges->shownRecently();
724 if ($newChangesStatus == ChangesModel::NEW_CHANGES_EXIST && !$shownRecently) {
725 $view->whatisnewShow = \true;
726 }
727 }
728 /**
729 * Checks if the current host is valid and sets variables on the given view, including:
730 *
731 * - **isValidHost** - true if host is valid, false if otherwise
732 * - **invalidHostMessage** - message to display if host is invalid (only set if host is invalid)
733 * - **invalidHost** - the invalid hostname (only set if host is invalid)
734 * - **mailLinkStart** - the open tag of a link to email the Super User of this problem (only set
735 * if host is invalid)
736 *
737 * @param View $view
738 * @api
739 */
740 public static function setHostValidationVariablesView($view)
741 {
742 // check if host is valid
743 $view->isValidHost = Url::isValidHost();
744 if (!$view->isValidHost) {
745 // invalid host, so display warning to user
746 $validHosts = Url::getTrustedHostsFromConfig();
747 $validHost = $validHosts[0];
748 $invalidHost = Common::sanitizeInputValue(Url::getHost(\false));
749 $emailSubject = rawurlencode(Piwik::translate('CoreHome_InjectedHostEmailSubject', $invalidHost));
750 $emailBody = rawurlencode(Piwik::translate('CoreHome_InjectedHostEmailBody'));
751 $superUserEmail = rawurlencode(implode(',', Piwik::getContactEmailAddresses()));
752 $mailToUrl = "mailto:{$superUserEmail}?subject={$emailSubject}&body={$emailBody}";
753 $mailLinkStart = "<a href=\"{$mailToUrl}\">";
754 $invalidUrl = Url::getCurrentUrlWithoutQueryString($checkIfTrusted = \false);
755 $validUrl = Url::getCurrentScheme() . '://' . $validHost . Url::getCurrentScriptName();
756 $invalidUrl = Common::sanitizeInputValue($invalidUrl);
757 $validUrl = Common::sanitizeInputValue($validUrl);
758 $changeTrustedHostsUrl = "index.php" . Url::getCurrentQueryStringWithParametersModified(array('module' => 'CoreAdminHome', 'action' => 'generalSettings')) . "#trustedHostsSection";
759 $warningStart = Piwik::translate('CoreHome_InjectedHostWarningIntro', array('<strong>' . $invalidUrl . '</strong>', '<strong>' . $validUrl . '</strong>')) . ' <br/>';
760 if (Piwik::hasUserSuperUserAccess()) {
761 $view->invalidHostMessage = $warningStart . ' ' . Piwik::translate('CoreHome_InjectedHostSuperUserWarning', array("<a href=\"{$changeTrustedHostsUrl}\">", $invalidHost, '</a>', "<br/><a href=\"{$validUrl}\">", Common::sanitizeInputValue($validHost), '</a>'));
762 } elseif (Piwik::isUserIsAnonymous()) {
763 $view->invalidHostMessage = $warningStart . ' ' . Piwik::translate('CoreHome_InjectedHostNonSuperUserWarning', array("<br/><a href=\"{$validUrl}\">", '</a>', '<span style="display:none">', '</span>'));
764 } else {
765 $view->invalidHostMessage = $warningStart . ' ' . Piwik::translate('CoreHome_InjectedHostNonSuperUserWarning', array("<br/><a href=\"{$validUrl}\">", '</a>', $mailLinkStart, '</a>'));
766 }
767 $view->invalidHostMessageHowToFix = '<p><b>How do I fix this problem and how do I login again?</b><br/> The Matomo Super User can manually edit the file /path/to/matomo/config/config.ini.php
768 and add the following lines: <pre>[General]' . "\n" . 'trusted_hosts[] = "' . $invalidHost . '"</pre>After making the change, you will be able to login again.</p>
769 <p>You may also <i>disable this security feature (not recommended)</i>. To do so edit config/config.ini.php and add:
770 <pre>[General]' . "\n" . 'enable_trusted_host_check=0</pre>';
771 $view->invalidHost = $invalidHost;
772 // for UserSettings warning
773 $view->invalidHostMailLinkStart = $mailLinkStart;
774 }
775 }
776 /**
777 * Sets general period variables on a view, including:
778 *
779 * - **displayUniqueVisitors** - Whether unique visitors should be displayed for the current
780 * period.
781 * - **period** - The value of the **period** query parameter.
782 * - **otherPeriods** - `array('day', 'week', 'month', 'year', 'range')`
783 * - **periodsNames** - List of available periods mapped to their singular and plural translations.
784 *
785 * @param View $view
786 * @throws Exception if the current period is invalid.
787 * @api
788 */
789 public static function setPeriodVariablesView($view)
790 {
791 if (isset($view->period)) {
792 return;
793 }
794 $periodValidator = new PeriodValidator();
795 $currentPeriod = Piwik::getPeriod(GeneralConfig::getConfigValue('default_period'));
796 $availablePeriods = $periodValidator->getPeriodsAllowedForUI();
797 if (!$periodValidator->isPeriodAllowedForUI($currentPeriod)) {
798 throw new Exception("Period must be one of: " . implode(", ", $availablePeriods));
799 }
800 $view->displayUniqueVisitors = SettingsPiwik::isUniqueVisitorsEnabled($currentPeriod);
801 $found = array_search($currentPeriod, $availablePeriods);
802 unset($availablePeriods[$found]);
803 $view->period = $currentPeriod;
804 $view->otherPeriods = $availablePeriods;
805 $view->enabledPeriods = self::getEnabledPeriodsInUI();
806 $view->periodsNames = self::getEnabledPeriodsNames();
807 }
808 /**
809 * Helper method used to redirect the current HTTP request to another module/action.
810 *
811 * This function will exit immediately after executing.
812 *
813 * @param string $moduleToRedirect The plugin to redirect to, eg. `"MultiSites"`.
814 * @param string $actionToRedirect Action, eg. `"index"`.
815 * @param int|null $websiteId The new idSite query parameter, eg, `1`.
816 * @param string|null $defaultPeriod The new period query parameter, eg, `'day'`.
817 * @param string|null $defaultDate The new date query parameter, eg, `'today'`.
818 * @param array $parameters Other query parameters to append to the URL.
819 * @api
820 */
821 public function redirectToIndex($moduleToRedirect, $actionToRedirect, $websiteId = null, $defaultPeriod = null, $defaultDate = null, $parameters = array())
822 {
823 try {
824 $this->doRedirectToUrl($moduleToRedirect, $actionToRedirect, $websiteId, $defaultPeriod, $defaultDate, $parameters);
825 } catch (Exception $e) {
826 // no website ID to default to, so could not redirect
827 }
828 if (Piwik::hasUserSuperUserAccess()) {
829 $siteTableName = Common::prefixTable('site');
830 $message = "Error: no website was found in this Matomo installation.\n\t\t\t<br />Check the table '{$siteTableName}' in your database, it should contain your Matomo websites.";
831 $ex = new NoWebsiteFoundException($message);
832 $ex->setIsHtmlMessage();
833 throw $ex;
834 }
835 if (!Piwik::isUserIsAnonymous()) {
836 $currentLogin = Piwik::getCurrentUserLogin();
837 $emails = rawurlencode(implode(',', Piwik::getContactEmailAddresses()));
838 $errorMessage = sprintf(Piwik::translate('CoreHome_NoPrivilegesAskPiwikAdmin'), $currentLogin, "<br/><a href='mailto:" . $emails . "?subject=Access to Matomo for user {$currentLogin}'>", "</a>");
839 $errorMessage .= "<br /><br />&nbsp;&nbsp;&nbsp;<b><a href='index.php?module=" . Piwik::getLoginPluginName() . "&amp;action=logout'>&rsaquo; " . Piwik::translate('General_Logout') . "</a></b><br />";
840 $ex = new NoPrivilegesException($errorMessage);
841 $ex->setIsHtmlMessage();
842 throw $ex;
843 }
844 echo FrontController::getInstance()->dispatch(Piwik::getLoginPluginName(), \false);
845 exit;
846 }
847 /**
848 * Checks that the token_auth in the URL matches the currently logged-in user's token_auth.
849 *
850 * This is a protection against CSRF and should be used in all controller
851 * methods that modify Piwik or any user settings.
852 *
853 * If called from JavaScript by using the `ajaxHelper` you have to call `ajaxHelper.withTokenInUrl();` before
854 * `ajaxHandler.send();` to send the token along with the request.
855 *
856 * **The token_auth should never appear in the browser's address bar.**
857 *
858 * @throws \Piwik\NoAccessException If the token doesn't match.
859 * @api
860 */
861 protected function checkTokenInUrl()
862 {
863 $tokenRequest = StaticContainer::get(AuthenticationToken::class)->getAuthToken();
864 $tokenUser = Piwik::getCurrentUserTokenAuth();
865 if (empty($tokenRequest) && empty($tokenUser)) {
866 return;
867 // UI tests
868 }
869 if ($tokenRequest !== $tokenUser) {
870 throw new NoAccessException(Piwik::translate('General_ExceptionSecurityCheckFailed'));
871 }
872 }
873 /**
874 * Returns a prettified date string for use in period selector widget.
875 *
876 * @param Period $period The period to return a pretty string for.
877 * @return string
878 * @api
879 */
880 public static function getCalendarPrettyDate($period)
881 {
882 if ($period instanceof Month) {
883 // show month name when period is for a month
884 return $period->getLocalizedLongString();
885 } else {
886 return $period->getPrettyString();
887 }
888 }
889 /**
890 * Returns the pretty date representation
891 *
892 * @param $date string
893 * @param $period string
894 * @return string Pretty date
895 */
896 public static function getPrettyDate($date, $period)
897 {
898 return self::getCalendarPrettyDate(Period\Factory::build($period, Date::factory($date)));
899 }
900 protected function checkSitePermission()
901 {
902 if (!empty($this->idSite)) {
903 Access::getInstance()->checkUserHasViewAccess($this->idSite);
904 new Site($this->idSite);
905 } elseif (empty($this->site) || empty($this->idSite)) {
906 throw new Exception("The requested website idSite is not found in the request, or is invalid.\n\t\t\t\tPlease check that you are logged in Matomo and have permission to access the specified website.");
907 }
908 }
909 /**
910 * @param $moduleToRedirect
911 * @param $actionToRedirect
912 * @param $websiteId
913 * @param $defaultPeriod
914 * @param $defaultDate
915 * @param $parameters
916 * @throws Exception
917 */
918 private function doRedirectToUrl($moduleToRedirect, $actionToRedirect, $websiteId, $defaultPeriod, $defaultDate, $parameters)
919 {
920 $menu = new \Piwik\Plugin\Menu();
921 $parameters = array_merge($menu->urlForDefaultUserParams($websiteId, $defaultPeriod, $defaultDate), $parameters);
922 $queryParams = '&' . Url::getQueryStringFromParameters($parameters);
923 $url = "index.php?module=%s&action=%s";
924 $url = sprintf($url, $moduleToRedirect, $actionToRedirect);
925 $url = $url . $queryParams;
926 Url::redirectToUrl($url);
927 }
928 private function checkViewType($viewType)
929 {
930 if ($viewType === 'admin' && !$this instanceof \Piwik\Plugin\ControllerAdmin) {
931 throw new Exception("'admin' view type is only allowed with ControllerAdmin class.");
932 }
933 }
934 }
935