PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 4.0.2
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v4.0.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.php
matomo / app / core Last commit date
API 5 years ago Access 5 years ago Application 5 years ago Archive 5 years ago ArchiveProcessor 5 years ago Archiver 5 years ago AssetManager 5 years ago Auth 5 years ago Category 5 years ago CliMulti 5 years ago Columns 5 years ago Composer 5 years ago Concurrency 5 years ago Config 5 years ago Container 5 years ago CronArchive 5 years ago DataAccess 5 years ago DataFiles 5 years ago DataTable 5 years ago Db 5 years ago DeviceDetector 5 years ago Email 5 years ago Exception 5 years ago Http 5 years ago Intl 5 years ago Mail 5 years ago Measurable 5 years ago Menu 5 years ago Metrics 5 years ago Notification 5 years ago Period 5 years ago Plugin 5 years ago ProfessionalServices 5 years ago Report 5 years ago ReportRenderer 5 years ago Scheduler 5 years ago Segment 5 years ago Session 5 years ago Settings 5 years ago Tracker 5 years ago Translation 5 years ago UpdateCheck 5 years ago Updater 5 years ago Updates 5 years ago Validators 5 years ago View 5 years ago ViewDataTable 5 years ago Visualization 5 years ago Widget 5 years ago .htaccess 6 years ago Access.php 5 years ago Archive.php 5 years ago ArchiveProcessor.php 5 years ago AssetManager.php 5 years ago Auth.php 5 years ago AuthResult.php 5 years ago BaseFactory.php 5 years ago Cache.php 5 years ago CacheId.php 5 years ago CliMulti.php 5 years ago Common.php 5 years ago Config.php 5 years ago Console.php 5 years ago Context.php 5 years ago Cookie.php 5 years ago CronArchive.php 5 years ago DataArray.php 5 years ago DataTable.php 5 years ago Date.php 5 years ago Db.php 5 years ago DbHelper.php 5 years ago Development.php 5 years ago ErrorHandler.php 5 years ago EventDispatcher.php 5 years ago ExceptionHandler.php 5 years ago FileIntegrity.php 5 years ago Filechecks.php 5 years ago Filesystem.php 5 years ago FrontController.php 5 years ago Http.php 5 years ago IP.php 5 years ago Log.php 5 years ago LogDeleter.php 5 years ago Mail.php 5 years ago Metrics.php 5 years ago NoAccessException.php 5 years ago Nonce.php 5 years ago Notification.php 5 years ago NumberFormatter.php 5 years ago Option.php 5 years ago Period.php 5 years ago Piwik.php 5 years ago Plugin.php 5 years ago Profiler.php 5 years ago ProxyHeaders.php 5 years ago ProxyHttp.php 5 years ago QuickForm2.php 5 years ago RankingQuery.php 5 years ago ReportRenderer.php 5 years ago Segment.php 5 years ago Sequence.php 5 years ago Session.php 5 years ago SettingsPiwik.php 5 years ago SettingsServer.php 5 years ago Singleton.php 5 years ago Site.php 5 years ago TCPDF.php 5 years ago Theme.php 5 years ago Timer.php 5 years ago Tracker.php 5 years ago Twig.php 5 years ago Unzip.php 5 years ago UpdateCheck.php 5 years ago Updater.php 5 years ago UpdaterErrorException.php 5 years ago Updates.php 5 years ago Url.php 5 years ago UrlHelper.php 5 years ago Version.php 5 years ago View.php 5 years ago bootstrap.php 5 years ago dispatch.php 5 years ago testMinimumPhpVersion.php 5 years ago
Plugin.php
655 lines
1 <?php
2 /**
3 * Matomo - free/libre analytics platform
4 *
5 * @link https://matomo.org
6 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
7 *
8 */
9 namespace Piwik;
10
11 use Piwik\Archive\ArchiveInvalidator;
12 use Piwik\Container\StaticContainer;
13 use Piwik\Plugin\Dependency;
14 use Piwik\Plugin\Manager;
15 use Piwik\Plugin\MetadataLoader;
16
17 if (!class_exists('Piwik\Plugin')) {
18
19 /**
20 * Base class of all Plugin Descriptor classes.
21 *
22 * Any plugin that wants to add event observers to one of Piwik's {@hook # hooks},
23 * or has special installation/uninstallation logic must implement this class.
24 * Plugins that can specify everything they need to in the _plugin.json_ files,
25 * such as themes, don't need to implement this class.
26 *
27 * Class implementations should be named after the plugin they are a part of
28 * (eg, `class UserCountry extends Plugin`).
29 *
30 * ### Plugin Metadata
31 *
32 * In addition to providing a place for plugins to install/uninstall themselves
33 * and add event observers, this class is also responsible for loading metadata
34 * found in the plugin.json file.
35 *
36 * The plugin.json file must exist in the root directory of a plugin. It can
37 * contain the following information:
38 *
39 * - **description**: An internationalized string description of what the plugin
40 * does.
41 * - **homepage**: The URL to the plugin's website.
42 * - **authors**: A list of author arrays with keys for 'name', 'email' and 'homepage'
43 * - **license**: The license the code uses (eg, GPL, MIT, etc.).
44 * - **version**: The plugin version (eg, 1.0.1).
45 * - **theme**: `true` or `false`. If `true`, the plugin will be treated as a theme.
46 *
47 * ### Examples
48 *
49 * **How to extend**
50 *
51 * use Piwik\Common;
52 * use Piwik\Plugin;
53 * use Piwik\Db;
54 *
55 * class MyPlugin extends Plugin
56 * {
57 * public function registerEvents()
58 * {
59 * return array(
60 * 'API.getReportMetadata' => 'getReportMetadata',
61 * 'Another.event' => array(
62 * 'function' => 'myOtherPluginFunction',
63 * 'after' => true // executes this callback after others
64 * )
65 * );
66 * }
67 *
68 * public function install()
69 * {
70 * Db::exec("CREATE TABLE " . Common::prefixTable('mytable') . "...");
71 * }
72 *
73 * public function uninstall()
74 * {
75 * Db::exec("DROP TABLE IF EXISTS " . Common::prefixTable('mytable'));
76 * }
77 *
78 * public function getReportMetadata(&$metadata)
79 * {
80 * // ...
81 * }
82 *
83 * public function myOtherPluginFunction()
84 * {
85 * // ...
86 * }
87 * }
88 *
89 * @api
90 */
91 class Plugin
92 {
93 /**
94 * Name of this plugin.
95 *
96 * @var string
97 */
98 protected $pluginName;
99
100 /**
101 * Holds plugin metadata.
102 *
103 * @var array
104 */
105 private $pluginInformation;
106
107 /**
108 * As the cache is used quite often we avoid having to create instances all the time. We reuse it which is not
109 * perfect but efficient. If the cache is used we need to make sure to call setId() before usage as there
110 * is maybe a different key set since last usage.
111 *
112 * @var \Matomo\Cache\Eager
113 */
114 private $cache;
115
116 /**
117 * Constructor.
118 *
119 * @param string|bool $pluginName A plugin name to force. If not supplied, it is set
120 * to the last part of the class name.
121 * @throws \Exception If plugin metadata is defined in both the getInformation() method
122 * and the **plugin.json** file.
123 */
124 public function __construct($pluginName = false)
125 {
126 if (empty($pluginName)) {
127 $pluginName = explode('\\', get_class($this));
128 $pluginName = end($pluginName);
129 }
130 $this->pluginName = $pluginName;
131
132 $cacheId = 'Plugin' . $pluginName . 'Metadata';
133 $cache = Cache::getEagerCache();
134
135 if ($cache->contains($cacheId)) {
136 $this->pluginInformation = $cache->fetch($cacheId);
137 } else {
138 $this->reloadPluginInformation();
139
140 $cache->save($cacheId, $this->pluginInformation);
141 }
142 }
143
144 public function reloadPluginInformation()
145 {
146 $metadataLoader = new MetadataLoader($this->pluginName);
147 $this->pluginInformation = $metadataLoader->load();
148
149 if ($this->hasDefinedPluginInformationInPluginClass() && $metadataLoader->hasPluginJson()) {
150 throw new \Exception('Plugin ' . $this->pluginName . ' has defined the method getInformation() and as well as having a plugin.json file. Please delete the getInformation() method from the plugin class. Alternatively, you may delete the plugin directory from plugins/' . $this->pluginName);
151 }
152 }
153
154 private function createCacheIfNeeded()
155 {
156 if (is_null($this->cache)) {
157 $this->cache = Cache::getEagerCache();
158 }
159 }
160
161 private function hasDefinedPluginInformationInPluginClass()
162 {
163 $myClassName = get_class();
164 $pluginClassName = get_class($this);
165
166 if ($pluginClassName == $myClassName) {
167 // plugin has not defined its own class
168 return false;
169 }
170
171 $foo = new \ReflectionMethod(get_class($this), 'getInformation');
172 $declaringClass = $foo->getDeclaringClass()->getName();
173
174 return $declaringClass != $myClassName;
175 }
176
177 /**
178 * Returns plugin information, including:
179 *
180 * - 'description' => string // 1-2 sentence description of the plugin
181 * - 'author' => string // plugin author
182 * - 'author_homepage' => string // author homepage URL (or email "mailto:youremail@example.org")
183 * - 'homepage' => string // plugin homepage URL
184 * - 'license' => string // plugin license
185 * - 'version' => string // plugin version number; examples and 3rd party plugins must not use Version::VERSION; 3rd party plugins must increment the version number with each plugin release
186 * - 'theme' => bool // Whether this plugin is a theme (a theme is a plugin, but a plugin is not necessarily a theme)
187 *
188 * @return array
189 */
190 public function getInformation()
191 {
192 return $this->pluginInformation;
193 }
194
195 final public function isPremiumFeature()
196 {
197 return !empty($this->pluginInformation['price']['base']);
198 }
199
200 /**
201 * Returns a list of events with associated event observers.
202 *
203 * Derived classes should use this method to associate callbacks with events.
204 *
205 * @return array eg,
206 *
207 * array(
208 * 'API.getReportMetadata' => 'myPluginFunction',
209 * 'Another.event' => array(
210 * 'function' => 'myOtherPluginFunction',
211 * 'after' => true // execute after callbacks w/o ordering
212 * )
213 * 'Yet.Another.event' => array(
214 * 'function' => 'myOtherPluginFunction',
215 * 'before' => true // execute before callbacks w/o ordering
216 * )
217 * )
218 * @since 2.15.0
219 */
220 public function registerEvents()
221 {
222 return array();
223 }
224
225 /**
226 * This method is executed after a plugin is loaded and translations are registered.
227 * Useful for initialization code that uses translated strings.
228 */
229 public function postLoad()
230 {
231 return;
232 }
233
234 /**
235 * Defines whether the whole plugin requires a working internet connection
236 * If set to true, the plugin will be automatically unloaded if `enable_internet_features` is 0,
237 * even if the plugin is activated
238 *
239 * @return bool
240 */
241 public function requiresInternetConnection()
242 {
243 return false;
244 }
245
246 /**
247 * Installs the plugin. Derived classes should implement this class if the plugin
248 * needs to:
249 *
250 * - create tables
251 * - update existing tables
252 * - etc.
253 *
254 * @throws \Exception if installation of fails for some reason.
255 */
256 public function install()
257 {
258 return;
259 }
260
261 /**
262 * Uninstalls the plugins. Derived classes should implement this method if the changes
263 * made in {@link install()} need to be undone during uninstallation.
264 *
265 * In most cases, if you have an {@link install()} method, you should provide
266 * an {@link uninstall()} method.
267 *
268 * @throws \Exception if uninstallation of fails for some reason.
269 */
270 public function uninstall()
271 {
272 return;
273 }
274
275 /**
276 * Executed every time the plugin is enabled.
277 */
278 public function activate()
279 {
280 return;
281 }
282
283 /**
284 * Executed every time the plugin is disabled.
285 */
286 public function deactivate()
287 {
288 return;
289 }
290
291 /**
292 * Returns the plugin version number.
293 *
294 * @return string
295 */
296 final public function getVersion()
297 {
298 $info = $this->getInformation();
299 return $info['version'];
300 }
301
302 /**
303 * Returns `true` if this plugin is a theme, `false` if otherwise.
304 *
305 * @return bool
306 */
307 public function isTheme()
308 {
309 $info = $this->getInformation();
310 return !empty($info['theme']) && (bool)$info['theme'];
311 }
312
313 /**
314 * Returns the plugin's base class name without the namespace,
315 * e.g., `"UserCountry"` when the plugin class is `"Piwik\Plugins\UserCountry\UserCountry"`.
316 *
317 * @return string
318 */
319 final public function getPluginName()
320 {
321 return $this->pluginName;
322 }
323
324 /**
325 * Tries to find a component such as a Menu or Tasks within this plugin.
326 *
327 * @param string $componentName The name of the component you want to look for. In case you request a
328 * component named 'Menu' it'll look for a file named 'Menu.php' within the
329 * root of the plugin folder that implements a class named
330 * Piwik\Plugin\$PluginName\Menu . If such a file exists but does not implement
331 * this class it'll silently ignored.
332 * @param string $expectedSubclass If not empty, a check will be performed whether a found file extends the
333 * given subclass. If the requested file exists but does not extend this class
334 * a warning will be shown to advice a developer to extend this certain class.
335 *
336 * @return string|null Null if the requested component does not exist or an instance of the found
337 * component.
338 */
339 public function findComponent($componentName, $expectedSubclass)
340 {
341 $this->createCacheIfNeeded();
342
343 $cacheId = 'Plugin' . $this->pluginName . $componentName . $expectedSubclass;
344
345 $pluginsDir = Manager::getPluginDirectory($this->pluginName);
346
347 $componentFile = sprintf('%s/%s.php', $pluginsDir, $componentName);
348
349 if ($this->cache->contains($cacheId)) {
350 $classname = $this->cache->fetch($cacheId);
351
352 if (empty($classname)) {
353 return null; // might by "false" in case has no menu, widget, ...
354 }
355
356 if (file_exists($componentFile)) {
357 include_once $componentFile;
358 }
359 } else {
360 $this->cache->save($cacheId, false); // prevent from trying to load over and over again for instance if there is no Menu for a plugin
361
362 if (!file_exists($componentFile)) {
363 return null;
364 }
365
366 require_once $componentFile;
367
368 $classname = sprintf('Piwik\\Plugins\\%s\\%s', $this->pluginName, $componentName);
369
370 if (!class_exists($classname)) {
371 return null;
372 }
373
374 if (!empty($expectedSubclass) && !is_subclass_of($classname, $expectedSubclass)) {
375 Log::warning(sprintf('Cannot use component %s for plugin %s, class %s does not extend %s',
376 $componentName, $this->pluginName, $classname, $expectedSubclass));
377 return null;
378 }
379
380 $this->cache->save($cacheId, $classname);
381 }
382
383 return $classname;
384 }
385
386 public function findMultipleComponents($directoryWithinPlugin, $expectedSubclass)
387 {
388 $this->createCacheIfNeeded();
389
390 $cacheId = 'Plugin' . $this->pluginName . $directoryWithinPlugin . $expectedSubclass;
391
392 if ($this->cache->contains($cacheId)) {
393 $components = $this->cache->fetch($cacheId);
394
395 if ($this->includeComponents($components)) {
396 return $components;
397 } else {
398 // problem including one cached file, refresh cache
399 }
400 }
401
402 $components = $this->doFindMultipleComponents($directoryWithinPlugin, $expectedSubclass);
403
404 $this->cache->save($cacheId, $components);
405
406 return $components;
407 }
408
409 /**
410 * Detect whether there are any missing dependencies.
411 *
412 * @param null $piwikVersion Defaults to the current Piwik version
413 * @return bool
414 */
415 public function hasMissingDependencies($piwikVersion = null)
416 {
417 $requirements = $this->getMissingDependencies($piwikVersion);
418
419 return !empty($requirements);
420 }
421
422 public function getMissingDependencies($piwikVersion = null)
423 {
424 if (empty($this->pluginInformation['require'])) {
425 return array();
426 }
427
428 $dependency = $this->makeDependency($piwikVersion);
429 return $dependency->getMissingDependencies($this->pluginInformation['require']);
430 }
431
432 /**
433 * Returns a string (translated) describing the missing requirements for this plugin and the given Piwik version
434 *
435 * @param string $piwikVersion
436 * @return string "AnonymousPiwikUsageMeasurement requires PIWIK >=3.0.0"
437 */
438 public function getMissingDependenciesAsString($piwikVersion = null)
439 {
440 if ($this->requiresInternetConnection() && !SettingsPiwik::isInternetEnabled()) {
441 return Piwik::translate('CorePluginsAdmin_PluginRequiresInternet');
442 }
443
444 if (empty($this->pluginInformation['require'])) {
445 return '';
446 }
447 $dependency = $this->makeDependency($piwikVersion);
448
449 $missingDependencies = $dependency->getMissingDependencies($this->pluginInformation['require']);
450
451 if(empty($missingDependencies)) {
452 return '';
453 }
454
455 $causedBy = array();
456 foreach ($missingDependencies as $dependency) {
457 $causedBy[] = ucfirst($dependency['requirement']) . ' ' . $dependency['causedBy'];
458 }
459
460 return Piwik::translate("CorePluginsAdmin_PluginRequirement", array(
461 $this->getPluginName(),
462 implode(', ', $causedBy)
463 ));
464 }
465
466 /**
467 * Schedules re-archiving of this plugin's reports from when this plugin was last
468 * deactivated to now. If the last time core:archive was run is earlier than the
469 * plugin's last deactivation time, then we use that time instead.
470 *
471 * Note: this only works for CLI archiving setups.
472 *
473 * Note: the time frame is limited by the `[General] rearchive_reports_in_past_last_n_months`
474 * INI config value.
475 *
476 * @throws \DI\DependencyException
477 * @throws \DI\NotFoundException
478 */
479 public function schedulePluginReArchiving()
480 {
481 $lastDeactivationTime = $this->getPluginLastDeactivationTime();
482
483 $dateTime = null;
484
485 $lastCronArchiveTime = (int) Option::get(CronArchive::OPTION_ARCHIVING_FINISHED_TS);
486 if (empty($lastCronArchiveTime)) {
487 $dateTime = $lastDeactivationTime;
488 } else if (empty($lastDeactivationTime)) {
489 $dateTime = null; // use default earliest time
490 } else {
491 $lastCronArchiveTime = Date::factory($lastCronArchiveTime);
492 $dateTime = $lastDeactivationTime->isEarlier($lastCronArchiveTime) ? $lastDeactivationTime : $lastCronArchiveTime;
493 }
494
495 if (empty($dateTime)) { // sanity check
496 $dateTime = null;
497 }
498
499 $archiveInvalidator = StaticContainer::get(ArchiveInvalidator::class);
500 $archiveInvalidator->scheduleReArchiving('all', $this->getPluginName(), $report = null, $dateTime);
501 }
502
503 /**
504 * Extracts the plugin name from a backtrace array. Returns `false` if we can't find one.
505 *
506 * @param array $backtrace The result of {@link debug_backtrace()} or
507 * [Exception::getTrace()](http://www.php.net/manual/en/exception.gettrace.php).
508 * @return string|false
509 */
510 public static function getPluginNameFromBacktrace($backtrace)
511 {
512 foreach ($backtrace as $tracepoint) {
513 // try and discern the plugin name
514 if (isset($tracepoint['class'])) {
515 $className = self::getPluginNameFromNamespace($tracepoint['class']);
516 if ($className) {
517 return $className;
518 }
519 }
520 }
521 return false;
522 }
523
524 /**
525 * Extracts the plugin name from a namespace name or a fully qualified class name. Returns `false`
526 * if we can't find one.
527 *
528 * @param string $namespaceOrClassName The namespace or class string.
529 * @return string|false
530 */
531 public static function getPluginNameFromNamespace($namespaceOrClassName)
532 {
533 if (preg_match("/Piwik\\\\Plugins\\\\([a-zA-Z_0-9]+)\\\\/", $namespaceOrClassName, $matches)) {
534 return $matches[1];
535 } else {
536 return false;
537 }
538 }
539
540 /**
541 * Override this method in your plugin class if you want your plugin to be loaded during tracking.
542 *
543 * Note: If you define your own dimension or handle a tracker event, your plugin will automatically
544 * be detected as a tracker plugin.
545 *
546 * @return bool
547 * @internal
548 */
549 public function isTrackerPlugin()
550 {
551 return false;
552 }
553
554 /**
555 * @return Date|null
556 * @throws \Exception
557 */
558 public function getPluginLastActivationTime()
559 {
560 $optionName = Manager::LAST_PLUGIN_ACTIVATION_TIME_OPTION_PREFIX . $this->pluginName;
561 $time = Option::get($optionName);
562 if (empty($time)) {
563 return null;
564 }
565 return Date::factory((int) $time);
566 }
567
568 /**
569 * @return Date|null
570 * @throws \Exception
571 */
572 public function getPluginLastDeactivationTime()
573 {
574 $optionName = Manager::LAST_PLUGIN_DEACTIVATION_TIME_OPTION_PREFIX . $this->pluginName;
575 $time = Option::get($optionName);
576 if (empty($time)) {
577 return null;
578 }
579 return Date::factory((int) $time);
580 }
581
582 /**
583 * @param $directoryWithinPlugin
584 * @param $expectedSubclass
585 * @return array
586 */
587 private function doFindMultipleComponents($directoryWithinPlugin, $expectedSubclass)
588 {
589 $components = array();
590
591 $pluginsDir = Manager::getPluginDirectory($this->pluginName);
592 $baseDir = $pluginsDir . '/' . $directoryWithinPlugin;
593
594 $files = Filesystem::globr($baseDir, '*.php');
595
596 foreach ($files as $file) {
597 require_once $file;
598
599 $fileName = str_replace(array($baseDir . '/', '.php'), '', $file);
600 $klassName = sprintf('Piwik\\Plugins\\%s\\%s\\%s', $this->pluginName, str_replace('/', '\\', $directoryWithinPlugin), str_replace('/', '\\', $fileName));
601
602 if (!class_exists($klassName)) {
603 continue;
604 }
605
606 if (!empty($expectedSubclass) && !is_subclass_of($klassName, $expectedSubclass)) {
607 continue;
608 }
609
610 $klass = new \ReflectionClass($klassName);
611
612 if ($klass->isAbstract()) {
613 continue;
614 }
615
616 $components[$file] = $klassName;
617 }
618 return $components;
619 }
620
621 /**
622 * @param $components
623 * @return bool true if all files were included, false if any file cannot be read
624 */
625 private function includeComponents($components)
626 {
627 foreach ($components as $file => $klass) {
628 if (!is_readable($file)) {
629 return false;
630 }
631 }
632 foreach ($components as $file => $klass) {
633 include_once $file;
634 }
635 return true;
636 }
637
638 /**
639 * @param $piwikVersion
640 * @return Dependency
641 */
642 private function makeDependency($piwikVersion)
643 {
644 $dependency = new Dependency();
645
646 if (!is_null($piwikVersion)) {
647 $dependency->setPiwikVersion($piwikVersion);
648 }
649 return $dependency;
650 }
651 }
652
653 }
654
655