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