1 <?php
  2 
  3 /**
  4  * Abstract base class for packages.
  5  *
  6  * @author gharlan
  7  *
  8  * @package redaxo\core\packages
  9  */
 10 abstract class rex_package implements rex_package_interface
 11 {
 12     const FILE_PACKAGE = 'package.yml';
 13     const FILE_BOOT = 'boot.php';
 14     const FILE_INSTALL = 'install.php';
 15     const FILE_INSTALL_SQL = 'install.sql';
 16     const FILE_UNINSTALL = 'uninstall.php';
 17     const FILE_UNINSTALL_SQL = 'uninstall.sql';
 18     const FILE_UPDATE = 'update.php';
 19 
 20     /**
 21      * Name of the package.
 22      *
 23      * @var string
 24      */
 25     private $name;
 26 
 27     /**
 28      * Properties.
 29      *
 30      * @var array
 31      */
 32     private $properties = [];
 33 
 34     /**
 35      * Flag whether the properties of package.yml are loaded.
 36      *
 37      * @var bool
 38      */
 39     private $propertiesLoaded = false;
 40 
 41     /**
 42      * Constructor.
 43      *
 44      * @param string $name Name
 45      */
 46     public function __construct($name)
 47     {
 48         $this->name = $name;
 49     }
 50 
 51     /**
 52      * Returns the package (addon or plugin) by the given package id.
 53      *
 54      * @param string $packageId Package ID
 55      *
 56      * @throws InvalidArgumentException
 57      *
 58      * @return self
 59      */
 60     public static function get($packageId)
 61     {
 62         if (!is_string($packageId)) {
 63             throw new InvalidArgumentException('Expecting $packageId to be string, but ' . gettype($packageId) . ' given!');
 64         }
 65         $package = explode('/', $packageId);
 66         $addon = rex_addon::get($package[0]);
 67         if (isset($package[1])) {
 68             return $addon->getPlugin($package[1]);
 69         }
 70         return $addon;
 71     }
 72 
 73     /**
 74      * Returns if the package exists.
 75      *
 76      * @param string $packageId Package ID
 77      *
 78      * @return bool
 79      */
 80     public static function exists($packageId)
 81     {
 82         $package = explode('/', $packageId);
 83         if (isset($package[1])) {
 84             return rex_plugin::exists($package[0], $package[1]);
 85         }
 86         return rex_addon::exists($package[0]);
 87     }
 88 
 89     /**
 90      * {@inheritdoc}
 91      */
 92     public function getName()
 93     {
 94         return $this->name;
 95     }
 96 
 97     /**
 98      * {@inheritdoc}
 99      */
100     public function setConfig($key, $value = null)
101     {
102         return rex_config::set($this->getPackageId(), $key, $value);
103     }
104 
105     /**
106      * {@inheritdoc}
107      */
108     public function getConfig($key = null, $default = null)
109     {
110         return rex_config::get($this->getPackageId(), $key, $default);
111     }
112 
113     /**
114      * {@inheritdoc}
115      */
116     public function hasConfig($key = null)
117     {
118         return rex_config::has($this->getPackageId(), $key);
119     }
120 
121     /**
122      * {@inheritdoc}
123      */
124     public function removeConfig($key)
125     {
126         return rex_config::remove($this->getPackageId(), $key);
127     }
128 
129     /**
130      * {@inheritdoc}
131      */
132     public function setProperty($key, $value)
133     {
134         if (!is_string($key)) {
135             throw new InvalidArgumentException('Expecting $key to be string, but ' . gettype($key) . ' given!');
136         }
137         $this->properties[$key] = $value;
138     }
139 
140     /**
141      * {@inheritdoc}
142      */
143     public function getProperty($key, $default = null)
144     {
145         if ($this->hasProperty($key)) {
146             return $this->properties[$key];
147         }
148         return $default;
149     }
150 
151     /**
152      * {@inheritdoc}
153      */
154     public function hasProperty($key)
155     {
156         if (!is_string($key)) {
157             throw new InvalidArgumentException('Expecting $key to be string, but ' . gettype($key) . ' given!');
158         }
159         if (!isset($this->properties[$key]) && !$this->propertiesLoaded) {
160             $this->loadProperties();
161         }
162         return isset($this->properties[$key]);
163     }
164 
165     /**
166      * {@inheritdoc}
167      */
168     public function removeProperty($key)
169     {
170         if (!is_string($key)) {
171             throw new InvalidArgumentException('Expecting $key to be string, but ' . gettype($key) . ' given!');
172         }
173         unset($this->properties[$key]);
174     }
175 
176     /**
177      * {@inheritdoc}
178      */
179     public function isAvailable()
180     {
181         return $this->isInstalled() && (bool) $this->getProperty('status', false);
182     }
183 
184     /**
185      * {@inheritdoc}
186      */
187     public function isInstalled()
188     {
189         return (bool) $this->getProperty('install', false);
190     }
191 
192     /**
193      * {@inheritdoc}
194      */
195     public function getAuthor($default = null)
196     {
197         return $this->getProperty('author', $default);
198     }
199 
200     /**
201      * {@inheritdoc}
202      */
203     public function getVersion($format = null)
204     {
205         $version = $this->getProperty('version');
206         if ($format) {
207             return rex_formatter::version($version, $format);
208         }
209         return $version;
210     }
211 
212     /**
213      * {@inheritdoc}
214      */
215     public function getSupportPage($default = null)
216     {
217         return $this->getProperty('supportpage', $default);
218     }
219 
220     /**
221      * {@inheritdoc}
222      */
223     public function includeFile($__file, array $__context = [])
224     {
225         extract($__context, EXTR_SKIP);
226 
227         if (file_exists($this->getPath($__file))) {
228             return include $this->getPath($__file);
229         }
230 
231         return include $__file;
232     }
233 
234     /**
235      * Loads the properties of package.yml.
236      */
237     public function loadProperties()
238     {
239         $file = $this->getPath(self::FILE_PACKAGE);
240         if (!file_exists($file)) {
241             $this->propertiesLoaded = true;
242             return;
243         }
244 
245         static $cache = null;
246         if (null === $cache) {
247             $cache = rex_file::getCache(rex_path::coreCache('packages.cache'));
248         }
249         $id = $this->getPackageId();
250 
251         $isCached = isset($cache[$id]);
252         $isBackendAdmin = rex::isBackend() && rex::getUser() && rex::getUser()->isAdmin();
253         if (!$isCached || (rex::getConsole() || $isBackendAdmin) && $cache[$id]['timestamp'] < filemtime($file)) {
254             try {
255                 $properties = rex_file::getConfig($file);
256 
257                 $cache[$id]['timestamp'] = filemtime($file);
258                 $cache[$id]['data'] = $properties;
259 
260                 static $registeredShutdown = false;
261                 if (!$registeredShutdown) {
262                     $registeredShutdown = true;
263                     register_shutdown_function(function () use (&$cache) {
264                         foreach ($cache as $package => $_) {
265                             if (!self::exists($package)) {
266                                 unset($cache[$package]);
267                             }
268                         }
269                         rex_file::putCache(rex_path::coreCache('packages.cache'), $cache);
270                     });
271                 }
272             } catch (rex_yaml_parse_exception $exception) {
273                 if ($this->isInstalled()) {
274                     throw $exception;
275                 }
276 
277                 $properties = [];
278             }
279         } else {
280             $properties = $cache[$id]['data'];
281         }
282 
283         $this->properties = array_intersect_key($this->properties, ['install' => null, 'status' => null]);
284         if ($properties) {
285             foreach ($properties as $key => $value) {
286                 if (isset($this->properties[$key])) {
287                     continue;
288                 }
289                 if ('supportpage' !== $key) {
290                     $value = rex_i18n::translateArray($value, false, [$this, 'i18n']);
291                 } elseif (!preg_match('@^https?://@i', $value)) {
292                     $value = 'https://'.$value;
293                 }
294                 $this->properties[$key] = $value;
295             }
296         }
297         $this->propertiesLoaded = true;
298     }
299 
300     /**
301      *  Clears the cache of the package.
302      *
303      * @throws rex_functional_exception
304      */
305     public function clearCache()
306     {
307         $cache_dir = $this->getCachePath();
308         if (is_dir($cache_dir) && !rex_dir::delete($cache_dir)) {
309             throw new rex_functional_exception($this->i18n('cache_not_writable', $cache_dir));
310         }
311     }
312 
313     public function enlist()
314     {
315         $folder = $this->getPath();
316 
317         // add addon path for i18n
318         if (is_readable($folder . 'lang')) {
319             rex_i18n::addDirectory($folder . 'lang');
320         }
321         // add package path for fragment loading
322         if (is_readable($folder . 'fragments')) {
323             rex_fragment::addDirectory($folder . 'fragments' . DIRECTORY_SEPARATOR);
324         }
325         // add addon path for class-loading
326         if (is_readable($folder . 'lib')) {
327             rex_autoload::addDirectory($folder . 'lib');
328         }
329         if (is_readable($folder . 'vendor')) {
330             rex_autoload::addDirectory($folder . 'vendor');
331         }
332         $autoload = $this->getProperty('autoload');
333         if (is_array($autoload) && isset($autoload['classes']) && is_array($autoload['classes'])) {
334             foreach ($autoload['classes'] as $dir) {
335                 $dir = $this->getPath($dir);
336                 if (is_readable($dir)) {
337                     rex_autoload::addDirectory($dir);
338                 }
339             }
340         }
341     }
342 
343     public function boot()
344     {
345         if (is_readable($this->getPath(self::FILE_BOOT))) {
346             $this->includeFile(self::FILE_BOOT);
347         }
348     }
349 
350     /**
351      * Returns the registered packages.
352      *
353      * @return self[]
354      */
355     public static function getRegisteredPackages()
356     {
357         return self::getPackages('Registered');
358     }
359 
360     /**
361      * Returns the installed packages.
362      *
363      * @return self[]
364      */
365     public static function getInstalledPackages()
366     {
367         return self::getPackages('Installed');
368     }
369 
370     /**
371      * Returns the available packages.
372      *
373      * @return self[]
374      */
375     public static function getAvailablePackages()
376     {
377         return self::getPackages('Available');
378     }
379 
380     /**
381      * Returns the setup packages.
382      *
383      * @return self[]
384      */
385     public static function getSetupPackages()
386     {
387         return self::getPackages('Setup', 'System');
388     }
389 
390     /**
391      * Returns the system packages.
392      *
393      * @return self[]
394      */
395     public static function getSystemPackages()
396     {
397         return self::getPackages('System');
398     }
399 
400     /**
401      * Returns the packages by the given method.
402      *
403      * @param string $method       Method
404      * @param string $pluginMethod Optional other method for plugins
405      *
406      * @return self[]
407      */
408     private static function getPackages($method, $pluginMethod = null)
409     {
410         $packages = [];
411         $addonMethod = 'get' . $method . 'Addons';
412         $pluginMethod = 'get' . ($pluginMethod ?: $method) . 'Plugins';
413         foreach (rex_addon::$addonMethod() as $addon) {
414             $packages[$addon->getPackageId()] = $addon;
415             foreach ($addon->$pluginMethod() as $plugin) {
416                 $packages[$plugin->getPackageId()] = $plugin;
417             }
418         }
419         return $packages;
420     }
421 }
422