diff options
Diffstat (limited to 'lib/BridgeAbstract.php')
-rw-r--r-- | lib/BridgeAbstract.php | 802 |
1 files changed, 410 insertions, 392 deletions
diff --git a/lib/BridgeAbstract.php b/lib/BridgeAbstract.php index 38e3da03..c479f53e 100644 --- a/lib/BridgeAbstract.php +++ b/lib/BridgeAbstract.php @@ -1,4 +1,5 @@ <?php + /** * This file is part of RSS-Bridge, a PHP project capable of generating RSS and * Atom feeds for websites that don't have one. @@ -6,9 +7,9 @@ * For the full license information, please view the UNLICENSE file distributed * with this source code. * - * @package Core - * @license http://unlicense.org/ UNLICENSE - * @link https://github.com/rss-bridge/rss-bridge + * @package Core + * @license http://unlicense.org/ UNLICENSE + * @link https://github.com/rss-bridge/rss-bridge */ /** @@ -24,393 +25,410 @@ * @todo Add specification for PARAMETERS () * @todo Add specification for $items */ -abstract class BridgeAbstract implements BridgeInterface { - - /** - * Name of the bridge - * - * Use {@see BridgeAbstract::getName()} to read this parameter - */ - const NAME = 'Unnamed bridge'; - - /** - * URI to the site the bridge is intended to be used for. - * - * Use {@see BridgeAbstract::getURI()} to read this parameter - */ - const URI = ''; - - /** - * Donation URI to the site the bridge is intended to be used for. - * - * Use {@see BridgeAbstract::getDonationURI()} to read this parameter - */ - const DONATION_URI = ''; - - /** - * A brief description of what the bridge can do - * - * Use {@see BridgeAbstract::getDescription()} to read this parameter - */ - const DESCRIPTION = 'No description provided'; - - /** - * The name of the maintainer. Multiple maintainers can be separated by comma - * - * Use {@see BridgeAbstract::getMaintainer()} to read this parameter - */ - const MAINTAINER = 'No maintainer'; - - /** - * The default cache timeout for the bridge - * - * Use {@see BridgeAbstract::getCacheTimeout()} to read this parameter - */ - const CACHE_TIMEOUT = 3600; - - /** - * Configuration for the bridge - * - * Use {@see BridgeAbstract::getConfiguration()} to read this parameter - */ - const CONFIGURATION = array(); - - /** - * Parameters for the bridge - * - * Use {@see BridgeAbstract::getParameters()} to read this parameter - */ - const PARAMETERS = array(); - - /** - * Test cases for detectParameters for the bridge - */ - const TEST_DETECT_PARAMETERS = array(); - - /** - * This is a convenient const for the limit option in bridge contexts. - * Can be inlined and modified if necessary. - */ - protected const LIMIT = [ - 'name' => 'Limit', - 'type' => 'number', - 'title' => 'Maximum number of items to return', - ]; - - /** - * Holds the list of items collected by the bridge - * - * Items must be collected by {@see BridgeInterface::collectData()} - * - * Use {@see BridgeAbstract::getItems()} to access items. - * - * @var array - */ - protected $items = array(); - - /** - * Holds the list of input parameters used by the bridge - * - * Do not access this parameter directly! - * Use {@see BridgeAbstract::setInputs()} and {@see BridgeAbstract::getInput()} instead! - * - * @var array - */ - protected $inputs = array(); - - /** - * Holds the name of the queried context - * - * @var string - */ - protected $queriedContext = ''; - - /** {@inheritdoc} */ - public function getItems(){ - return $this->items; - } - - /** - * Sets the input values for a given context. - * - * @param array $inputs Associative array of inputs - * @param string $queriedContext The context name - * @return void - */ - protected function setInputs(array $inputs, $queriedContext){ - // Import and assign all inputs to their context - foreach($inputs as $name => $value) { - foreach(static::PARAMETERS as $context => $set) { - if(array_key_exists($name, static::PARAMETERS[$context])) { - $this->inputs[$context][$name]['value'] = $value; - } - } - } - - // Apply default values to missing data - $contexts = array($queriedContext); - if(array_key_exists('global', static::PARAMETERS)) { - $contexts[] = 'global'; - } - - foreach($contexts as $context) { - foreach(static::PARAMETERS[$context] as $name => $properties) { - if(isset($this->inputs[$context][$name]['value'])) { - continue; - } - - $type = isset($properties['type']) ? $properties['type'] : 'text'; - - switch($type) { - case 'checkbox': - if(!isset($properties['defaultValue'])) { - $this->inputs[$context][$name]['value'] = false; - } else { - $this->inputs[$context][$name]['value'] = $properties['defaultValue']; - } - break; - case 'list': - if(!isset($properties['defaultValue'])) { - $firstItem = reset($properties['values']); - if(is_array($firstItem)) { - $firstItem = reset($firstItem); - } - $this->inputs[$context][$name]['value'] = $firstItem; - } else { - $this->inputs[$context][$name]['value'] = $properties['defaultValue']; - } - break; - default: - if(isset($properties['defaultValue'])) { - $this->inputs[$context][$name]['value'] = $properties['defaultValue']; - } - break; - } - } - } - - // Copy global parameter values to the guessed context - if(array_key_exists('global', static::PARAMETERS)) { - foreach(static::PARAMETERS['global'] as $name => $properties) { - if(isset($inputs[$name])) { - $value = $inputs[$name]; - } elseif(isset($properties['defaultValue'])) { - $value = $properties['defaultValue']; - } else { - continue; - } - $this->inputs[$queriedContext][$name]['value'] = $value; - } - } - - // Only keep guessed context parameters values - if(isset($this->inputs[$queriedContext])) { - $this->inputs = array($queriedContext => $this->inputs[$queriedContext]); - } else { - $this->inputs = array(); - } - } - - /** - * Set inputs for the bridge - * - * Returns errors and aborts execution if the provided input parameters are - * invalid. - * - * @param array List of input parameters. Each element in this list must - * relate to an item in {@see BridgeAbstract::PARAMETERS} - * @return void - */ - public function setDatas(array $inputs){ - - if(isset($inputs['context'])) { // Context hinting (optional) - $this->queriedContext = $inputs['context']; - unset($inputs['context']); - } - - if(empty(static::PARAMETERS)) { - - if(!empty($inputs)) { - returnClientError('Invalid parameters value(s)'); - } - - return; - - } - - $validator = new ParameterValidator(); - - if(!$validator->validateData($inputs, static::PARAMETERS)) { - $parameters = array_map( - function($i){ return $i['name']; }, // Just display parameter names - $validator->getInvalidParameters() - ); - - returnClientError( - 'Invalid parameters value(s): ' - . implode(', ', $parameters) - ); - } - - // Guess the context from input data - if(empty($this->queriedContext)) { - $this->queriedContext = $validator->getQueriedContext($inputs, static::PARAMETERS); - } - - if(is_null($this->queriedContext)) { - returnClientError('Required parameter(s) missing'); - } elseif($this->queriedContext === false) { - returnClientError('Mixed context parameters'); - } - - $this->setInputs($inputs, $this->queriedContext); - - } - - /** - * Loads configuration for the bridge - * - * Returns errors and aborts execution if the provided configuration is - * invalid. - * - * @return void - */ - public function loadConfiguration() { - foreach(static::CONFIGURATION as $optionName => $optionValue) { - - $configurationOption = Configuration::getConfig(get_class($this), $optionName); - - if($configurationOption !== null) { - $this->configuration[$optionName] = $configurationOption; - continue; - } - - if(isset($optionValue['required']) && $optionValue['required'] === true) { - returnServerError( - 'Missing configuration option: ' - . $optionName - ); - } elseif(isset($optionValue['defaultValue'])) { - $this->configuration[$optionName] = $optionValue['defaultValue']; - } - - } - } - - /** - * Returns the value for the provided input - * - * @param string $input The input name - * @return mixed|null The input value or null if the input is not defined - */ - protected function getInput($input){ - if(!isset($this->inputs[$this->queriedContext][$input]['value'])) { - return null; - } - return $this->inputs[$this->queriedContext][$input]['value']; - } - - /** - * Returns the value for the selected configuration - * - * @param string $input The option name - * @return mixed|null The option value or null if the input is not defined - */ - public function getOption($name){ - if(!isset($this->configuration[$name])) { - return null; - } - return $this->configuration[$name]; - } - - /** {@inheritdoc} */ - public function getDescription(){ - return static::DESCRIPTION; - } - - /** {@inheritdoc} */ - public function getMaintainer(){ - return static::MAINTAINER; - } - - /** {@inheritdoc} */ - public function getName(){ - return static::NAME; - } - - /** {@inheritdoc} */ - public function getIcon(){ - return static::URI . '/favicon.ico'; - } - - /** {@inheritdoc} */ - public function getConfiguration(){ - return static::CONFIGURATION; - } - - /** {@inheritdoc} */ - public function getParameters(){ - return static::PARAMETERS; - } - - /** {@inheritdoc} */ - public function getURI(){ - return static::URI; - } - - /** {@inheritdoc} */ - public function getDonationURI(){ - return static::DONATION_URI; - } - - /** {@inheritdoc} */ - public function getCacheTimeout(){ - return static::CACHE_TIMEOUT; - } - - /** {@inheritdoc} */ - public function detectParameters($url){ - $regex = '/^(https?:\/\/)?(www\.)?(.+?)(\/)?$/'; - if(empty(static::PARAMETERS) - && preg_match($regex, $url, $urlMatches) > 0 - && preg_match($regex, static::URI, $bridgeUriMatches) > 0 - && $urlMatches[3] === $bridgeUriMatches[3]) { - return array(); - } else { - return null; - } - } - - /** - * Loads a cached value for the specified key - * - * @param string $key Key name - * @param int $duration Cache duration (optional, default: 24 hours) - * @return mixed Cached value or null if the key doesn't exist or has expired - */ - protected function loadCacheValue($key, $duration = 86400){ - $cacheFac = new CacheFactory(); - - $cache = $cacheFac->create(Configuration::getConfig('cache', 'type')); - $cache->setScope(get_called_class()); - $cache->setKey($key); - if($cache->getTime() < time() - $duration) - return null; - return $cache->loadData(); - } - - /** - * Stores a value to cache with the specified key - * - * @param string $key Key name - * @param mixed $value Value to cache - */ - protected function saveCacheValue($key, $value){ - $cacheFac = new CacheFactory(); - - $cache = $cacheFac->create(Configuration::getConfig('cache', 'type')); - $cache->setScope(get_called_class()); - $cache->setKey($key); - $cache->saveData($value); - } +abstract class BridgeAbstract implements BridgeInterface +{ + /** + * Name of the bridge + * + * Use {@see BridgeAbstract::getName()} to read this parameter + */ + const NAME = 'Unnamed bridge'; + + /** + * URI to the site the bridge is intended to be used for. + * + * Use {@see BridgeAbstract::getURI()} to read this parameter + */ + const URI = ''; + + /** + * Donation URI to the site the bridge is intended to be used for. + * + * Use {@see BridgeAbstract::getDonationURI()} to read this parameter + */ + const DONATION_URI = ''; + + /** + * A brief description of what the bridge can do + * + * Use {@see BridgeAbstract::getDescription()} to read this parameter + */ + const DESCRIPTION = 'No description provided'; + + /** + * The name of the maintainer. Multiple maintainers can be separated by comma + * + * Use {@see BridgeAbstract::getMaintainer()} to read this parameter + */ + const MAINTAINER = 'No maintainer'; + + /** + * The default cache timeout for the bridge + * + * Use {@see BridgeAbstract::getCacheTimeout()} to read this parameter + */ + const CACHE_TIMEOUT = 3600; + + /** + * Configuration for the bridge + * + * Use {@see BridgeAbstract::getConfiguration()} to read this parameter + */ + const CONFIGURATION = []; + + /** + * Parameters for the bridge + * + * Use {@see BridgeAbstract::getParameters()} to read this parameter + */ + const PARAMETERS = []; + + /** + * Test cases for detectParameters for the bridge + */ + const TEST_DETECT_PARAMETERS = []; + + /** + * This is a convenient const for the limit option in bridge contexts. + * Can be inlined and modified if necessary. + */ + protected const LIMIT = [ + 'name' => 'Limit', + 'type' => 'number', + 'title' => 'Maximum number of items to return', + ]; + + /** + * Holds the list of items collected by the bridge + * + * Items must be collected by {@see BridgeInterface::collectData()} + * + * Use {@see BridgeAbstract::getItems()} to access items. + * + * @var array + */ + protected $items = []; + + /** + * Holds the list of input parameters used by the bridge + * + * Do not access this parameter directly! + * Use {@see BridgeAbstract::setInputs()} and {@see BridgeAbstract::getInput()} instead! + * + * @var array + */ + protected $inputs = []; + + /** + * Holds the name of the queried context + * + * @var string + */ + protected $queriedContext = ''; + + /** {@inheritdoc} */ + public function getItems() + { + return $this->items; + } + + /** + * Sets the input values for a given context. + * + * @param array $inputs Associative array of inputs + * @param string $queriedContext The context name + * @return void + */ + protected function setInputs(array $inputs, $queriedContext) + { + // Import and assign all inputs to their context + foreach ($inputs as $name => $value) { + foreach (static::PARAMETERS as $context => $set) { + if (array_key_exists($name, static::PARAMETERS[$context])) { + $this->inputs[$context][$name]['value'] = $value; + } + } + } + + // Apply default values to missing data + $contexts = [$queriedContext]; + if (array_key_exists('global', static::PARAMETERS)) { + $contexts[] = 'global'; + } + + foreach ($contexts as $context) { + foreach (static::PARAMETERS[$context] as $name => $properties) { + if (isset($this->inputs[$context][$name]['value'])) { + continue; + } + + $type = isset($properties['type']) ? $properties['type'] : 'text'; + + switch ($type) { + case 'checkbox': + if (!isset($properties['defaultValue'])) { + $this->inputs[$context][$name]['value'] = false; + } else { + $this->inputs[$context][$name]['value'] = $properties['defaultValue']; + } + break; + case 'list': + if (!isset($properties['defaultValue'])) { + $firstItem = reset($properties['values']); + if (is_array($firstItem)) { + $firstItem = reset($firstItem); + } + $this->inputs[$context][$name]['value'] = $firstItem; + } else { + $this->inputs[$context][$name]['value'] = $properties['defaultValue']; + } + break; + default: + if (isset($properties['defaultValue'])) { + $this->inputs[$context][$name]['value'] = $properties['defaultValue']; + } + break; + } + } + } + + // Copy global parameter values to the guessed context + if (array_key_exists('global', static::PARAMETERS)) { + foreach (static::PARAMETERS['global'] as $name => $properties) { + if (isset($inputs[$name])) { + $value = $inputs[$name]; + } elseif (isset($properties['defaultValue'])) { + $value = $properties['defaultValue']; + } else { + continue; + } + $this->inputs[$queriedContext][$name]['value'] = $value; + } + } + + // Only keep guessed context parameters values + if (isset($this->inputs[$queriedContext])) { + $this->inputs = [$queriedContext => $this->inputs[$queriedContext]]; + } else { + $this->inputs = []; + } + } + + /** + * Set inputs for the bridge + * + * Returns errors and aborts execution if the provided input parameters are + * invalid. + * + * @param array List of input parameters. Each element in this list must + * relate to an item in {@see BridgeAbstract::PARAMETERS} + * @return void + */ + public function setDatas(array $inputs) + { + if (isset($inputs['context'])) { // Context hinting (optional) + $this->queriedContext = $inputs['context']; + unset($inputs['context']); + } + + if (empty(static::PARAMETERS)) { + if (!empty($inputs)) { + returnClientError('Invalid parameters value(s)'); + } + + return; + } + + $validator = new ParameterValidator(); + + if (!$validator->validateData($inputs, static::PARAMETERS)) { + $parameters = array_map( + function ($i) { + return $i['name']; + }, // Just display parameter names + $validator->getInvalidParameters() + ); + + returnClientError( + 'Invalid parameters value(s): ' + . implode(', ', $parameters) + ); + } + + // Guess the context from input data + if (empty($this->queriedContext)) { + $this->queriedContext = $validator->getQueriedContext($inputs, static::PARAMETERS); + } + + if (is_null($this->queriedContext)) { + returnClientError('Required parameter(s) missing'); + } elseif ($this->queriedContext === false) { + returnClientError('Mixed context parameters'); + } + + $this->setInputs($inputs, $this->queriedContext); + } + + /** + * Loads configuration for the bridge + * + * Returns errors and aborts execution if the provided configuration is + * invalid. + * + * @return void + */ + public function loadConfiguration() + { + foreach (static::CONFIGURATION as $optionName => $optionValue) { + $configurationOption = Configuration::getConfig(get_class($this), $optionName); + + if ($configurationOption !== null) { + $this->configuration[$optionName] = $configurationOption; + continue; + } + + if (isset($optionValue['required']) && $optionValue['required'] === true) { + returnServerError( + 'Missing configuration option: ' + . $optionName + ); + } elseif (isset($optionValue['defaultValue'])) { + $this->configuration[$optionName] = $optionValue['defaultValue']; + } + } + } + + /** + * Returns the value for the provided input + * + * @param string $input The input name + * @return mixed|null The input value or null if the input is not defined + */ + protected function getInput($input) + { + if (!isset($this->inputs[$this->queriedContext][$input]['value'])) { + return null; + } + return $this->inputs[$this->queriedContext][$input]['value']; + } + + /** + * Returns the value for the selected configuration + * + * @param string $input The option name + * @return mixed|null The option value or null if the input is not defined + */ + public function getOption($name) + { + if (!isset($this->configuration[$name])) { + return null; + } + return $this->configuration[$name]; + } + + /** {@inheritdoc} */ + public function getDescription() + { + return static::DESCRIPTION; + } + + /** {@inheritdoc} */ + public function getMaintainer() + { + return static::MAINTAINER; + } + + /** {@inheritdoc} */ + public function getName() + { + return static::NAME; + } + + /** {@inheritdoc} */ + public function getIcon() + { + return static::URI . '/favicon.ico'; + } + + /** {@inheritdoc} */ + public function getConfiguration() + { + return static::CONFIGURATION; + } + + /** {@inheritdoc} */ + public function getParameters() + { + return static::PARAMETERS; + } + + /** {@inheritdoc} */ + public function getURI() + { + return static::URI; + } + + /** {@inheritdoc} */ + public function getDonationURI() + { + return static::DONATION_URI; + } + + /** {@inheritdoc} */ + public function getCacheTimeout() + { + return static::CACHE_TIMEOUT; + } + + /** {@inheritdoc} */ + public function detectParameters($url) + { + $regex = '/^(https?:\/\/)?(www\.)?(.+?)(\/)?$/'; + if ( + empty(static::PARAMETERS) + && preg_match($regex, $url, $urlMatches) > 0 + && preg_match($regex, static::URI, $bridgeUriMatches) > 0 + && $urlMatches[3] === $bridgeUriMatches[3] + ) { + return []; + } else { + return null; + } + } + + /** + * Loads a cached value for the specified key + * + * @param string $key Key name + * @param int $duration Cache duration (optional, default: 24 hours) + * @return mixed Cached value or null if the key doesn't exist or has expired + */ + protected function loadCacheValue($key, $duration = 86400) + { + $cacheFac = new CacheFactory(); + + $cache = $cacheFac->create(Configuration::getConfig('cache', 'type')); + $cache->setScope(get_called_class()); + $cache->setKey($key); + if ($cache->getTime() < time() - $duration) { + return null; + } + return $cache->loadData(); + } + + /** + * Stores a value to cache with the specified key + * + * @param string $key Key name + * @param mixed $value Value to cache + */ + protected function saveCacheValue($key, $value) + { + $cacheFac = new CacheFactory(); + + $cache = $cacheFac->create(Configuration::getConfig('cache', 'type')); + $cache->setScope(get_called_class()); + $cache->setKey($key); + $cache->saveData($value); + } } |