PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.2.0
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.2.0
5.11.1 5.11.0 5.10.2 5.10.1 trunk 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.3.2 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.1.0 4.1.1 4.1.2 4.1.3 4.10.0 4.11.0 4.12.0 4.13.0 4.13.2 4.13.3 4.13.4 4.13.5 4.14.0 4.14.1 4.14.2 4.15.0 4.15.1 4.15.2 4.15.3 4.2.0 4.3.0 4.3.1 4.4.1 4.4.2 4.5.0 4.6.0 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.0.6 5.0.7 5.0.8 5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.10.0 5.2.0 5.2.1 5.2.2 5.3.0 5.3.1 5.3.2 5.3.3 5.6.0 5.6.1 5.7.0 5.7.1 5.8.0 5.8.1 5.8.2
matomo / app / core / Plugin / Controller.php
matomo / app / core / Plugin Last commit date
ConsoleCommand 2 years ago Dimension 1 year ago API.php 1 year ago AggregatedMetric.php 2 years ago ArchivedMetric.php 1 year ago Archiver.php 1 year ago Categories.php 2 years ago ComponentFactory.php 2 years ago ComputedMetric.php 1 year ago ConsoleCommand.php 1 year ago Controller.php 1 year ago ControllerAdmin.php 1 year ago Dependency.php 1 year ago LogTablesProvider.php 2 years ago Manager.php 1 year ago Menu.php 1 year ago MetadataLoader.php 1 year ago Metric.php 1 year ago PluginException.php 1 year ago ProcessedMetric.php 1 year ago ReleaseChannels.php 2 years ago Report.php 1 year ago ReportsProvider.php 2 years ago RequestProcessors.php 2 years ago Segment.php 1 year ago SettingsProvider.php 2 years ago Tasks.php 1 year ago ThemeStyles.php 2 years ago ViewDataTable.php 1 year ago Visualization.php 1 year ago WidgetsProvider.php 2 years ago
Controller.php
933 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\Changes\Model as ChangesModel;
16 use Piwik\Changes\UserChanges;
17 use Piwik\Common;
18 use Piwik\Config as PiwikConfig;
19 use Piwik\Config\GeneralConfig;
20 use Piwik\Container\StaticContainer;
21 use Piwik\Date;
22 use Piwik\Exception\NoPrivilegesException;
23 use Piwik\Exception\NoWebsiteFoundException;
24 use Piwik\FrontController;
25 use Piwik\Menu\MenuAdmin;
26 use Piwik\Menu\MenuTop;
27 use Piwik\NoAccessException;
28 use Piwik\Notification\Manager as NotificationManager;
29 use Piwik\Period\Month;
30 use Piwik\Period;
31 use Piwik\Period\PeriodValidator;
32 use Piwik\Period\Range;
33 use Piwik\Piwik;
34 use Piwik\Plugins\CoreAdminHome\CustomLogo;
35 use Piwik\Plugins\CoreVisualizations\Visualizations\JqplotGraph\Evolution;
36 use Piwik\Plugins\LanguagesManager\LanguagesManager;
37 use Piwik\Plugins\UsersManager\Model as UsersModel;
38 use Piwik\SettingsPiwik;
39 use Piwik\Site;
40 use Piwik\Url;
41 use Piwik\Plugin;
42 use Piwik\View;
43 use Piwik\View\ViewInterface;
44 use Piwik\ViewDataTable\Factory as ViewDataTableFactory;
45 /**
46 * Base class of all plugin Controllers.
47 *
48 * Plugins that wish to add display HTML should create a Controller that either
49 * extends from this class or from {@link ControllerAdmin}. Every public method in
50 * the controller will be exposed as a controller method and can be invoked via
51 * an HTTP request.
52 *
53 * Learn more about Piwik's MVC system [here](/guides/mvc-in-piwik).
54 *
55 * ### Examples
56 *
57 * **Defining a controller**
58 *
59 * class Controller extends \Piwik\Plugin\Controller
60 * {
61 * public function index()
62 * {
63 * $view = new View("@MyPlugin/index.twig");
64 * // ... setup view ...
65 * return $view->render();
66 * }
67 * }
68 *
69 * **Linking to a controller action**
70 *
71 * <a href="?module=MyPlugin&action=index&idSite=1&period=day&date=2013-10-10">Link</a>
72 *
73 */
74 abstract class Controller
75 {
76 /**
77 * The plugin name, eg. `'Referrers'`.
78 *
79 * @var string
80 * @api
81 */
82 protected $pluginName;
83 /**
84 * The value of the **date** query parameter.
85 *
86 * @var string
87 * @api
88 */
89 protected $strDate;
90 /**
91 * The Date object created with ($strDate)[#strDate] or null if the requested date is a range.
92 *
93 * @var Date|null
94 * @api
95 */
96 protected $date;
97 /**
98 * The value of the **idSite** query parameter.
99 *
100 * @var int
101 * @api
102 */
103 protected $idSite;
104 /**
105 * The Site object created with {@link $idSite}.
106 *
107 * @var Site
108 * @api
109 */
110 protected $site = null;
111 /**
112 * The SecurityPolicy object.
113 *
114 * @var \Piwik\View\SecurityPolicy
115 * @api
116 */
117 protected $securityPolicy = null;
118 /**
119 * Constructor.
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 * @param bool $fetch If `true`, the rendered string is returned, if `false` it is `echo`'d.
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 * @param string|null $viewType 'basic' or 'admin'. If null, set based on the type of controller.
528 * @return void
529 * @api
530 */
531 protected function setGeneralVariablesView($view)
532 {
533 $this->setGeneralVariablesViewAs($view, $viewType = null);
534 }
535 protected function setGeneralVariablesViewAs($view, $viewType)
536 {
537 $this->checkViewType($viewType);
538 if ($viewType === null) {
539 $viewType = $this instanceof \Piwik\Plugin\ControllerAdmin ? 'admin' : 'basic';
540 }
541 $view->idSite = $this->idSite;
542 $this->checkSitePermission();
543 $this->setPeriodVariablesView($view);
544 $view->siteName = $this->site->getName();
545 $view->siteMainUrl = $this->site->getMainUrl();
546 $siteTimezone = $this->site->getTimezone();
547 $datetimeMinDate = $this->site->getCreationDate()->getDatetime();
548 $minDate = Date::factory($datetimeMinDate, $siteTimezone);
549 $this->setMinDateView($minDate, $view);
550 $maxDate = Date::factory('now', $siteTimezone);
551 $this->setMaxDateView($maxDate, $view);
552 $rawDate = Piwik::getDate(GeneralConfig::getConfigValue('default_day'));
553 Period::checkDateFormat($rawDate);
554 $periodStr = Piwik::getPeriod(GeneralConfig::getConfigValue('default_period'));
555 if ($periodStr !== 'range') {
556 $date = Date::factory($this->strDate);
557 $validDate = $this->getValidDate($date, $minDate, $maxDate);
558 $period = Period\Factory::build($periodStr, $validDate);
559 if ($date->toString() !== $validDate->toString()) {
560 // we to not always change date since it could convert a strDate "today" to "YYYY-MM-DD"
561 // only change $this->strDate if it was not valid before
562 $this->setDate($validDate);
563 }
564 } else {
565 $period = new Range($periodStr, $rawDate, $siteTimezone);
566 }
567 // Setting current period start & end dates, for pre-setting the calendar when "Date Range" is selected
568 $dateStart = $period->getDateStart();
569 $dateStart = $this->getValidDate($dateStart, $minDate, $maxDate);
570 $dateEnd = $period->getDateEnd();
571 $dateEnd = $this->getValidDate($dateEnd, $minDate, $maxDate);
572 if ($periodStr === 'range') {
573 // make sure we actually display the correct calendar pretty date
574 $newRawDate = $dateStart->toString() . ',' . $dateEnd->toString();
575 $period = new Range($periodStr, $newRawDate, $siteTimezone);
576 }
577 $view->date = $this->strDate;
578 $view->prettyDate = self::getCalendarPrettyDate($period);
579 // prettyDateLong is not used by core, leaving in case plugins may be using it
580 $view->prettyDateLong = $period->getLocalizedLongString();
581 $view->rawDate = $rawDate;
582 $view->startDate = $dateStart;
583 $view->endDate = $dateEnd;
584 $timezoneOffsetInSeconds = Date::getUtcOffset($siteTimezone);
585 $view->timezoneOffset = $timezoneOffsetInSeconds;
586 $language = LanguagesManager::getLanguageForSession();
587 $view->language = !empty($language) ? $language : LanguagesManager::getLanguageCodeForCurrentUser();
588 $this->setBasicVariablesViewAs($view, $viewType);
589 $view->topMenu = MenuTop::getInstance()->getMenu();
590 $view->adminMenu = MenuAdmin::getInstance()->getMenu();
591 $notifications = $view->notifications;
592 if (empty($notifications)) {
593 $view->notifications = NotificationManager::getAllNotificationsToDisplay();
594 NotificationManager::cancelAllNonPersistent();
595 }
596 }
597 private function getValidDate(Date $date, Date $minDate, Date $maxDate)
598 {
599 if ($date->isEarlier($minDate)) {
600 $date = $minDate;
601 }
602 if ($date->isLater($maxDate)) {
603 $date = $maxDate;
604 }
605 return $date;
606 }
607 /**
608 * Needed when a controller extends ControllerAdmin but you don't want to call the controller admin basic variables
609 * view. Solves a problem when a controller has regular controller and admin controller views.
610 * @param View $view
611 */
612 protected function setBasicVariablesNoneAdminView($view)
613 {
614 $view->clientSideConfig = PiwikConfig::getInstance()->getClientSideOptions();
615 $view->isSuperUser = Access::getInstance()->hasSuperUserAccess();
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 $view->isSingleSite = Access::doAsSuperUser(function () {
656 $allSites = Request::processRequest('SitesManager.getAllSitesId', [], []);
657 return count($allSites) === 1;
658 });
659 if (isset($this->site) && is_object($this->site) && $this->site instanceof Site) {
660 $view->siteName = $this->site->getName();
661 }
662 self::setHostValidationVariablesView($view);
663 }
664 /**
665 * Assigns a set of generally useful variables to a {@link Piwik\View} instance.
666 *
667 * The following variables assigned:
668 *
669 * **isSuperUser** - True if the current user is the Super User, false if otherwise.
670 * **hasSomeAdminAccess** - True if the current user has admin access to at least one site,
671 * false if otherwise.
672 * **isCustomLogo** - The value of the `branding_use_custom_logo` option.
673 * **logoHeader** - The header logo URL to use.
674 * **logoLarge** - The large logo URL to use.
675 * **logoSVG** - The SVG logo URL to use.
676 * **hasSVGLogo** - True if there is a SVG logo, false if otherwise.
677 * **enableFrames** - The value of the `[General] enable_framed_pages` INI config option. If
678 * true, {@link Piwik\View::setXFrameOptions()} is called on the view.
679 *
680 * Also calls {@link setHostValidationVariablesView()}.
681 *
682 * @param View $view
683 * @param string $viewType 'basic' or 'admin'. Used by ControllerAdmin.
684 * @api
685 */
686 protected function setBasicVariablesView($view)
687 {
688 $this->setBasicVariablesViewAs($view);
689 }
690 protected function setBasicVariablesViewAs($view, $viewType = null)
691 {
692 $this->checkViewType($viewType);
693 // param is not used here, but the check can be useful for a developer
694 $this->setBasicVariablesNoneAdminView($view);
695 }
696 protected function addCustomLogoInfo($view)
697 {
698 $customLogo = new CustomLogo();
699 $view->isCustomLogo = $customLogo->isEnabled();
700 $view->customFavicon = $customLogo->getPathUserFavicon();
701 }
702 /**
703 * Set the template variables to show the what's new popup if appropriate
704 *
705 * @param View $view
706 * @return void
707 */
708 protected function showWhatIsNew(View $view) : void
709 {
710 $view->whatisnewShow = \false;
711 if (isset($view->hideWhatIsNew) && $view->hideWhatIsNew) {
712 return;
713 }
714 $model = new UsersModel();
715 $user = $model->getUser(Piwik::getCurrentUserLogin());
716 if (!$user) {
717 return;
718 }
719 $userChanges = new UserChanges($user);
720 $newChangesStatus = $userChanges->getNewChangesStatus();
721 $shownRecently = $userChanges->shownRecently();
722 if ($newChangesStatus == ChangesModel::NEW_CHANGES_EXIST && !$shownRecently) {
723 $view->whatisnewShow = \true;
724 }
725 }
726 /**
727 * Checks if the current host is valid and sets variables on the given view, including:
728 *
729 * - **isValidHost** - true if host is valid, false if otherwise
730 * - **invalidHostMessage** - message to display if host is invalid (only set if host is invalid)
731 * - **invalidHost** - the invalid hostname (only set if host is invalid)
732 * - **mailLinkStart** - the open tag of a link to email the Super User of this problem (only set
733 * if host is invalid)
734 *
735 * @param View $view
736 * @api
737 */
738 public static function setHostValidationVariablesView($view)
739 {
740 // check if host is valid
741 $view->isValidHost = Url::isValidHost();
742 if (!$view->isValidHost) {
743 // invalid host, so display warning to user
744 $validHosts = Url::getTrustedHostsFromConfig();
745 $validHost = $validHosts[0];
746 $invalidHost = Common::sanitizeInputValue(Url::getHost(\false));
747 $emailSubject = rawurlencode(Piwik::translate('CoreHome_InjectedHostEmailSubject', $invalidHost));
748 $emailBody = rawurlencode(Piwik::translate('CoreHome_InjectedHostEmailBody'));
749 $superUserEmail = rawurlencode(implode(',', Piwik::getContactEmailAddresses()));
750 $mailToUrl = "mailto:{$superUserEmail}?subject={$emailSubject}&body={$emailBody}";
751 $mailLinkStart = "<a href=\"{$mailToUrl}\">";
752 $invalidUrl = Url::getCurrentUrlWithoutQueryString($checkIfTrusted = \false);
753 $validUrl = Url::getCurrentScheme() . '://' . $validHost . Url::getCurrentScriptName();
754 $invalidUrl = Common::sanitizeInputValue($invalidUrl);
755 $validUrl = Common::sanitizeInputValue($validUrl);
756 $changeTrustedHostsUrl = "index.php" . Url::getCurrentQueryStringWithParametersModified(array('module' => 'CoreAdminHome', 'action' => 'generalSettings')) . "#trustedHostsSection";
757 $warningStart = Piwik::translate('CoreHome_InjectedHostWarningIntro', array('<strong>' . $invalidUrl . '</strong>', '<strong>' . $validUrl . '</strong>')) . ' <br/>';
758 if (Piwik::hasUserSuperUserAccess()) {
759 $view->invalidHostMessage = $warningStart . ' ' . Piwik::translate('CoreHome_InjectedHostSuperUserWarning', array("<a href=\"{$changeTrustedHostsUrl}\">", $invalidHost, '</a>', "<br/><a href=\"{$validUrl}\">", Common::sanitizeInputValue($validHost), '</a>'));
760 } elseif (Piwik::isUserIsAnonymous()) {
761 $view->invalidHostMessage = $warningStart . ' ' . Piwik::translate('CoreHome_InjectedHostNonSuperUserWarning', array("<br/><a href=\"{$validUrl}\">", '</a>', '<span style="display:none">', '</span>'));
762 } else {
763 $view->invalidHostMessage = $warningStart . ' ' . Piwik::translate('CoreHome_InjectedHostNonSuperUserWarning', array("<br/><a href=\"{$validUrl}\">", '</a>', $mailLinkStart, '</a>'));
764 }
765 $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
766 and add the following lines: <pre>[General]' . "\n" . 'trusted_hosts[] = "' . $invalidHost . '"</pre>After making the change, you will be able to login again.</p>
767 <p>You may also <i>disable this security feature (not recommended)</i>. To do so edit config/config.ini.php and add:
768 <pre>[General]' . "\n" . 'enable_trusted_host_check=0</pre>';
769 $view->invalidHost = $invalidHost;
770 // for UserSettings warning
771 $view->invalidHostMailLinkStart = $mailLinkStart;
772 }
773 }
774 /**
775 * Sets general period variables on a view, including:
776 *
777 * - **displayUniqueVisitors** - Whether unique visitors should be displayed for the current
778 * period.
779 * - **period** - The value of the **period** query parameter.
780 * - **otherPeriods** - `array('day', 'week', 'month', 'year', 'range')`
781 * - **periodsNames** - List of available periods mapped to their singular and plural translations.
782 *
783 * @param View $view
784 * @throws Exception if the current period is invalid.
785 * @api
786 */
787 public static function setPeriodVariablesView($view)
788 {
789 if (isset($view->period)) {
790 return;
791 }
792 $periodValidator = new PeriodValidator();
793 $currentPeriod = Piwik::getPeriod(GeneralConfig::getConfigValue('default_period'));
794 $availablePeriods = $periodValidator->getPeriodsAllowedForUI();
795 if (!$periodValidator->isPeriodAllowedForUI($currentPeriod)) {
796 throw new Exception("Period must be one of: " . implode(", ", $availablePeriods));
797 }
798 $view->displayUniqueVisitors = SettingsPiwik::isUniqueVisitorsEnabled($currentPeriod);
799 $found = array_search($currentPeriod, $availablePeriods);
800 unset($availablePeriods[$found]);
801 $view->period = $currentPeriod;
802 $view->otherPeriods = $availablePeriods;
803 $view->enabledPeriods = self::getEnabledPeriodsInUI();
804 $view->periodsNames = self::getEnabledPeriodsNames();
805 }
806 /**
807 * Helper method used to redirect the current HTTP request to another module/action.
808 *
809 * This function will exit immediately after executing.
810 *
811 * @param string $moduleToRedirect The plugin to redirect to, eg. `"MultiSites"`.
812 * @param string $actionToRedirect Action, eg. `"index"`.
813 * @param int|null $websiteId The new idSite query parameter, eg, `1`.
814 * @param string|null $defaultPeriod The new period query parameter, eg, `'day'`.
815 * @param string|null $defaultDate The new date query parameter, eg, `'today'`.
816 * @param array $parameters Other query parameters to append to the URL.
817 * @api
818 */
819 public function redirectToIndex($moduleToRedirect, $actionToRedirect, $websiteId = null, $defaultPeriod = null, $defaultDate = null, $parameters = array())
820 {
821 try {
822 $this->doRedirectToUrl($moduleToRedirect, $actionToRedirect, $websiteId, $defaultPeriod, $defaultDate, $parameters);
823 } catch (Exception $e) {
824 // no website ID to default to, so could not redirect
825 }
826 if (Piwik::hasUserSuperUserAccess()) {
827 $siteTableName = Common::prefixTable('site');
828 $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.";
829 $ex = new NoWebsiteFoundException($message);
830 $ex->setIsHtmlMessage();
831 throw $ex;
832 }
833 if (!Piwik::isUserIsAnonymous()) {
834 $currentLogin = Piwik::getCurrentUserLogin();
835 $emails = rawurlencode(implode(',', Piwik::getContactEmailAddresses()));
836 $errorMessage = sprintf(Piwik::translate('CoreHome_NoPrivilegesAskPiwikAdmin'), $currentLogin, "<br/><a href='mailto:" . $emails . "?subject=Access to Matomo for user {$currentLogin}'>", "</a>");
837 $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 />";
838 $ex = new NoPrivilegesException($errorMessage);
839 $ex->setIsHtmlMessage();
840 throw $ex;
841 }
842 echo FrontController::getInstance()->dispatch(Piwik::getLoginPluginName(), \false);
843 exit;
844 }
845 /**
846 * Checks that the token_auth in the URL matches the currently logged-in user's token_auth.
847 *
848 * This is a protection against CSRF and should be used in all controller
849 * methods that modify Piwik or any user settings.
850 *
851 * If called from JavaScript by using the `ajaxHelper` you have to call `ajaxHelper.withTokenInUrl();` before
852 * `ajaxHandler.send();` to send the token along with the request.
853 *
854 * **The token_auth should never appear in the browser's address bar.**
855 *
856 * @throws \Piwik\NoAccessException If the token doesn't match.
857 * @api
858 */
859 protected function checkTokenInUrl()
860 {
861 $tokenRequest = Common::getRequestVar('token_auth', \false);
862 $tokenUser = Piwik::getCurrentUserTokenAuth();
863 if (empty($tokenRequest) && empty($tokenUser)) {
864 return;
865 // UI tests
866 }
867 if ($tokenRequest !== $tokenUser) {
868 throw new NoAccessException(Piwik::translate('General_ExceptionSecurityCheckFailed'));
869 }
870 }
871 /**
872 * Returns a prettified date string for use in period selector widget.
873 *
874 * @param Period $period The period to return a pretty string for.
875 * @return string
876 * @api
877 */
878 public static function getCalendarPrettyDate($period)
879 {
880 if ($period instanceof Month) {
881 // show month name when period is for a month
882 return $period->getLocalizedLongString();
883 } else {
884 return $period->getPrettyString();
885 }
886 }
887 /**
888 * Returns the pretty date representation
889 *
890 * @param $date string
891 * @param $period string
892 * @return string Pretty date
893 */
894 public static function getPrettyDate($date, $period)
895 {
896 return self::getCalendarPrettyDate(Period\Factory::build($period, Date::factory($date)));
897 }
898 protected function checkSitePermission()
899 {
900 if (!empty($this->idSite)) {
901 Access::getInstance()->checkUserHasViewAccess($this->idSite);
902 new Site($this->idSite);
903 } elseif (empty($this->site) || empty($this->idSite)) {
904 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.");
905 }
906 }
907 /**
908 * @param $moduleToRedirect
909 * @param $actionToRedirect
910 * @param $websiteId
911 * @param $defaultPeriod
912 * @param $defaultDate
913 * @param $parameters
914 * @throws Exception
915 */
916 private function doRedirectToUrl($moduleToRedirect, $actionToRedirect, $websiteId, $defaultPeriod, $defaultDate, $parameters)
917 {
918 $menu = new \Piwik\Plugin\Menu();
919 $parameters = array_merge($menu->urlForDefaultUserParams($websiteId, $defaultPeriod, $defaultDate), $parameters);
920 $queryParams = !empty($parameters) ? '&' . Url::getQueryStringFromParameters($parameters) : '';
921 $url = "index.php?module=%s&action=%s";
922 $url = sprintf($url, $moduleToRedirect, $actionToRedirect);
923 $url = $url . $queryParams;
924 Url::redirectToUrl($url);
925 }
926 private function checkViewType($viewType)
927 {
928 if ($viewType === 'admin' && !$this instanceof \Piwik\Plugin\ControllerAdmin) {
929 throw new Exception("'admin' view type is only allowed with ControllerAdmin class.");
930 }
931 }
932 }
933