PluginProbe
Metricool – Social media and site statistics / 2.1.1
Metricool – Social media and site statistics v2.1.1
2.1.1 2.1.0 2.0.2 2.0.1 2.0.0 1.27 trunk
← All changes | app/Managers/FeatureManager.php +163 -0 2.0.1 → 2.1.1 View file →
@@ -1,0 +1,163 @@
1 +<?php
2 +
3 +declare(strict_types=1);
4 +
5 +namespace Metricool\Managers;
6 +
7 +use Metricool\Bootstrap\App;
8 +use Metricool\Interfaces\FeatureInterface;
9 +use Metricool\Support\Helpers\Storages\EnvironmentConfig;
10 +
11 +/**
12 + * This manager dynamically fetches the features of the plugin. It differs from
13 + * other manager classes due to this nature. By preventing any class usage of
14 + * features we prevent composer from loading the feature file entirely until
15 + * first use. This prevents overhead from loading features that are no longer
16 + * needed. We prevent loading feature files by utilizing the
17 + * {@see AbstractLoader} class at {@see FeatureManager:92}
18 + */
19 +final class FeatureManager extends AbstractManager
20 +{
21 + private const PRO_FEATURE_HANDLE = 'Pro:';
22 +
23 + private EnvironmentConfig $env;
24 +
25 + public function __construct(EnvironmentConfig $env)
26 + {
27 + $this->env = $env;
28 + }
29 +
30 + /**
31 + * @inheritDoc
32 + */
33 + public function isRegistrable(object $class): bool
34 + {
35 + return $class instanceof FeatureInterface;
36 + }
37 +
38 + /**
39 + * @inheritDoc
40 + */
41 + public function registerClass(object $class): void
42 + {
43 + $class->register();
44 + }
45 +
46 + /**
47 + * @inheritDoc
48 + */
49 + public function afterRegister(): void
50 + {
51 + do_action('metricool_features_loaded');
52 + }
53 +
54 + /**
55 + * Register and load all features from the src/features directory. This
56 + * method automatically loads all classes from the features directory and
57 + * injects the dependency classes into the Controller class if they exist.
58 + * @uses do_action rss_core_features_loaded
59 + */
60 + public function registerFeatures(): void
61 + {
62 + $featureClasses = $this->getFeatureClasses();
63 + $this->register($featureClasses);
64 + }
65 +
66 + /**
67 + * Dynamically build and then return an array of feature classes that are
68 + * saved in the features path of the plugin.
69 + */
70 + public function getFeatureClasses(): array
71 + {
72 + $features = $this->getFeatures();
73 + $featureClasses = [];
74 +
75 + foreach ($features as $featureName) {
76 + $needsPro = strpos($featureName, self::PRO_FEATURE_HANDLE) !== false;
77 + if ($needsPro && !$this->env->getBoolean('plugin.pro')) {
78 + continue; // Pro not installed, don't register pro features
79 + }
80 +
81 + if ($needsPro) {
82 + $featureName = substr($featureName, strlen(self::PRO_FEATURE_HANDLE));
83 + }
84 +
85 + // Check if the feature directory exists
86 + $featuresPath = $this->getFeaturePath($featureName, $needsPro);
87 + if (!is_dir($featuresPath)) {
88 + continue;
89 + }
90 +
91 + // Get the feature namespace
92 + $prefix = $this->getFeatureNamespace($featureName, $needsPro) . $featureName;
93 +
94 + // Get the {FeatureName}Loader class for the feature
95 + if (class_exists($prefix . 'Loader') === false) {
96 + continue;
97 + }
98 +
99 + $loader = App::getInstance()->make($prefix . 'Loader', false, false);
100 + if (!$loader->isEnabled() || !$loader->inScope()) {
101 + continue;
102 + }
103 +
104 + // The controller is the backbone of a feature
105 + $featureClasses[] = $prefix . 'Controller';
106 + };
107 +
108 + return $featureClasses;
109 + }
110 +
111 + /**
112 + * Get all feature directory names. Includes "Pro" features prefixed
113 + * with {@see PRO_FEATURE_HANDLE}.
114 + */
115 + private function getFeatures(): array
116 + {
117 + $featuresPath = $this->env->getString('plugin.feature_path');
118 + $features = [];
119 +
120 + foreach (new \DirectoryIterator($featuresPath) as $fileInfo) {
121 + if ($fileInfo->isDot() || !$fileInfo->isDir()) {
122 + continue;
123 + }
124 +
125 + $proEnabled = $this->env->getBoolean('plugin.pro');
126 + $skipPro = ($proEnabled === false && $fileInfo->getFilename() === 'Pro');
127 + if ($skipPro) {
128 + continue;
129 + }
130 +
131 + if ($fileInfo->getFilename() === 'Pro') {
132 + foreach (new \DirectoryIterator($fileInfo->getPathname()) as $proInfo) {
133 + if ($proInfo->isDot() || !$proInfo->isDir()) {
134 + continue;
135 + }
136 + $features[] = self::PRO_FEATURE_HANDLE . $proInfo->getFilename();
137 + }
138 + continue;
139 + }
140 +
141 + $features[] = $fileInfo->getFilename();
142 + }
143 +
144 + return $features;
145 + }
146 +
147 + /**
148 + * Get the feature path based on the feature name and if it needs the Pro
149 + * version.
150 + */
151 + private function getFeaturePath(string $featureName, bool $needsPro): string
152 + {
153 + return $this->env->getString('plugin.feature_path') . ($needsPro ? 'Pro/' : '') . $featureName . '/';
154 + }
155 +
156 + /**
157 + * Get the feature namespace.
158 + */
159 + private function getFeatureNamespace(string $featureName, bool $needsPro = false): string
160 + {
161 + return 'Metricool\Features\\' . ($needsPro ? 'Pro\\' : '') . $featureName . '\\';
162 + }
163 +}