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