aboutsummaryrefslogtreecommitdiffstats
path: root/phpBB/phpbb/extension
diff options
context:
space:
mode:
authorMaat <maat-pub@mageia.biz>2020-05-08 18:29:30 +0200
committerMaat <maat-pub@mageia.biz>2020-05-08 21:36:04 +0200
commit36bc1870f21fac04736a1049c1d5b8e127d729f4 (patch)
tree9d102331eeaf1ef3cd23e656320d7c08e65757ed /phpBB/phpbb/extension
parent8875d385d0579b451dac4d9bda465172b4f69ee0 (diff)
parent149375253685b3a38996f63015a74b7a0f53aa14 (diff)
downloadforums-36bc1870f21fac04736a1049c1d5b8e127d729f4.tar
forums-36bc1870f21fac04736a1049c1d5b8e127d729f4.tar.gz
forums-36bc1870f21fac04736a1049c1d5b8e127d729f4.tar.bz2
forums-36bc1870f21fac04736a1049c1d5b8e127d729f4.tar.xz
forums-36bc1870f21fac04736a1049c1d5b8e127d729f4.zip
Merge remote-tracking branch 'upstream/prep-release-3.1.11'
Diffstat (limited to 'phpBB/phpbb/extension')
-rw-r--r--phpBB/phpbb/extension/base.php142
-rw-r--r--phpBB/phpbb/extension/exception.php25
-rw-r--r--phpBB/phpbb/extension/extension_interface.php70
-rw-r--r--phpBB/phpbb/extension/manager.php592
-rw-r--r--phpBB/phpbb/extension/metadata_manager.php343
-rw-r--r--phpBB/phpbb/extension/provider.php72
6 files changed, 1244 insertions, 0 deletions
diff --git a/phpBB/phpbb/extension/base.php b/phpBB/phpbb/extension/base.php
new file mode 100644
index 0000000000..5bb530bad4
--- /dev/null
+++ b/phpBB/phpbb/extension/base.php
@@ -0,0 +1,142 @@
+<?php
+/**
+*
+* This file is part of the phpBB Forum Software package.
+*
+* @copyright (c) phpBB Limited <https://www.phpbb.com>
+* @license GNU General Public License, version 2 (GPL-2.0)
+*
+* For full copyright and license information, please see
+* the docs/CREDITS.txt file.
+*
+*/
+
+namespace phpbb\extension;
+
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+* A base class for extensions without custom enable/disable/purge code.
+*/
+class base implements \phpbb\extension\extension_interface
+{
+ /** @var ContainerInterface */
+ protected $container;
+
+ /** @var \phpbb\finder */
+ protected $finder;
+
+ /** @var \phpbb\db\migrator */
+ protected $migrator;
+
+ /** @var string */
+ protected $extension_name;
+
+ /** @var string */
+ protected $extension_path;
+
+ /** @var string[] */
+ private $migrations = false;
+
+ /**
+ * Constructor
+ *
+ * @param ContainerInterface $container Container object
+ * @param \phpbb\finder $extension_finder
+ * @param \phpbb\db\migrator $migrator
+ * @param string $extension_name Name of this extension (from ext.manager)
+ * @param string $extension_path Relative path to this extension
+ */
+ public function __construct(ContainerInterface $container, \phpbb\finder $extension_finder, \phpbb\db\migrator $migrator, $extension_name, $extension_path)
+ {
+ $this->container = $container;
+ $this->extension_finder = $extension_finder;
+ $this->migrator = $migrator;
+
+ $this->extension_name = $extension_name;
+ $this->extension_path = $extension_path;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function is_enableable()
+ {
+ return true;
+ }
+
+ /**
+ * Single enable step that installs any included migrations
+ *
+ * @param mixed $old_state State returned by previous call of this method
+ * @return false Indicates no further steps are required
+ */
+ public function enable_step($old_state)
+ {
+ $migrations = $this->get_migration_file_list();
+
+ $this->migrator->set_migrations($migrations);
+
+ $this->migrator->update();
+
+ return !$this->migrator->finished();
+ }
+
+ /**
+ * Single disable step that does nothing
+ *
+ * @param mixed $old_state State returned by previous call of this method
+ * @return false Indicates no further steps are required
+ */
+ public function disable_step($old_state)
+ {
+ return false;
+ }
+
+ /**
+ * Single purge step that reverts any included and installed migrations
+ *
+ * @param mixed $old_state State returned by previous call of this method
+ * @return false Indicates no further steps are required
+ */
+ public function purge_step($old_state)
+ {
+ $migrations = $this->get_migration_file_list();
+
+ $this->migrator->set_migrations($migrations);
+
+ foreach ($migrations as $migration)
+ {
+ while ($this->migrator->migration_state($migration) !== false)
+ {
+ $this->migrator->revert($migration);
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Get the list of migration files from this extension
+ *
+ * @return array
+ */
+ protected function get_migration_file_list()
+ {
+ if ($this->migrations !== false)
+ {
+ return $this->migrations;
+ }
+
+ // Only have the finder search in this extension path directory
+ $migrations = $this->extension_finder
+ ->extension_directory('/migrations')
+ ->find_from_extension($this->extension_name, $this->extension_path);
+
+ $migrations = $this->extension_finder->get_classes_from_files($migrations);
+
+ return $migrations;
+ }
+}
diff --git a/phpBB/phpbb/extension/exception.php b/phpBB/phpbb/extension/exception.php
new file mode 100644
index 0000000000..3f7d251a4e
--- /dev/null
+++ b/phpBB/phpbb/extension/exception.php
@@ -0,0 +1,25 @@
+<?php
+/**
+*
+* This file is part of the phpBB Forum Software package.
+*
+* @copyright (c) phpBB Limited <https://www.phpbb.com>
+* @license GNU General Public License, version 2 (GPL-2.0)
+*
+* For full copyright and license information, please see
+* the docs/CREDITS.txt file.
+*
+*/
+
+namespace phpbb\extension;
+
+/**
+ * Exception class for metadata
+ */
+class exception extends \UnexpectedValueException
+{
+ public function __toString()
+ {
+ return $this->getMessage();
+ }
+}
diff --git a/phpBB/phpbb/extension/extension_interface.php b/phpBB/phpbb/extension/extension_interface.php
new file mode 100644
index 0000000000..6a6b6adb8f
--- /dev/null
+++ b/phpBB/phpbb/extension/extension_interface.php
@@ -0,0 +1,70 @@
+<?php
+/**
+*
+* This file is part of the phpBB Forum Software package.
+*
+* @copyright (c) phpBB Limited <https://www.phpbb.com>
+* @license GNU General Public License, version 2 (GPL-2.0)
+*
+* For full copyright and license information, please see
+* the docs/CREDITS.txt file.
+*
+*/
+
+namespace phpbb\extension;
+
+/**
+* The interface extension meta classes have to implement to run custom code
+* on enable/disable/purge.
+*/
+interface extension_interface
+{
+ /**
+ * Indicate whether or not the extension can be enabled.
+ *
+ * @return bool
+ */
+ public function is_enableable();
+
+ /**
+ * enable_step is executed on enabling an extension until it returns false.
+ *
+ * Calls to this function can be made in subsequent requests, when the
+ * function is invoked through a webserver with a too low max_execution_time.
+ *
+ * @param mixed $old_state The return value of the previous call
+ * of this method, or false on the first call
+ * @return mixed Returns false after last step, otherwise
+ * temporary state which is passed as an
+ * argument to the next step
+ */
+ public function enable_step($old_state);
+
+ /**
+ * Disables the extension.
+ *
+ * Calls to this function can be made in subsequent requests, when the
+ * function is invoked through a webserver with a too low max_execution_time.
+ *
+ * @param mixed $old_state The return value of the previous call
+ * of this method, or false on the first call
+ * @return mixed Returns false after last step, otherwise
+ * temporary state which is passed as an
+ * argument to the next step
+ */
+ public function disable_step($old_state);
+
+ /**
+ * purge_step is executed on purging an extension until it returns false.
+ *
+ * Calls to this function can be made in subsequent requests, when the
+ * function is invoked through a webserver with a too low max_execution_time.
+ *
+ * @param mixed $old_state The return value of the previous call
+ * of this method, or false on the first call
+ * @return mixed Returns false after last step, otherwise
+ * temporary state which is passed as an
+ * argument to the next step
+ */
+ public function purge_step($old_state);
+}
diff --git a/phpBB/phpbb/extension/manager.php b/phpBB/phpbb/extension/manager.php
new file mode 100644
index 0000000000..e7e5f83c23
--- /dev/null
+++ b/phpBB/phpbb/extension/manager.php
@@ -0,0 +1,592 @@
+<?php
+/**
+*
+* This file is part of the phpBB Forum Software package.
+*
+* @copyright (c) phpBB Limited <https://www.phpbb.com>
+* @license GNU General Public License, version 2 (GPL-2.0)
+*
+* For full copyright and license information, please see
+* the docs/CREDITS.txt file.
+*
+*/
+
+namespace phpbb\extension;
+
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+* The extension manager provides means to activate/deactivate extensions.
+*/
+class manager
+{
+ /** @var ContainerInterface */
+ protected $container;
+
+ protected $db;
+ protected $config;
+ protected $cache;
+ protected $user;
+ protected $php_ext;
+ protected $extensions;
+ protected $extension_table;
+ protected $phpbb_root_path;
+ protected $cache_name;
+
+ /**
+ * Creates a manager and loads information from database
+ *
+ * @param ContainerInterface $container A container
+ * @param \phpbb\db\driver\driver_interface $db A database connection
+ * @param \phpbb\config\config $config Config object
+ * @param \phpbb\filesystem $filesystem
+ * @param \phpbb\user $user User object
+ * @param string $extension_table The name of the table holding extensions
+ * @param string $phpbb_root_path Path to the phpbb includes directory.
+ * @param string $php_ext php file extension, defaults to php
+ * @param \phpbb\cache\driver\driver_interface $cache A cache instance or null
+ * @param string $cache_name The name of the cache variable, defaults to _ext
+ */
+ public function __construct(ContainerInterface $container, \phpbb\db\driver\driver_interface $db, \phpbb\config\config $config, \phpbb\filesystem $filesystem, \phpbb\user $user, $extension_table, $phpbb_root_path, $php_ext = 'php', \phpbb\cache\driver\driver_interface $cache = null, $cache_name = '_ext')
+ {
+ $this->cache = $cache;
+ $this->cache_name = $cache_name;
+ $this->config = $config;
+ $this->container = $container;
+ $this->db = $db;
+ $this->extension_table = $extension_table;
+ $this->filesystem = $filesystem;
+ $this->phpbb_root_path = $phpbb_root_path;
+ $this->php_ext = $php_ext;
+ $this->user = $user;
+
+ $this->extensions = ($this->cache) ? $this->cache->get($this->cache_name) : false;
+
+ if ($this->extensions === false)
+ {
+ $this->load_extensions();
+ }
+ }
+
+ /**
+ * Loads all extension information from the database
+ *
+ * @return null
+ */
+ public function load_extensions()
+ {
+ $this->extensions = array();
+
+ // Do not try to load any extensions if the extension table
+ // does not exist or when installing or updating.
+ // Note: database updater invokes this code, and in 3.0
+ // there is no extension table therefore the rest of this function
+ // fails
+ if (defined('IN_INSTALL') || version_compare($this->config['version'], '3.1.0-dev', '<'))
+ {
+ return;
+ }
+
+ $sql = 'SELECT *
+ FROM ' . $this->extension_table;
+
+ $result = $this->db->sql_query($sql);
+ $extensions = $this->db->sql_fetchrowset($result);
+ $this->db->sql_freeresult($result);
+
+ foreach ($extensions as $extension)
+ {
+ $extension['ext_path'] = $this->get_extension_path($extension['ext_name']);
+ $this->extensions[$extension['ext_name']] = $extension;
+ }
+
+ ksort($this->extensions);
+
+ if ($this->cache)
+ {
+ $this->cache->put($this->cache_name, $this->extensions);
+ }
+ }
+
+ /**
+ * Generates the path to an extension
+ *
+ * @param string $name The name of the extension
+ * @param bool $phpbb_relative Whether the path should be relative to phpbb root
+ * @return string Path to an extension
+ */
+ public function get_extension_path($name, $phpbb_relative = false)
+ {
+ $name = str_replace('.', '', $name);
+
+ return (($phpbb_relative) ? $this->phpbb_root_path : '') . 'ext/' . $name . '/';
+ }
+
+ /**
+ * Instantiates the extension meta class for the extension with the given name
+ *
+ * @param string $name The extension name
+ * @return \phpbb\extension\extension_interface Instance of the extension meta class or
+ * \phpbb\extension\base if the class does not exist
+ */
+ public function get_extension($name)
+ {
+ $extension_class_name = str_replace('/', '\\', $name) . '\\ext';
+
+ $migrator = $this->container->get('migrator');
+
+ if (class_exists($extension_class_name))
+ {
+ return new $extension_class_name($this->container, $this->get_finder(), $migrator, $name, $this->get_extension_path($name, true));
+ }
+ else
+ {
+ return new \phpbb\extension\base($this->container, $this->get_finder(), $migrator, $name, $this->get_extension_path($name, true));
+ }
+ }
+
+ /**
+ * Instantiates the metadata manager for the extension with the given name
+ *
+ * @param string $name The extension name
+ * @param \phpbb\template\template $template The template manager or null
+ * @return \phpbb\extension\metadata_manager Instance of the metadata manager
+ */
+ public function create_extension_metadata_manager($name, \phpbb\template\template $template = null)
+ {
+ return new \phpbb\extension\metadata_manager($name, $this->config, $this, $template, $this->user, $this->phpbb_root_path);
+ }
+
+ /**
+ * Runs a step of the extension enabling process.
+ *
+ * Allows the exentension to enable in a long running script that works
+ * in multiple steps across requests. State is kept for the extension
+ * in the extensions table.
+ *
+ * @param string $name The extension's name
+ * @return bool False if enabling is finished, true otherwise
+ */
+ public function enable_step($name)
+ {
+ // ignore extensions that are already enabled
+ if (isset($this->extensions[$name]) && $this->extensions[$name]['ext_active'])
+ {
+ return false;
+ }
+
+ $old_state = (isset($this->extensions[$name]['ext_state'])) ? unserialize($this->extensions[$name]['ext_state']) : false;
+
+ $extension = $this->get_extension($name);
+
+ if (!$extension->is_enableable())
+ {
+ return false;
+ }
+
+ $state = $extension->enable_step($old_state);
+
+ $active = ($state === false);
+
+ $extension_data = array(
+ 'ext_name' => $name,
+ 'ext_active' => $active,
+ 'ext_state' => serialize($state),
+ );
+
+ $this->extensions[$name] = $extension_data;
+ $this->extensions[$name]['ext_path'] = $this->get_extension_path($extension_data['ext_name']);
+ ksort($this->extensions);
+
+ $sql = 'SELECT COUNT(ext_name) as row_count
+ FROM ' . $this->extension_table . "
+ WHERE ext_name = '" . $this->db->sql_escape($name) . "'";
+ $result = $this->db->sql_query($sql);
+ $count = $this->db->sql_fetchfield('row_count');
+ $this->db->sql_freeresult($result);
+
+ if ($count)
+ {
+ $sql = 'UPDATE ' . $this->extension_table . '
+ SET ' . $this->db->sql_build_array('UPDATE', $extension_data) . "
+ WHERE ext_name = '" . $this->db->sql_escape($name) . "'";
+ $this->db->sql_query($sql);
+ }
+ else
+ {
+ $sql = 'INSERT INTO ' . $this->extension_table . '
+ ' . $this->db->sql_build_array('INSERT', $extension_data);
+ $this->db->sql_query($sql);
+ }
+
+ if ($this->cache)
+ {
+ $this->cache->purge();
+ }
+
+ if ($active)
+ {
+ $this->config->increment('assets_version', 1);
+ }
+
+ return !$active;
+ }
+
+ /**
+ * Enables an extension
+ *
+ * This method completely enables an extension. But it could be long running
+ * so never call this in a script that has a max_execution time.
+ *
+ * @param string $name The extension's name
+ * @return null
+ */
+ public function enable($name)
+ {
+ // @codingStandardsIgnoreStart
+ while ($this->enable_step($name));
+ // @codingStandardsIgnoreEnd
+ }
+
+ /**
+ * Disables an extension
+ *
+ * Calls the disable method on the extension's meta class to allow it to
+ * process the event.
+ *
+ * @param string $name The extension's name
+ * @return bool False if disabling is finished, true otherwise
+ */
+ public function disable_step($name)
+ {
+ // ignore extensions that are already disabled
+ if (!isset($this->extensions[$name]) || !$this->extensions[$name]['ext_active'])
+ {
+ return false;
+ }
+
+ $old_state = unserialize($this->extensions[$name]['ext_state']);
+
+ $extension = $this->get_extension($name);
+ $state = $extension->disable_step($old_state);
+
+ // continue until the state is false
+ if ($state !== false)
+ {
+ $extension_data = array(
+ 'ext_state' => serialize($state),
+ );
+ $this->extensions[$name]['ext_state'] = serialize($state);
+
+ $sql = 'UPDATE ' . $this->extension_table . '
+ SET ' . $this->db->sql_build_array('UPDATE', $extension_data) . "
+ WHERE ext_name = '" . $this->db->sql_escape($name) . "'";
+ $this->db->sql_query($sql);
+
+ if ($this->cache)
+ {
+ $this->cache->purge();
+ }
+
+ return true;
+ }
+
+ $extension_data = array(
+ 'ext_active' => false,
+ 'ext_state' => serialize(false),
+ );
+ $this->extensions[$name]['ext_active'] = false;
+ $this->extensions[$name]['ext_state'] = serialize(false);
+
+ $sql = 'UPDATE ' . $this->extension_table . '
+ SET ' . $this->db->sql_build_array('UPDATE', $extension_data) . "
+ WHERE ext_name = '" . $this->db->sql_escape($name) . "'";
+ $this->db->sql_query($sql);
+
+ if ($this->cache)
+ {
+ $this->cache->purge();
+ }
+
+ return false;
+ }
+
+ /**
+ * Disables an extension
+ *
+ * Disables an extension completely at once. This process could run for a
+ * while so never call this in a script that has a max_execution time.
+ *
+ * @param string $name The extension's name
+ * @return null
+ */
+ public function disable($name)
+ {
+ // @codingStandardsIgnoreStart
+ while ($this->disable_step($name));
+ // @codingStandardsIgnoreEnd
+ }
+
+ /**
+ * Purge an extension
+ *
+ * Disables the extension first if active, and then calls purge on the
+ * extension's meta class to delete the extension's database content.
+ *
+ * @param string $name The extension's name
+ * @return bool False if purging is finished, true otherwise
+ */
+ public function purge_step($name)
+ {
+ // ignore extensions that do not exist
+ if (!isset($this->extensions[$name]))
+ {
+ return false;
+ }
+
+ // disable first if necessary
+ if ($this->extensions[$name]['ext_active'])
+ {
+ $this->disable($name);
+ }
+
+ $old_state = unserialize($this->extensions[$name]['ext_state']);
+
+ $extension = $this->get_extension($name);
+ $state = $extension->purge_step($old_state);
+
+ // continue until the state is false
+ if ($state !== false)
+ {
+ $extension_data = array(
+ 'ext_state' => serialize($state),
+ );
+ $this->extensions[$name]['ext_state'] = serialize($state);
+
+ $sql = 'UPDATE ' . $this->extension_table . '
+ SET ' . $this->db->sql_build_array('UPDATE', $extension_data) . "
+ WHERE ext_name = '" . $this->db->sql_escape($name) . "'";
+ $this->db->sql_query($sql);
+
+ if ($this->cache)
+ {
+ $this->cache->purge();
+ }
+
+ return true;
+ }
+
+ unset($this->extensions[$name]);
+
+ $sql = 'DELETE FROM ' . $this->extension_table . "
+ WHERE ext_name = '" . $this->db->sql_escape($name) . "'";
+ $this->db->sql_query($sql);
+
+ if ($this->cache)
+ {
+ $this->cache->purge();
+ }
+
+ return false;
+ }
+
+ /**
+ * Purge an extension
+ *
+ * Purges an extension completely at once. This process could run for a while
+ * so never call this in a script that has a max_execution time.
+ *
+ * @param string $name The extension's name
+ * @return null
+ */
+ public function purge($name)
+ {
+ // @codingStandardsIgnoreStart
+ while ($this->purge_step($name));
+ // @codingStandardsIgnoreEnd
+ }
+
+ /**
+ * Retrieves a list of all available extensions on the filesystem
+ *
+ * @return array An array with extension names as keys and paths to the
+ * extension as values
+ */
+ public function all_available()
+ {
+ $available = array();
+ if (!is_dir($this->phpbb_root_path . 'ext/'))
+ {
+ return $available;
+ }
+
+ $iterator = new \RecursiveIteratorIterator(
+ new \phpbb\recursive_dot_prefix_filter_iterator(
+ new \RecursiveDirectoryIterator($this->phpbb_root_path . 'ext/', \FilesystemIterator::NEW_CURRENT_AND_KEY | \FilesystemIterator::FOLLOW_SYMLINKS)
+ ),
+ \RecursiveIteratorIterator::SELF_FIRST
+ );
+ $iterator->setMaxDepth(2);
+
+ foreach ($iterator as $file_info)
+ {
+ if ($file_info->isFile() && $file_info->getFilename() == 'composer.json')
+ {
+ $ext_name = $iterator->getInnerIterator()->getSubPath();
+ $ext_name = str_replace(DIRECTORY_SEPARATOR, '/', $ext_name);
+ if ($this->is_available($ext_name))
+ {
+ $available[$ext_name] = $this->phpbb_root_path . 'ext/' . $ext_name . '/';
+ }
+ }
+ }
+ ksort($available);
+ return $available;
+ }
+
+ /**
+ * Retrieves all configured extensions.
+ *
+ * All enabled and disabled extensions are considered configured. A purged
+ * extension that is no longer in the database is not configured.
+ *
+ * @return array An array with extension names as keys and and the
+ * database stored extension information as values
+ */
+ public function all_configured()
+ {
+ $configured = array();
+ foreach ($this->extensions as $name => $data)
+ {
+ $data['ext_path'] = $this->phpbb_root_path . $data['ext_path'];
+ $configured[$name] = $data;
+ }
+ return $configured;
+ }
+
+ /**
+ * Retrieves all enabled extensions.
+ *
+ * @return array An array with extension names as keys and and the
+ * database stored extension information as values
+ */
+ public function all_enabled()
+ {
+ $enabled = array();
+ foreach ($this->extensions as $name => $data)
+ {
+ if ($data['ext_active'])
+ {
+ $enabled[$name] = $this->phpbb_root_path . $data['ext_path'];
+ }
+ }
+ return $enabled;
+ }
+
+ /**
+ * Retrieves all disabled extensions.
+ *
+ * @return array An array with extension names as keys and and the
+ * database stored extension information as values
+ */
+ public function all_disabled()
+ {
+ $disabled = array();
+ foreach ($this->extensions as $name => $data)
+ {
+ if (!$data['ext_active'])
+ {
+ $disabled[$name] = $this->phpbb_root_path . $data['ext_path'];
+ }
+ }
+ return $disabled;
+ }
+
+ /**
+ * Check to see if a given extension is available on the filesystem
+ *
+ * @param string $name Extension name to check NOTE: Can be user input
+ * @return bool Depending on whether or not the extension is available
+ */
+ public function is_available($name)
+ {
+ $md_manager = $this->create_extension_metadata_manager($name);
+ try
+ {
+ return $md_manager->get_metadata('all') && $md_manager->validate_enable();
+ }
+ catch (\phpbb\extension\exception $e)
+ {
+ return false;
+ }
+ }
+
+ /**
+ * Check to see if a given extension is enabled
+ *
+ * @param string $name Extension name to check
+ * @return bool Depending on whether or not the extension is enabled
+ */
+ public function is_enabled($name)
+ {
+ return isset($this->extensions[$name]) && $this->extensions[$name]['ext_active'];
+ }
+
+ /**
+ * Check to see if a given extension is disabled
+ *
+ * @param string $name Extension name to check
+ * @return bool Depending on whether or not the extension is disabled
+ */
+ public function is_disabled($name)
+ {
+ return isset($this->extensions[$name]) && !$this->extensions[$name]['ext_active'];
+ }
+
+ /**
+ * Check to see if a given extension is configured
+ *
+ * All enabled and disabled extensions are considered configured. A purged
+ * extension that is no longer in the database is not configured.
+ *
+ * @param string $name Extension name to check
+ * @return bool Depending on whether or not the extension is configured
+ */
+ public function is_configured($name)
+ {
+ return isset($this->extensions[$name]);
+ }
+
+ /**
+ * Check to see if a given extension is purged
+ *
+ * An extension is purged if it is available, not enabled and not disabled.
+ *
+ * @param string $name Extension name to check
+ * @return bool Depending on whether or not the extension is purged
+ */
+ public function is_purged($name)
+ {
+ return $this->is_available($name) && !$this->is_configured($name);
+ }
+
+ /**
+ * Instantiates a \phpbb\finder.
+ *
+ * @param bool $use_all_available Should we load all extensions, or just enabled ones
+ * @return \phpbb\finder An extension finder instance
+ */
+ public function get_finder($use_all_available = false)
+ {
+ $finder = new \phpbb\finder($this->filesystem, $this->phpbb_root_path, $this->cache, $this->php_ext, $this->cache_name . '_finder');
+ if ($use_all_available)
+ {
+ $finder->set_extensions(array_keys($this->all_available()));
+ }
+ else
+ {
+ $finder->set_extensions(array_keys($this->all_enabled()));
+ }
+ return $finder;
+ }
+}
diff --git a/phpBB/phpbb/extension/metadata_manager.php b/phpBB/phpbb/extension/metadata_manager.php
new file mode 100644
index 0000000000..a09f07bed2
--- /dev/null
+++ b/phpBB/phpbb/extension/metadata_manager.php
@@ -0,0 +1,343 @@
+<?php
+/**
+*
+* This file is part of the phpBB Forum Software package.
+*
+* @copyright (c) phpBB Limited <https://www.phpbb.com>
+* @license GNU General Public License, version 2 (GPL-2.0)
+*
+* For full copyright and license information, please see
+* the docs/CREDITS.txt file.
+*
+*/
+
+namespace phpbb\extension;
+
+/**
+* The extension metadata manager validates and gets meta-data for extensions
+*/
+class metadata_manager
+{
+ /**
+ * phpBB Config instance
+ * @var \phpbb\config\config
+ */
+ protected $config;
+
+ /**
+ * phpBB Extension Manager
+ * @var \phpbb\extension\manager
+ */
+ protected $extension_manager;
+
+ /**
+ * phpBB Template instance
+ * @var \phpbb\template\template
+ */
+ protected $template;
+
+ /**
+ * phpBB User instance
+ * @var \phpbb\user
+ */
+ protected $user;
+
+ /**
+ * phpBB root path
+ * @var string
+ */
+ protected $phpbb_root_path;
+
+ /**
+ * Name (including vendor) of the extension
+ * @var string
+ */
+ protected $ext_name;
+
+ /**
+ * Metadata from the composer.json file
+ * @var array
+ */
+ protected $metadata;
+
+ /**
+ * Link (including root path) to the metadata file
+ * @var string
+ */
+ protected $metadata_file;
+
+ // @codingStandardsIgnoreStart
+ /**
+ * Creates the metadata manager
+ *
+ * @param string $ext_name Name (including vendor) of the extension
+ * @param \phpbb\config\config $config phpBB Config instance
+ * @param \phpbb\extension\manager $extension_manager An instance of the phpBB extension manager
+ * @param \phpbb\template\template $template phpBB Template instance or null
+ * @param \phpbb\user $user User instance
+ * @param string $phpbb_root_path Path to the phpbb includes directory.
+ */
+ public function __construct($ext_name, \phpbb\config\config $config, \phpbb\extension\manager $extension_manager, \phpbb\template\template $template = null, \phpbb\user $user, $phpbb_root_path)
+ {
+ $this->config = $config;
+ $this->extension_manager = $extension_manager;
+ $this->template = $template;
+ $this->user = $user;
+ $this->phpbb_root_path = $phpbb_root_path;
+
+ $this->ext_name = $ext_name;
+ $this->metadata = array();
+ $this->metadata_file = '';
+ }
+ // @codingStandardsIgnoreEnd
+
+ /**
+ * Processes and gets the metadata requested
+ *
+ * @param string $element All for all metadata that it has and is valid, otherwise specify which section you want by its shorthand term.
+ * @return array Contains all of the requested metadata, throws an exception on failure
+ */
+ public function get_metadata($element = 'all')
+ {
+ // Fetch and clean the metadata if not done yet
+ if ($this->metadata_file === '')
+ {
+ $this->fetch_metadata_from_file();
+ }
+
+ switch ($element)
+ {
+ case 'all':
+ default:
+ $this->validate();
+ return $this->metadata;
+ break;
+
+ case 'version':
+ case 'name':
+ $this->validate($element);
+ return $this->metadata[$element];
+ break;
+
+ case 'display-name':
+ return (isset($this->metadata['extra']['display-name'])) ? $this->metadata['extra']['display-name'] : $this->get_metadata('name');
+ break;
+ }
+ }
+
+ /**
+ * Sets the path of the metadata file, gets its contents and cleans loaded file
+ *
+ * @throws \phpbb\extension\exception
+ */
+ private function fetch_metadata_from_file()
+ {
+ $ext_filepath = $this->extension_manager->get_extension_path($this->ext_name);
+ $metadata_filepath = $this->phpbb_root_path . $ext_filepath . 'composer.json';
+
+ $this->metadata_file = $metadata_filepath;
+
+ if (!file_exists($this->metadata_file))
+ {
+ throw new \phpbb\extension\exception($this->user->lang('FILE_NOT_FOUND', $this->metadata_file));
+ }
+
+ if (!($file_contents = file_get_contents($this->metadata_file)))
+ {
+ throw new \phpbb\extension\exception($this->user->lang('FILE_CONTENT_ERR', $this->metadata_file));
+ }
+
+ if (($metadata = json_decode($file_contents, true)) === null)
+ {
+ throw new \phpbb\extension\exception($this->user->lang('FILE_JSON_DECODE_ERR', $this->metadata_file));
+ }
+
+ array_walk_recursive($metadata, array($this, 'sanitize_json'));
+ $this->metadata = $metadata;
+ }
+
+ /**
+ * Sanitize input from JSON array using htmlspecialchars()
+ *
+ * @param mixed $value Value of array row
+ * @param string $key Key of array row
+ */
+ public function sanitize_json(&$value, $key)
+ {
+ $value = htmlspecialchars($value);
+ }
+
+ /**
+ * Validate fields
+ *
+ * @param string $name ("all" for display and enable validation
+ * "display" for name, type, and authors
+ * "name", "type")
+ * @return Bool True if valid, throws an exception if invalid
+ * @throws \phpbb\extension\exception
+ */
+ public function validate($name = 'display')
+ {
+ // Basic fields
+ $fields = array(
+ 'name' => '#^[a-zA-Z0-9_\x7f-\xff]{2,}/[a-zA-Z0-9_\x7f-\xff]{2,}$#',
+ 'type' => '#^phpbb-extension$#',
+ 'license' => '#.+#',
+ 'version' => '#.+#',
+ );
+
+ switch ($name)
+ {
+ case 'all':
+ $this->validate_enable();
+ // no break
+
+ case 'display':
+ foreach ($fields as $field => $data)
+ {
+ $this->validate($field);
+ }
+
+ $this->validate_authors();
+ break;
+
+ default:
+ if (isset($fields[$name]))
+ {
+ if (!isset($this->metadata[$name]))
+ {
+ throw new \phpbb\extension\exception($this->user->lang('META_FIELD_NOT_SET', $name));
+ }
+
+ if (!preg_match($fields[$name], $this->metadata[$name]))
+ {
+ throw new \phpbb\extension\exception($this->user->lang('META_FIELD_INVALID', $name));
+ }
+ }
+ break;
+ }
+
+ return true;
+ }
+
+ /**
+ * Validates the contents of the authors field
+ *
+ * @return boolean True when passes validation, throws exception if invalid
+ * @throws \phpbb\extension\exception
+ */
+ public function validate_authors()
+ {
+ if (empty($this->metadata['authors']))
+ {
+ throw new \phpbb\extension\exception($this->user->lang('META_FIELD_NOT_SET', 'authors'));
+ }
+
+ foreach ($this->metadata['authors'] as $author)
+ {
+ if (!isset($author['name']))
+ {
+ throw new \phpbb\extension\exception($this->user->lang('META_FIELD_NOT_SET', 'author name'));
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * This array handles the verification that this extension can be enabled on this board
+ *
+ * @return bool True if validation succeeded, throws an exception if invalid
+ * @throws \phpbb\extension\exception
+ */
+ public function validate_enable()
+ {
+ // Check for valid directory & phpBB, PHP versions
+ return $this->validate_dir() && $this->validate_require_phpbb() && $this->validate_require_php();
+ }
+
+ /**
+ * Validates the most basic directory structure to ensure it follows <vendor>/<ext> convention.
+ *
+ * @return boolean True when passes validation, throws an exception if invalid
+ * @throws \phpbb\extension\exception
+ */
+ public function validate_dir()
+ {
+ if (substr_count($this->ext_name, '/') !== 1 || $this->ext_name != $this->get_metadata('name'))
+ {
+ throw new \phpbb\extension\exception($this->user->lang('EXTENSION_DIR_INVALID'));
+ }
+
+ return true;
+ }
+
+
+ /**
+ * Validates the contents of the phpbb requirement field
+ *
+ * @return boolean True when passes validation, throws an exception if invalid
+ * @throws \phpbb\extension\exception
+ */
+ public function validate_require_phpbb()
+ {
+ if (!isset($this->metadata['extra']['soft-require']['phpbb/phpbb']))
+ {
+ throw new \phpbb\extension\exception($this->user->lang('META_FIELD_NOT_SET', 'soft-require'));
+ }
+
+ return true;
+ }
+
+ /**
+ * Validates the contents of the php requirement field
+ *
+ * @return boolean True when passes validation, throws an exception if invalid
+ * @throws \phpbb\extension\exception
+ */
+ public function validate_require_php()
+ {
+ if (!isset($this->metadata['require']['php']))
+ {
+ throw new \phpbb\extension\exception($this->user->lang('META_FIELD_NOT_SET', 'require php'));
+ }
+
+ return true;
+ }
+
+ /**
+ * Outputs the metadata into the template
+ *
+ * @return null
+ */
+ public function output_template_data()
+ {
+ $this->template->assign_vars(array(
+ 'META_NAME' => $this->metadata['name'],
+ 'META_TYPE' => $this->metadata['type'],
+ 'META_DESCRIPTION' => (isset($this->metadata['description'])) ? $this->metadata['description'] : '',
+ 'META_HOMEPAGE' => (isset($this->metadata['homepage'])) ? $this->metadata['homepage'] : '',
+ 'META_VERSION' => (isset($this->metadata['version'])) ? $this->metadata['version'] : '',
+ 'META_TIME' => (isset($this->metadata['time'])) ? $this->metadata['time'] : '',
+ 'META_LICENSE' => $this->metadata['license'],
+
+ 'META_REQUIRE_PHP' => (isset($this->metadata['require']['php'])) ? $this->metadata['require']['php'] : '',
+ 'META_REQUIRE_PHP_FAIL' => (isset($this->metadata['require']['php'])) ? false : true,
+
+ 'META_REQUIRE_PHPBB' => (isset($this->metadata['extra']['soft-require']['phpbb/phpbb'])) ? $this->metadata['extra']['soft-require']['phpbb/phpbb'] : '',
+ 'META_REQUIRE_PHPBB_FAIL' => (isset($this->metadata['extra']['soft-require']['phpbb/phpbb'])) ? false : true,
+
+ 'META_DISPLAY_NAME' => (isset($this->metadata['extra']['display-name'])) ? $this->metadata['extra']['display-name'] : '',
+ ));
+
+ foreach ($this->metadata['authors'] as $author)
+ {
+ $this->template->assign_block_vars('meta_authors', array(
+ 'AUTHOR_NAME' => $author['name'],
+ 'AUTHOR_EMAIL' => (isset($author['email'])) ? $author['email'] : '',
+ 'AUTHOR_HOMEPAGE' => (isset($author['homepage'])) ? $author['homepage'] : '',
+ 'AUTHOR_ROLE' => (isset($author['role'])) ? $author['role'] : '',
+ ));
+ }
+ }
+}
diff --git a/phpBB/phpbb/extension/provider.php b/phpBB/phpbb/extension/provider.php
new file mode 100644
index 0000000000..1c42cf7b5e
--- /dev/null
+++ b/phpBB/phpbb/extension/provider.php
@@ -0,0 +1,72 @@
+<?php
+/**
+*
+* This file is part of the phpBB Forum Software package.
+*
+* @copyright (c) phpBB Limited <https://www.phpbb.com>
+* @license GNU General Public License, version 2 (GPL-2.0)
+*
+* For full copyright and license information, please see
+* the docs/CREDITS.txt file.
+*
+*/
+
+namespace phpbb\extension;
+
+/**
+* Provides a set of items found in extensions.
+*
+* This abstract class is essentially a wrapper around item-specific
+* finding logic. It handles storing the extension manager via constructor
+* for the finding logic to use to find the items, and provides an
+* iterator interface over the items found by the finding logic.
+*
+* Items could be anything, for example template paths or cron task names.
+* Derived classes completely define what the items are.
+*/
+abstract class provider implements \IteratorAggregate
+{
+ /**
+ * Array holding all found items
+ * @var array|null
+ */
+ protected $items = null;
+
+ /**
+ * An extension manager to search for items in extensions
+ * @var \phpbb\extension\manager
+ */
+ protected $extension_manager;
+
+ /**
+ * Constructor. Loads all available items.
+ *
+ * @param \phpbb\extension\manager $extension_manager phpBB extension manager
+ */
+ public function __construct(\phpbb\extension\manager $extension_manager)
+ {
+ $this->extension_manager = $extension_manager;
+ }
+
+ /**
+ * Finds items using the extension manager.
+ *
+ * @return array List of task names
+ */
+ abstract protected function find();
+
+ /**
+ * Retrieve an iterator over all items
+ *
+ * @return \ArrayIterator An iterator for the array of template paths
+ */
+ public function getIterator()
+ {
+ if ($this->items === null)
+ {
+ $this->items = $this->find();
+ }
+
+ return new \ArrayIterator($this->items);
+ }
+}