Reformat codebase v4 (#2872)

Reformat code base to PSR12

Co-authored-by: rssbridge <noreply@github.com>
This commit is contained in:
Dag 2022-07-01 15:10:30 +02:00 committed by GitHub
parent 66568e3a39
commit 4f75591060
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
398 changed files with 58607 additions and 56442 deletions

View file

@ -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,31 +7,31 @@
* 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
*/
class ActionFactory
{
private $folder;
private $folder;
public function __construct(string $folder = PATH_LIB_ACTIONS)
{
$this->folder = $folder;
}
public function __construct(string $folder = PATH_LIB_ACTIONS)
{
$this->folder = $folder;
}
/**
* @param string $name The name of the action e.g. "Display", "List", or "Connectivity"
*/
public function create(string $name): ActionInterface
{
$name = ucfirst(strtolower($name)) . 'Action';
$filePath = $this->folder . $name . '.php';
if(!file_exists($filePath)) {
throw new \Exception('Invalid action');
}
$className = '\\' . $name;
return new $className();
}
/**
* @param string $name The name of the action e.g. "Display", "List", or "Connectivity"
*/
public function create(string $name): ActionInterface
{
$name = ucfirst(strtolower($name)) . 'Action';
$filePath = $this->folder . $name . '.php';
if (!file_exists($filePath)) {
throw new \Exception('Invalid action');
}
$className = '\\' . $name;
return new $className();
}
}

View file

@ -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,21 +7,22 @@
* 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
*/
/**
* Interface for action objects.
*/
interface ActionInterface {
/**
* Execute the action.
*
* Note: This function directly outputs data to the user.
*
* @return void
*/
public function execute();
interface ActionInterface
{
/**
* Execute the action.
*
* Note: This function directly outputs data to the user.
*
* @return void
*/
public function execute();
}

View file

@ -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
*/
/**
@ -30,56 +31,57 @@
* @todo Add functions to detect if a user is authenticated or not. This can be
* utilized for limiting access to authorized users only.
*/
class Authentication {
/**
* Throw an exception when trying to create a new instance of this class.
* Use {@see Authentication::showPromptIfNeeded()} instead!
*
* @throws \LogicException if called.
*/
public function __construct(){
throw new \LogicException('Use ' . __CLASS__ . '::showPromptIfNeeded()!');
}
class Authentication
{
/**
* Throw an exception when trying to create a new instance of this class.
* Use {@see Authentication::showPromptIfNeeded()} instead!
*
* @throws \LogicException if called.
*/
public function __construct()
{
throw new \LogicException('Use ' . __CLASS__ . '::showPromptIfNeeded()!');
}
/**
* Requests the user for login credentials if necessary.
*
* Responds to an authentication request or returns the `WWW-Authenticate`
* header if authentication is enabled in the configuration of RSS-Bridge
* (`[authentication] enable = true`).
*
* @return void
*/
public static function showPromptIfNeeded() {
/**
* Requests the user for login credentials if necessary.
*
* Responds to an authentication request or returns the `WWW-Authenticate`
* header if authentication is enabled in the configuration of RSS-Bridge
* (`[authentication] enable = true`).
*
* @return void
*/
public static function showPromptIfNeeded()
{
if (Configuration::getConfig('authentication', 'enable') === true) {
if (!Authentication::verifyPrompt()) {
header('WWW-Authenticate: Basic realm="RSS-Bridge"', true, 401);
die('Please authenticate in order to access this instance !');
}
}
}
if(Configuration::getConfig('authentication', 'enable') === true) {
if(!Authentication::verifyPrompt()) {
header('WWW-Authenticate: Basic realm="RSS-Bridge"', true, 401);
die('Please authenticate in order to access this instance !');
}
}
}
/**
* Verifies if an authentication request was received and compares the
* provided username and password to the configuration of RSS-Bridge
* (`[authentication] username` and `[authentication] password`).
*
* @return bool True if authentication succeeded.
*/
public static function verifyPrompt() {
if(isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])) {
if(Configuration::getConfig('authentication', 'username') === $_SERVER['PHP_AUTH_USER']
&& Configuration::getConfig('authentication', 'password') === $_SERVER['PHP_AUTH_PW']) {
return true;
} else {
error_log('[RSS-Bridge] Failed authentication attempt from ' . $_SERVER['REMOTE_ADDR']);
}
}
return false;
}
/**
* Verifies if an authentication request was received and compares the
* provided username and password to the configuration of RSS-Bridge
* (`[authentication] username` and `[authentication] password`).
*
* @return bool True if authentication succeeded.
*/
public static function verifyPrompt()
{
if (isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])) {
if (
Configuration::getConfig('authentication', 'username') === $_SERVER['PHP_AUTH_USER']
&& Configuration::getConfig('authentication', 'password') === $_SERVER['PHP_AUTH_PW']
) {
return true;
} else {
error_log('[RSS-Bridge] Failed authentication attempt from ' . $_SERVER['REMOTE_ADDR']);
}
}
return false;
}
}

View file

@ -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 {
abstract class BridgeAbstract implements BridgeInterface
{
/**
* Name of the bridge
*
* Use {@see BridgeAbstract::getName()} to read this parameter
*/
const NAME = 'Unnamed bridge';
/**
* 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 = '';
/**
* 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 = '';
/**
* 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';
/**
* 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 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;
/**
* 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 = [];
/**
* 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 = [];
/**
* 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 = [];
/**
* 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',
];
/**
* 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 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 = [];
/**
* 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 = '';
/**
* Holds the name of the queried context
*
* @var string
*/
protected $queriedContext = '';
/** {@inheritdoc} */
public function getItems()
{
return $this->items;
}
/** {@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;
}
}
}
/**
* 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';
}
// 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;
}
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';
$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;
}
}
}
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;
}
}
// 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 = [];
}
}
// 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']);
}
/**
* 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 (empty(static::PARAMETERS)) {
if (!empty($inputs)) {
returnClientError('Invalid parameters value(s)');
}
if(isset($inputs['context'])) { // Context hinting (optional)
$this->queriedContext = $inputs['context'];
unset($inputs['context']);
}
return;
}
if(empty(static::PARAMETERS)) {
$validator = new ParameterValidator();
if(!empty($inputs)) {
returnClientError('Invalid parameters value(s)');
}
if (!$validator->validateData($inputs, static::PARAMETERS)) {
$parameters = array_map(
function ($i) {
return $i['name'];
}, // Just display parameter names
$validator->getInvalidParameters()
);
return;
returnClientError(
'Invalid parameters value(s): '
. implode(', ', $parameters)
);
}
}
// Guess the context from input data
if (empty($this->queriedContext)) {
$this->queriedContext = $validator->getQueriedContext($inputs, static::PARAMETERS);
}
$validator = new ParameterValidator();
if (is_null($this->queriedContext)) {
returnClientError('Required parameter(s) missing');
} elseif ($this->queriedContext === false) {
returnClientError('Mixed context parameters');
}
if(!$validator->validateData($inputs, static::PARAMETERS)) {
$parameters = array_map(
function($i){ return $i['name']; }, // Just display parameter names
$validator->getInvalidParameters()
);
$this->setInputs($inputs, $this->queriedContext);
}
returnClientError(
'Invalid parameters value(s): '
. implode(', ', $parameters)
);
}
/**
* 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);
// Guess the context from input data
if(empty($this->queriedContext)) {
$this->queriedContext = $validator->getQueriedContext($inputs, static::PARAMETERS);
}
if ($configurationOption !== null) {
$this->configuration[$optionName] = $configurationOption;
continue;
}
if(is_null($this->queriedContext)) {
returnClientError('Required parameter(s) missing');
} elseif($this->queriedContext === false) {
returnClientError('Mixed context parameters');
}
if (isset($optionValue['required']) && $optionValue['required'] === true) {
returnServerError(
'Missing configuration option: '
. $optionName
);
} elseif (isset($optionValue['defaultValue'])) {
$this->configuration[$optionName] = $optionValue['defaultValue'];
}
}
}
$this->setInputs($inputs, $this->queriedContext);
/**
* 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];
}
/**
* 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) {
/** {@inheritdoc} */
public function getDescription()
{
return static::DESCRIPTION;
}
$configurationOption = Configuration::getConfig(get_class($this), $optionName);
/** {@inheritdoc} */
public function getMaintainer()
{
return static::MAINTAINER;
}
if($configurationOption !== null) {
$this->configuration[$optionName] = $configurationOption;
continue;
}
/** {@inheritdoc} */
public function getName()
{
return static::NAME;
}
if(isset($optionValue['required']) && $optionValue['required'] === true) {
returnServerError(
'Missing configuration option: '
. $optionName
);
} elseif(isset($optionValue['defaultValue'])) {
$this->configuration[$optionName] = $optionValue['defaultValue'];
}
/** {@inheritdoc} */
public function getIcon()
{
return static::URI . '/favicon.ico';
}
}
}
/** {@inheritdoc} */
public function getConfiguration()
{
return static::CONFIGURATION;
}
/**
* 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'];
}
/** {@inheritdoc} */
public function getParameters()
{
return static::PARAMETERS;
}
/**
* 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 getURI()
{
return static::URI;
}
/** {@inheritdoc} */
public function getDescription(){
return static::DESCRIPTION;
}
/** {@inheritdoc} */
public function getDonationURI()
{
return static::DONATION_URI;
}
/** {@inheritdoc} */
public function getMaintainer(){
return static::MAINTAINER;
}
/** {@inheritdoc} */
public function getCacheTimeout()
{
return static::CACHE_TIMEOUT;
}
/** {@inheritdoc} */
public function getName(){
return static::NAME;
}
/** {@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;
}
}
/** {@inheritdoc} */
public function getIcon(){
return static::URI . '/favicon.ico';
}
/**
* 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();
/** {@inheritdoc} */
public function getConfiguration(){
return static::CONFIGURATION;
}
$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();
}
/** {@inheritdoc} */
public function getParameters(){
return static::PARAMETERS;
}
/**
* 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();
/** {@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);
}
$cache = $cacheFac->create(Configuration::getConfig('cache', 'type'));
$cache->setScope(get_called_class());
$cache->setKey($key);
$cache->saveData($value);
}
}

View file

@ -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
*/
/**
@ -19,310 +20,326 @@
*
* @todo Return error if a caller creates an object of this class.
*/
final class BridgeCard {
/**
* Get the form header for a bridge card
*
* @param string $bridgeName The bridge name
* @param bool $isHttps If disabled, adds a warning to the form
* @return string The form header
*/
private static function getFormHeader($bridgeName, $isHttps = false, $parameterName = '') {
$form = <<<EOD
final class BridgeCard
{
/**
* Get the form header for a bridge card
*
* @param string $bridgeName The bridge name
* @param bool $isHttps If disabled, adds a warning to the form
* @return string The form header
*/
private static function getFormHeader($bridgeName, $isHttps = false, $parameterName = '')
{
$form = <<<EOD
<form method="GET" action="?">
<input type="hidden" name="action" value="display" />
<input type="hidden" name="bridge" value="{$bridgeName}" />
EOD;
if(!empty($parameterName)) {
$form .= <<<EOD
if (!empty($parameterName)) {
$form .= <<<EOD
<input type="hidden" name="context" value="{$parameterName}" />
EOD;
}
}
if(!$isHttps) {
$form .= '<div class="secure-warning">Warning :
if (!$isHttps) {
$form .= '<div class="secure-warning">Warning :
This bridge is not fetching its content through a secure connection</div>';
}
}
return $form;
}
return $form;
}
/**
* Get the form body for a bridge
*
* @param string $bridgeName The bridge name
* @param array $formats A list of supported formats
* @param bool $isActive Indicates if a bridge is enabled or not
* @param bool $isHttps Indicates if a bridge uses HTTPS or not
* @param string $parameterName Sets the bridge context for the current form
* @param array $parameters The bridge parameters
* @return string The form body
*/
private static function getForm($bridgeName,
$formats,
$isActive = false,
$isHttps = false,
$parameterName = '',
$parameters = array()) {
$form = self::getFormHeader($bridgeName, $isHttps, $parameterName);
/**
* Get the form body for a bridge
*
* @param string $bridgeName The bridge name
* @param array $formats A list of supported formats
* @param bool $isActive Indicates if a bridge is enabled or not
* @param bool $isHttps Indicates if a bridge uses HTTPS or not
* @param string $parameterName Sets the bridge context for the current form
* @param array $parameters The bridge parameters
* @return string The form body
*/
private static function getForm(
$bridgeName,
$formats,
$isActive = false,
$isHttps = false,
$parameterName = '',
$parameters = []
) {
$form = self::getFormHeader($bridgeName, $isHttps, $parameterName);
if(count($parameters) > 0) {
if (count($parameters) > 0) {
$form .= '<div class="parameters">';
$form .= '<div class="parameters">';
foreach ($parameters as $id => $inputEntry) {
if (!isset($inputEntry['exampleValue'])) {
$inputEntry['exampleValue'] = '';
}
foreach($parameters as $id => $inputEntry) {
if(!isset($inputEntry['exampleValue']))
$inputEntry['exampleValue'] = '';
if (!isset($inputEntry['defaultValue'])) {
$inputEntry['defaultValue'] = '';
}
if(!isset($inputEntry['defaultValue']))
$inputEntry['defaultValue'] = '';
$idArg = 'arg-'
. urlencode($bridgeName)
. '-'
. urlencode($parameterName)
. '-'
. urlencode($id);
$idArg = 'arg-'
. urlencode($bridgeName)
. '-'
. urlencode($parameterName)
. '-'
. urlencode($id);
$form .= '<label for="'
. $idArg
. '">'
. filter_var($inputEntry['name'], FILTER_SANITIZE_FULL_SPECIAL_CHARS)
. '</label>'
. PHP_EOL;
$form .= '<label for="'
. $idArg
. '">'
. filter_var($inputEntry['name'], FILTER_SANITIZE_FULL_SPECIAL_CHARS)
. '</label>'
. PHP_EOL;
if (!isset($inputEntry['type']) || $inputEntry['type'] === 'text') {
$form .= self::getTextInput($inputEntry, $idArg, $id);
} elseif ($inputEntry['type'] === 'number') {
$form .= self::getNumberInput($inputEntry, $idArg, $id);
} elseif ($inputEntry['type'] === 'list') {
$form .= self::getListInput($inputEntry, $idArg, $id);
} elseif ($inputEntry['type'] === 'checkbox') {
$form .= self::getCheckboxInput($inputEntry, $idArg, $id);
}
if(!isset($inputEntry['type']) || $inputEntry['type'] === 'text') {
$form .= self::getTextInput($inputEntry, $idArg, $id);
} elseif($inputEntry['type'] === 'number') {
$form .= self::getNumberInput($inputEntry, $idArg, $id);
} else if($inputEntry['type'] === 'list') {
$form .= self::getListInput($inputEntry, $idArg, $id);
} elseif($inputEntry['type'] === 'checkbox') {
$form .= self::getCheckboxInput($inputEntry, $idArg, $id);
}
if (isset($inputEntry['title'])) {
$title_filtered = filter_var($inputEntry['title'], FILTER_SANITIZE_FULL_SPECIAL_CHARS);
$form .= '<i class="info" title="' . $title_filtered . '">i</i>';
} else {
$form .= '<i class="no-info"></i>';
}
}
if(isset($inputEntry['title'])) {
$title_filtered = filter_var($inputEntry['title'], FILTER_SANITIZE_FULL_SPECIAL_CHARS);
$form .= '<i class="info" title="' . $title_filtered . '">i</i>';
} else {
$form .= '<i class="no-info"></i>';
}
}
$form .= '</div>';
}
$form .= '</div>';
if ($isActive) {
$form .= '<button type="submit" name="format" formtarget="_blank" value="Html">Generate feed</button>';
} else {
$form .= '<span style="font-weight: bold;">Inactive</span>';
}
}
return $form . '</form>' . PHP_EOL;
}
if($isActive) {
$form .= '<button type="submit" name="format" formtarget="_blank" value="Html">Generate feed</button>';
} else {
$form .= '<span style="font-weight: bold;">Inactive</span>';
}
/**
* Get input field attributes
*
* @param array $entry The current entry
* @return string The input field attributes
*/
private static function getInputAttributes($entry)
{
$retVal = '';
return $form . '</form>' . PHP_EOL;
}
if (isset($entry['required']) && $entry['required'] === true) {
$retVal .= ' required';
}
/**
* Get input field attributes
*
* @param array $entry The current entry
* @return string The input field attributes
*/
private static function getInputAttributes($entry) {
$retVal = '';
if (isset($entry['pattern'])) {
$retVal .= ' pattern="' . $entry['pattern'] . '"';
}
if(isset($entry['required']) && $entry['required'] === true)
$retVal .= ' required';
return $retVal;
}
if(isset($entry['pattern']))
$retVal .= ' pattern="' . $entry['pattern'] . '"';
/**
* Get text input
*
* @param array $entry The current entry
* @param string $id The field ID
* @param string $name The field name
* @return string The text input field
*/
private static function getTextInput($entry, $id, $name)
{
return '<input '
. self::getInputAttributes($entry)
. ' id="'
. $id
. '" type="text" value="'
. filter_var($entry['defaultValue'], FILTER_SANITIZE_FULL_SPECIAL_CHARS)
. '" placeholder="'
. filter_var($entry['exampleValue'], FILTER_SANITIZE_FULL_SPECIAL_CHARS)
. '" name="'
. $name
. '" />'
. PHP_EOL;
}
return $retVal;
}
/**
* Get number input
*
* @param array $entry The current entry
* @param string $id The field ID
* @param string $name The field name
* @return string The number input field
*/
private static function getNumberInput($entry, $id, $name)
{
return '<input '
. self::getInputAttributes($entry)
. ' id="'
. $id
. '" type="number" value="'
. filter_var($entry['defaultValue'], FILTER_SANITIZE_NUMBER_INT)
. '" placeholder="'
. filter_var($entry['exampleValue'], FILTER_SANITIZE_NUMBER_INT)
. '" name="'
. $name
. '" />'
. PHP_EOL;
}
/**
* Get text input
*
* @param array $entry The current entry
* @param string $id The field ID
* @param string $name The field name
* @return string The text input field
*/
private static function getTextInput($entry, $id, $name) {
return '<input '
. self::getInputAttributes($entry)
. ' id="'
. $id
. '" type="text" value="'
. filter_var($entry['defaultValue'], FILTER_SANITIZE_FULL_SPECIAL_CHARS)
. '" placeholder="'
. filter_var($entry['exampleValue'], FILTER_SANITIZE_FULL_SPECIAL_CHARS)
. '" name="'
. $name
. '" />'
. PHP_EOL;
}
/**
* Get list input
*
* @param array $entry The current entry
* @param string $id The field ID
* @param string $name The field name
* @return string The list input field
*/
private static function getListInput($entry, $id, $name)
{
if (isset($entry['required']) && $entry['required'] === true) {
Debug::log('The "required" attribute is not supported for lists.');
unset($entry['required']);
}
/**
* Get number input
*
* @param array $entry The current entry
* @param string $id The field ID
* @param string $name The field name
* @return string The number input field
*/
private static function getNumberInput($entry, $id, $name) {
return '<input '
. self::getInputAttributes($entry)
. ' id="'
. $id
. '" type="number" value="'
. filter_var($entry['defaultValue'], FILTER_SANITIZE_NUMBER_INT)
. '" placeholder="'
. filter_var($entry['exampleValue'], FILTER_SANITIZE_NUMBER_INT)
. '" name="'
. $name
. '" />'
. PHP_EOL;
}
$list = '<select '
. self::getInputAttributes($entry)
. ' id="'
. $id
. '" name="'
. $name
. '" >';
/**
* Get list input
*
* @param array $entry The current entry
* @param string $id The field ID
* @param string $name The field name
* @return string The list input field
*/
private static function getListInput($entry, $id, $name) {
if(isset($entry['required']) && $entry['required'] === true) {
Debug::log('The "required" attribute is not supported for lists.');
unset($entry['required']);
}
foreach ($entry['values'] as $name => $value) {
if (is_array($value)) {
$list .= '<optgroup label="' . htmlentities($name) . '">';
foreach ($value as $subname => $subvalue) {
if (
$entry['defaultValue'] === $subname
|| $entry['defaultValue'] === $subvalue
) {
$list .= '<option value="'
. $subvalue
. '" selected>'
. $subname
. '</option>';
} else {
$list .= '<option value="'
. $subvalue
. '">'
. $subname
. '</option>';
}
}
$list .= '</optgroup>';
} else {
if (
$entry['defaultValue'] === $name
|| $entry['defaultValue'] === $value
) {
$list .= '<option value="'
. $value
. '" selected>'
. $name
. '</option>';
} else {
$list .= '<option value="'
. $value
. '">'
. $name
. '</option>';
}
}
}
$list = '<select '
. self::getInputAttributes($entry)
. ' id="'
. $id
. '" name="'
. $name
. '" >';
$list .= '</select>';
foreach($entry['values'] as $name => $value) {
if(is_array($value)) {
$list .= '<optgroup label="' . htmlentities($name) . '">';
foreach($value as $subname => $subvalue) {
if($entry['defaultValue'] === $subname
|| $entry['defaultValue'] === $subvalue) {
$list .= '<option value="'
. $subvalue
. '" selected>'
. $subname
. '</option>';
} else {
$list .= '<option value="'
. $subvalue
. '">'
. $subname
. '</option>';
}
}
$list .= '</optgroup>';
} else {
if($entry['defaultValue'] === $name
|| $entry['defaultValue'] === $value) {
$list .= '<option value="'
. $value
. '" selected>'
. $name
. '</option>';
} else {
$list .= '<option value="'
. $value
. '">'
. $name
. '</option>';
}
}
}
return $list;
}
$list .= '</select>';
/**
* Get checkbox input
*
* @param array $entry The current entry
* @param string $id The field ID
* @param string $name The field name
* @return string The checkbox input field
*/
private static function getCheckboxInput($entry, $id, $name)
{
if (isset($entry['required']) && $entry['required'] === true) {
Debug::log('The "required" attribute is not supported for checkboxes.');
unset($entry['required']);
}
return $list;
}
return '<input '
. self::getInputAttributes($entry)
. ' id="'
. $id
. '" type="checkbox" name="'
. $name
. '" '
. ($entry['defaultValue'] === 'checked' ? 'checked' : '')
. ' />'
. PHP_EOL;
}
/**
* Get checkbox input
*
* @param array $entry The current entry
* @param string $id The field ID
* @param string $name The field name
* @return string The checkbox input field
*/
private static function getCheckboxInput($entry, $id, $name) {
if(isset($entry['required']) && $entry['required'] === true) {
Debug::log('The "required" attribute is not supported for checkboxes.');
unset($entry['required']);
}
/**
* Gets a single bridge card
*
* @param string $bridgeName The bridge name
* @param array $formats A list of formats
* @param bool $isActive Indicates if the bridge is active or not
* @return string The bridge card
*/
public static function displayBridgeCard($bridgeName, $formats, $isActive = true)
{
$bridgeFac = new \BridgeFactory();
return '<input '
. self::getInputAttributes($entry)
. ' id="'
. $id
. '" type="checkbox" name="'
. $name
. '" '
. ($entry['defaultValue'] === 'checked' ? 'checked' : '')
. ' />'
. PHP_EOL;
}
$bridge = $bridgeFac->create($bridgeName);
/**
* Gets a single bridge card
*
* @param string $bridgeName The bridge name
* @param array $formats A list of formats
* @param bool $isActive Indicates if the bridge is active or not
* @return string The bridge card
*/
public static function displayBridgeCard($bridgeName, $formats, $isActive = true){
if ($bridge == false) {
return '';
}
$bridgeFac = new \BridgeFactory();
$isHttps = strpos($bridge->getURI(), 'https') === 0;
$bridge = $bridgeFac->create($bridgeName);
$uri = $bridge->getURI();
$name = $bridge->getName();
$icon = $bridge->getIcon();
$description = $bridge->getDescription();
$parameters = $bridge->getParameters();
$donationUri = $bridge->getDonationURI();
$maintainer = $bridge->getMaintainer();
if($bridge == false)
return '';
$donationsAllowed = Configuration::getConfig('admin', 'donations');
$isHttps = strpos($bridge->getURI(), 'https') === 0;
if (defined('PROXY_URL') && PROXY_BYBRIDGE) {
$parameters['global']['_noproxy'] = [
'name' => 'Disable proxy (' . ((defined('PROXY_NAME') && PROXY_NAME) ? PROXY_NAME : PROXY_URL) . ')',
'type' => 'checkbox'
];
}
$uri = $bridge->getURI();
$name = $bridge->getName();
$icon = $bridge->getIcon();
$description = $bridge->getDescription();
$parameters = $bridge->getParameters();
$donationUri = $bridge->getDonationURI();
$maintainer = $bridge->getMaintainer();
if (CUSTOM_CACHE_TIMEOUT) {
$parameters['global']['_cache_timeout'] = [
'name' => 'Cache timeout in seconds',
'type' => 'number',
'defaultValue' => $bridge->getCacheTimeout()
];
}
$donationsAllowed = Configuration::getConfig('admin', 'donations');
if(defined('PROXY_URL') && PROXY_BYBRIDGE) {
$parameters['global']['_noproxy'] = array(
'name' => 'Disable proxy (' . ((defined('PROXY_NAME') && PROXY_NAME) ? PROXY_NAME : PROXY_URL) . ')',
'type' => 'checkbox'
);
}
if(CUSTOM_CACHE_TIMEOUT) {
$parameters['global']['_cache_timeout'] = array(
'name' => 'Cache timeout in seconds',
'type' => 'number',
'defaultValue' => $bridge->getCacheTimeout()
);
}
$card = <<<CARD
$card = <<<CARD
<section id="bridge-{$bridgeName}" data-ref="{$name}">
<h2><a href="{$uri}">{$name}</a></h2>
<p class="description">{$description}</p>
@ -330,38 +347,39 @@ This bridge is not fetching its content through a secure connection</div>';
<label class="showmore" for="showmore-{$bridgeName}">Show more</label>
CARD;
// If we don't have any parameter for the bridge, we print a generic form to load it.
if (count($parameters) === 0) {
$card .= self::getForm($bridgeName, $formats, $isActive, $isHttps);
// If we don't have any parameter for the bridge, we print a generic form to load it.
if (count($parameters) === 0) {
$card .= self::getForm($bridgeName, $formats, $isActive, $isHttps);
// Display form with cache timeout and/or noproxy options (if enabled) when bridge has no parameters
} else if (count($parameters) === 1 && array_key_exists('global', $parameters)) {
$card .= self::getForm($bridgeName, $formats, $isActive, $isHttps, '', $parameters['global']);
} else {
// Display form with cache timeout and/or noproxy options (if enabled) when bridge has no parameters
} elseif (count($parameters) === 1 && array_key_exists('global', $parameters)) {
$card .= self::getForm($bridgeName, $formats, $isActive, $isHttps, '', $parameters['global']);
} else {
foreach ($parameters as $parameterName => $parameter) {
if (!is_numeric($parameterName) && $parameterName === 'global') {
continue;
}
foreach($parameters as $parameterName => $parameter) {
if(!is_numeric($parameterName) && $parameterName === 'global')
continue;
if (array_key_exists('global', $parameters)) {
$parameter = array_merge($parameter, $parameters['global']);
}
if(array_key_exists('global', $parameters))
$parameter = array_merge($parameter, $parameters['global']);
if (!is_numeric($parameterName)) {
$card .= '<h5>' . $parameterName . '</h5>' . PHP_EOL;
}
if(!is_numeric($parameterName))
$card .= '<h5>' . $parameterName . '</h5>' . PHP_EOL;
$card .= self::getForm($bridgeName, $formats, $isActive, $isHttps, $parameterName, $parameter);
}
}
$card .= self::getForm($bridgeName, $formats, $isActive, $isHttps, $parameterName, $parameter);
}
$card .= '<label class="showless" for="showmore-' . $bridgeName . '">Show less</label>';
if ($donationUri !== '' && $donationsAllowed) {
$card .= '<p class="maintainer">' . $maintainer . ' ~ <a href="' . $donationUri . '">Donate</a></p>';
} else {
$card .= '<p class="maintainer">' . $maintainer . '</p>';
}
$card .= '</section>';
}
$card .= '<label class="showless" for="showmore-' . $bridgeName . '">Show less</label>';
if($donationUri !== '' && $donationsAllowed) {
$card .= '<p class="maintainer">' . $maintainer . ' ~ <a href="' . $donationUri . '">Donate</a></p>';
} else {
$card .= '<p class="maintainer">' . $maintainer . '</p>';
}
$card .= '</section>';
return $card;
}
return $card;
}
}

View file

@ -1,87 +1,87 @@
<?php
final class BridgeFactory {
final class BridgeFactory
{
private $folder;
private $bridgeNames = [];
private $whitelist = [];
private $folder;
private $bridgeNames = [];
private $whitelist = [];
public function __construct(string $folder = PATH_LIB_BRIDGES)
{
$this->folder = $folder;
public function __construct(string $folder = PATH_LIB_BRIDGES)
{
$this->folder = $folder;
// create names
foreach (scandir($this->folder) as $file) {
if (preg_match('/^([^.]+)Bridge\.php$/U', $file, $m)) {
$this->bridgeNames[] = $m[1];
}
}
// create names
foreach(scandir($this->folder) as $file) {
if(preg_match('/^([^.]+)Bridge\.php$/U', $file, $m)) {
$this->bridgeNames[] = $m[1];
}
}
// create whitelist
if (file_exists(WHITELIST)) {
$contents = trim(file_get_contents(WHITELIST));
} elseif (file_exists(WHITELIST_DEFAULT)) {
$contents = trim(file_get_contents(WHITELIST_DEFAULT));
} else {
$contents = '';
}
if ($contents === '*') { // Whitelist all bridges
$this->whitelist = $this->getBridgeNames();
} else {
foreach (explode("\n", $contents) as $bridgeName) {
$this->whitelist[] = $this->sanitizeBridgeName($bridgeName);
}
}
}
// create whitelist
if (file_exists(WHITELIST)) {
$contents = trim(file_get_contents(WHITELIST));
} elseif (file_exists(WHITELIST_DEFAULT)) {
$contents = trim(file_get_contents(WHITELIST_DEFAULT));
} else {
$contents = '';
}
if ($contents === '*') { // Whitelist all bridges
$this->whitelist = $this->getBridgeNames();
} else {
foreach (explode("\n", $contents) as $bridgeName) {
$this->whitelist[] = $this->sanitizeBridgeName($bridgeName);
}
}
}
public function create(string $name): BridgeInterface
{
if (preg_match('/^[A-Z][a-zA-Z0-9-]*$/', $name)) {
$className = sprintf('%sBridge', $this->sanitizeBridgeName($name));
return new $className();
}
throw new \InvalidArgumentException('Bridge name invalid!');
}
public function create(string $name): BridgeInterface
{
if(preg_match('/^[A-Z][a-zA-Z0-9-]*$/', $name)) {
$className = sprintf('%sBridge', $this->sanitizeBridgeName($name));
return new $className();
}
throw new \InvalidArgumentException('Bridge name invalid!');
}
public function getBridgeNames(): array
{
return $this->bridgeNames;
}
public function getBridgeNames(): array
{
return $this->bridgeNames;
}
public function isWhitelisted($name): bool
{
return in_array($this->sanitizeBridgeName($name), $this->whitelist);
}
public function isWhitelisted($name): bool
{
return in_array($this->sanitizeBridgeName($name), $this->whitelist);
}
private function sanitizeBridgeName($name)
{
if (!is_string($name)) {
return null;
}
private function sanitizeBridgeName($name) {
// Trim trailing '.php' if exists
if (preg_match('/(.+)(?:\.php)/', $name, $matches)) {
$name = $matches[1];
}
if(!is_string($name)) {
return null;
}
// Trim trailing 'Bridge' if exists
if (preg_match('/(.+)(?:Bridge)/i', $name, $matches)) {
$name = $matches[1];
}
// Trim trailing '.php' if exists
if (preg_match('/(.+)(?:\.php)/', $name, $matches)) {
$name = $matches[1];
}
// Improve performance for correctly written bridge names
if (in_array($name, $this->getBridgeNames())) {
$index = array_search($name, $this->getBridgeNames());
return $this->getBridgeNames()[$index];
}
// Trim trailing 'Bridge' if exists
if (preg_match('/(.+)(?:Bridge)/i', $name, $matches)) {
$name = $matches[1];
}
// The name is valid if a corresponding bridge file is found on disk
if (in_array(strtolower($name), array_map('strtolower', $this->getBridgeNames()))) {
$index = array_search(strtolower($name), array_map('strtolower', $this->getBridgeNames()));
return $this->getBridgeNames()[$index];
}
// Improve performance for correctly written bridge names
if (in_array($name, $this->getBridgeNames())) {
$index = array_search($name, $this->getBridgeNames());
return $this->getBridgeNames()[$index];
}
// The name is valid if a corresponding bridge file is found on disk
if (in_array(strtolower($name), array_map('strtolower', $this->getBridgeNames()))) {
$index = array_search(strtolower($name), array_map('strtolower', $this->getBridgeNames()));
return $this->getBridgeNames()[$index];
}
Debug::log('Invalid bridge name specified: "' . $name . '"!');
return null;
}
Debug::log('Invalid bridge name specified: "' . $name . '"!');
return null;
}
}

View file

@ -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
*/
/**
@ -52,93 +53,94 @@
* * **Cache timeout**
* The default cache timeout for the bridge.
*/
interface BridgeInterface {
/**
* Collects data from the site
*/
public function collectData();
interface BridgeInterface
{
/**
* Collects data from the site
*/
public function collectData();
/**
* Get the user's supplied configuration for the bridge
*/
public function getConfiguration();
/**
* Get the user's supplied configuration for the bridge
*/
public function getConfiguration();
/**
* 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);
/**
* 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);
/**
* Returns the description
*
* @return string Description
*/
public function getDescription();
/**
* Returns the description
*
* @return string Description
*/
public function getDescription();
/**
* Returns an array of collected items
*
* @return array Associative array of items
*/
public function getItems();
/**
* Returns an array of collected items
*
* @return array Associative array of items
*/
public function getItems();
/**
* Returns the bridge maintainer
*
* @return string Bridge maintainer
*/
public function getMaintainer();
/**
* Returns the bridge maintainer
*
* @return string Bridge maintainer
*/
public function getMaintainer();
/**
* Returns the bridge name
*
* @return string Bridge name
*/
public function getName();
/**
* Returns the bridge name
*
* @return string Bridge name
*/
public function getName();
/**
* Returns the bridge icon
*
* @return string Bridge icon
*/
public function getIcon();
/**
* Returns the bridge icon
*
* @return string Bridge icon
*/
public function getIcon();
/**
* Returns the bridge parameters
*
* @return array Bridge parameters
*/
public function getParameters();
/**
* Returns the bridge parameters
*
* @return array Bridge parameters
*/
public function getParameters();
/**
* Returns the bridge URI
*
* @return string Bridge URI
*/
public function getURI();
/**
* Returns the bridge URI
*
* @return string Bridge URI
*/
public function getURI();
/**
* Returns the bridge Donation URI
*
* @return string Bridge Donation URI
*/
public function getDonationURI();
/**
* Returns the bridge Donation URI
*
* @return string Bridge Donation URI
*/
public function getDonationURI();
/**
* Returns the cache timeout
*
* @return int Cache timeout
*/
public function getCacheTimeout();
/**
* Returns the cache timeout
*
* @return int Cache timeout
*/
public function getCacheTimeout();
/**
* Returns parameters from given URL or null if URL is not applicable
*
* @param string $url URL to extract parameters from
* @return array|null List of bridge parameters or null if detection failed.
*/
public function detectParameters($url);
/**
* Returns parameters from given URL or null if URL is not applicable
*
* @param string $url URL to extract parameters from
* @return array|null List of bridge parameters or null if detection failed.
*/
public function detectParameters($url);
}

View file

@ -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
*/
/**
@ -19,14 +20,16 @@
*
* @todo Return error if a caller creates an object of this class.
*/
final class BridgeList {
/**
* Get the document head
*
* @return string The document head
*/
private static function getHead() {
return <<<EOD
final class BridgeList
{
/**
* Get the document head
*
* @return string The document head
*/
private static function getHead()
{
return <<<EOD
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
@ -45,91 +48,87 @@ final class BridgeList {
</noscript>
</head>
EOD;
}
}
/**
* Get the document body for all bridge cards
*
* @param bool $showInactive Inactive bridges are visible on the home page if
* enabled.
* @param int $totalBridges (ref) Returns the total number of bridges.
* @param int $totalActiveBridges (ref) Returns the number of active bridges.
* @return string The document body for all bridge cards.
*/
private static function getBridges($showInactive, &$totalBridges, &$totalActiveBridges) {
/**
* Get the document body for all bridge cards
*
* @param bool $showInactive Inactive bridges are visible on the home page if
* enabled.
* @param int $totalBridges (ref) Returns the total number of bridges.
* @param int $totalActiveBridges (ref) Returns the number of active bridges.
* @return string The document body for all bridge cards.
*/
private static function getBridges($showInactive, &$totalBridges, &$totalActiveBridges)
{
$body = '';
$totalActiveBridges = 0;
$inactiveBridges = '';
$body = '';
$totalActiveBridges = 0;
$inactiveBridges = '';
$bridgeFac = new \BridgeFactory();
$bridgeList = $bridgeFac->getBridgeNames();
$bridgeFac = new \BridgeFactory();
$bridgeList = $bridgeFac->getBridgeNames();
$formatFac = new FormatFactory();
$formats = $formatFac->getFormatNames();
$formatFac = new FormatFactory();
$formats = $formatFac->getFormatNames();
$totalBridges = count($bridgeList);
$totalBridges = count($bridgeList);
foreach ($bridgeList as $bridgeName) {
if ($bridgeFac->isWhitelisted($bridgeName)) {
$body .= BridgeCard::displayBridgeCard($bridgeName, $formats);
$totalActiveBridges++;
} elseif ($showInactive) {
// inactive bridges
$inactiveBridges .= BridgeCard::displayBridgeCard($bridgeName, $formats, false) . PHP_EOL;
}
}
foreach($bridgeList as $bridgeName) {
$body .= $inactiveBridges;
if($bridgeFac->isWhitelisted($bridgeName)) {
return $body;
}
$body .= BridgeCard::displayBridgeCard($bridgeName, $formats);
$totalActiveBridges++;
/**
* Get the document header
*
* @return string The document header
*/
private static function getHeader()
{
$warning = '';
} elseif($showInactive) {
// inactive bridges
$inactiveBridges .= BridgeCard::displayBridgeCard($bridgeName, $formats, false) . PHP_EOL;
}
}
$body .= $inactiveBridges;
return $body;
}
/**
* Get the document header
*
* @return string The document header
*/
private static function getHeader() {
$warning = '';
if(Debug::isEnabled()) {
if(!Debug::isSecure()) {
$warning .= <<<EOD
if (Debug::isEnabled()) {
if (!Debug::isSecure()) {
$warning .= <<<EOD
<section class="critical-warning">Warning : Debug mode is active from any location,
make sure only you can access RSS-Bridge.</section>
EOD;
} else {
$warning .= <<<EOD
} else {
$warning .= <<<EOD
<section class="warning">Warning : Debug mode is active from your IP address,
your requests will bypass the cache.</section>
EOD;
}
}
}
}
return <<<EOD
return <<<EOD
<header>
<div class="logo"></div>
{$warning}
</header>
EOD;
}
}
/**
* Get the searchbar
*
* @return string The searchbar
*/
private static function getSearchbar() {
$query = filter_input(INPUT_GET, 'q', FILTER_SANITIZE_SPECIAL_CHARS);
/**
* Get the searchbar
*
* @return string The searchbar
*/
private static function getSearchbar()
{
$query = filter_input(INPUT_GET, 'q', FILTER_SANITIZE_SPECIAL_CHARS);
return <<<EOD
return <<<EOD
<section class="searchbar">
<h3>Search</h3>
<input type="text" name="searchfield"
@ -137,46 +136,45 @@ EOD;
onchange="search()" onkeyup="search()" value="{$query}">
</section>
EOD;
}
}
/**
* Get the document footer
*
* @param int $totalBridges The total number of bridges, shown in the footer
* @param int $totalActiveBridges The total number of active bridges, shown
* in the footer.
* @param bool $showInactive Sets the 'Show active'/'Show inactive' text in
* the footer.
* @return string The document footer
*/
private static function getFooter($totalBridges, $totalActiveBridges, $showInactive) {
$version = Configuration::getVersion();
/**
* Get the document footer
*
* @param int $totalBridges The total number of bridges, shown in the footer
* @param int $totalActiveBridges The total number of active bridges, shown
* in the footer.
* @param bool $showInactive Sets the 'Show active'/'Show inactive' text in
* the footer.
* @return string The document footer
*/
private static function getFooter($totalBridges, $totalActiveBridges, $showInactive)
{
$version = Configuration::getVersion();
$email = Configuration::getConfig('admin', 'email');
$admininfo = '';
if (!empty($email)) {
$admininfo = <<<EOD
$email = Configuration::getConfig('admin', 'email');
$admininfo = '';
if (!empty($email)) {
$admininfo = <<<EOD
<br />
<span>
You may email the administrator of this RSS-Bridge instance
at <a href="mailto:{$email}">{$email}</a>
</span>
EOD;
}
}
$inactive = '';
$inactive = '';
if($totalActiveBridges !== $totalBridges) {
if ($totalActiveBridges !== $totalBridges) {
if (!$showInactive) {
$inactive = '<a href="?show_inactive=1"><button class="small">Show inactive bridges</button></a><br>';
} else {
$inactive = '<a href="?show_inactive=0"><button class="small">Hide inactive bridges</button></a><br>';
}
}
if(!$showInactive) {
$inactive = '<a href="?show_inactive=1"><button class="small">Show inactive bridges</button></a><br>';
} else {
$inactive = '<a href="?show_inactive=0"><button class="small">Hide inactive bridges</button></a><br>';
}
}
return <<<EOD
return <<<EOD
<section class="footer">
<a href="https://github.com/rss-bridge/rss-bridge">RSS-Bridge ~ Public Domain</a><br>
<p class="version">{$version}</p>
@ -185,28 +183,27 @@ EOD;
{$admininfo}
</section>
EOD;
}
}
/**
* Create the entire home page
*
* @param bool $showInactive Inactive bridges are displayed on the home page,
* if enabled.
* @return string The home page
*/
public static function create($showInactive = true) {
/**
* Create the entire home page
*
* @param bool $showInactive Inactive bridges are displayed on the home page,
* if enabled.
* @return string The home page
*/
public static function create($showInactive = true)
{
$totalBridges = 0;
$totalActiveBridges = 0;
$totalBridges = 0;
$totalActiveBridges = 0;
return '<!DOCTYPE html><html lang="en">'
. BridgeList::getHead()
. '<body onload="search()">'
. BridgeList::getHeader()
. BridgeList::getSearchbar()
. BridgeList::getBridges($showInactive, $totalBridges, $totalActiveBridges)
. BridgeList::getFooter($totalBridges, $totalActiveBridges, $showInactive)
. '</body></html>';
}
return '<!DOCTYPE html><html lang="en">'
. BridgeList::getHead()
. '<body onload="search()">'
. BridgeList::getHeader()
. BridgeList::getSearchbar()
. BridgeList::getBridges($showInactive, $totalBridges, $totalActiveBridges)
. BridgeList::getFooter($totalBridges, $totalActiveBridges, $showInactive)
. '</body></html>';
}
}

View file

@ -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,62 +7,62 @@
* 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
*/
class CacheFactory
{
private $folder;
private $cacheNames;
private $folder;
private $cacheNames;
public function __construct(string $folder = PATH_LIB_CACHES)
{
$this->folder = $folder;
// create cache names
foreach(scandir($this->folder) as $file) {
if(preg_match('/^([^.]+)Cache\.php$/U', $file, $m)) {
$this->cacheNames[] = $m[1];
}
}
}
public function __construct(string $folder = PATH_LIB_CACHES)
{
$this->folder = $folder;
// create cache names
foreach (scandir($this->folder) as $file) {
if (preg_match('/^([^.]+)Cache\.php$/U', $file, $m)) {
$this->cacheNames[] = $m[1];
}
}
}
/**
* @param string $name The name of the cache e.g. "File", "Memcached" or "SQLite"
*/
public function create(string $name): CacheInterface
{
$name = $this->sanitizeCacheName($name) . 'Cache';
/**
* @param string $name The name of the cache e.g. "File", "Memcached" or "SQLite"
*/
public function create(string $name): CacheInterface
{
$name = $this->sanitizeCacheName($name) . 'Cache';
if(! preg_match('/^[A-Z][a-zA-Z0-9-]*$/', $name)) {
throw new \InvalidArgumentException('Cache name invalid!');
}
if (! preg_match('/^[A-Z][a-zA-Z0-9-]*$/', $name)) {
throw new \InvalidArgumentException('Cache name invalid!');
}
$filePath = $this->folder . $name . '.php';
if(!file_exists($filePath)) {
throw new \Exception('Invalid cache');
}
$className = '\\' . $name;
return new $className();
}
$filePath = $this->folder . $name . '.php';
if (!file_exists($filePath)) {
throw new \Exception('Invalid cache');
}
$className = '\\' . $name;
return new $className();
}
protected function sanitizeCacheName(string $name)
{
// Trim trailing '.php' if exists
if (preg_match('/(.+)(?:\.php)/', $name, $matches)) {
$name = $matches[1];
}
protected function sanitizeCacheName(string $name)
{
// Trim trailing '.php' if exists
if (preg_match('/(.+)(?:\.php)/', $name, $matches)) {
$name = $matches[1];
}
// Trim trailing 'Cache' if exists
if (preg_match('/(.+)(?:Cache)$/i', $name, $matches)) {
$name = $matches[1];
}
// Trim trailing 'Cache' if exists
if (preg_match('/(.+)(?:Cache)$/i', $name, $matches)) {
$name = $matches[1];
}
if(in_array(strtolower($name), array_map('strtolower', $this->cacheNames))) {
$index = array_search(strtolower($name), array_map('strtolower', $this->cacheNames));
return $this->cacheNames[$index];
}
return null;
}
if (in_array(strtolower($name), array_map('strtolower', $this->cacheNames))) {
$index = array_search(strtolower($name), array_map('strtolower', $this->cacheNames));
return $this->cacheNames[$index];
}
return null;
}
}

View file

@ -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,61 +7,62 @@
* 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
*/
/**
* The cache interface
*/
interface CacheInterface {
/**
* Set scope of the current cache
*
* If $scope is an empty string, the cache is set to a global context.
*
* @param string $scope The scope the data is related to
*/
public function setScope($scope);
interface CacheInterface
{
/**
* Set scope of the current cache
*
* If $scope is an empty string, the cache is set to a global context.
*
* @param string $scope The scope the data is related to
*/
public function setScope($scope);
/**
* Set key to assign the current data
*
* Since $key can be anything, the cache implementation must ensure to
* assign the related data reliably; most commonly by serializing and
* hashing the key in an appropriate way.
*
* @param array $key The key the data is related to
*/
public function setKey($key);
/**
* Set key to assign the current data
*
* Since $key can be anything, the cache implementation must ensure to
* assign the related data reliably; most commonly by serializing and
* hashing the key in an appropriate way.
*
* @param array $key The key the data is related to
*/
public function setKey($key);
/**
* Loads data from cache
*
* @return mixed The cached data or null
*/
public function loadData();
/**
* Loads data from cache
*
* @return mixed The cached data or null
*/
public function loadData();
/**
* Stores data to the cache
*
* @param mixed $data The data to store
* @return self The cache object
*/
public function saveData($data);
/**
* Stores data to the cache
*
* @param mixed $data The data to store
* @return self The cache object
*/
public function saveData($data);
/**
* Returns the timestamp for the curent cache data
*
* @return int Timestamp or null
*/
public function getTime();
/**
* Returns the timestamp for the curent cache data
*
* @return int Timestamp or null
*/
public function getTime();
/**
* Removes any data that is older than the specified age from cache
*
* @param int $seconds The cache age in seconds
*/
public function purgeCache($seconds);
/**
* Removes any data that is older than the specified age from cache
*
* @param int $seconds The cache age in seconds
*/
public function purgeCache($seconds);
}

View file

@ -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
*/
/**
@ -16,294 +17,317 @@
*
* This class implements a configuration module for RSS-Bridge.
*/
final class Configuration {
final class Configuration
{
/**
* Holds the current release version of RSS-Bridge.
*
* Do not access this property directly!
* Use {@see Configuration::getVersion()} instead.
*
* @var string
*
* @todo Replace this property by a constant.
*/
public static $VERSION = 'dev.2022-06-14';
/**
* Holds the current release version of RSS-Bridge.
*
* Do not access this property directly!
* Use {@see Configuration::getVersion()} instead.
*
* @var string
*
* @todo Replace this property by a constant.
*/
public static $VERSION = 'dev.2022-06-14';
/**
* Holds the configuration data.
*
* Do not access this property directly!
* Use {@see Configuration::getConfig()} instead.
*
* @var array|null
*/
private static $config = null;
/**
* Holds the configuration data.
*
* Do not access this property directly!
* Use {@see Configuration::getConfig()} instead.
*
* @var array|null
*/
private static $config = null;
/**
* Throw an exception when trying to create a new instance of this class.
*
* @throws \LogicException if called.
*/
public function __construct()
{
throw new \LogicException('Can\'t create object of this class!');
}
/**
* Throw an exception when trying to create a new instance of this class.
*
* @throws \LogicException if called.
*/
public function __construct(){
throw new \LogicException('Can\'t create object of this class!');
}
/**
* Verifies the current installation of RSS-Bridge and PHP.
*
* Returns an error message and aborts execution if the installation does
* not satisfy the requirements of RSS-Bridge.
*
* **Requirements**
* - PHP 7.1.0 or higher
* - `openssl` extension
* - `libxml` extension
* - `mbstring` extension
* - `simplexml` extension
* - `curl` extension
* - `json` extension
* - The cache folder specified by {@see PATH_CACHE} requires write permission
* - The whitelist file specified by {@see WHITELIST} requires write permission
*
* @link http://php.net/supported-versions.php PHP Supported Versions
* @link http://php.net/manual/en/book.openssl.php OpenSSL
* @link http://php.net/manual/en/book.libxml.php libxml
* @link http://php.net/manual/en/book.mbstring.php Multibyte String (mbstring)
* @link http://php.net/manual/en/book.simplexml.php SimpleXML
* @link http://php.net/manual/en/book.curl.php Client URL Library (curl)
* @link http://php.net/manual/en/book.json.php JavaScript Object Notation (json)
*
* @return void
*/
public static function verifyInstallation()
{
// Check PHP version
if (version_compare(PHP_VERSION, '7.4.0') === -1) {
self::reportError('RSS-Bridge requires at least PHP version 7.4.0!');
}
// extensions check
if (!extension_loaded('openssl')) {
self::reportError('"openssl" extension not loaded. Please check "php.ini"');
}
/**
* Verifies the current installation of RSS-Bridge and PHP.
*
* Returns an error message and aborts execution if the installation does
* not satisfy the requirements of RSS-Bridge.
*
* **Requirements**
* - PHP 7.1.0 or higher
* - `openssl` extension
* - `libxml` extension
* - `mbstring` extension
* - `simplexml` extension
* - `curl` extension
* - `json` extension
* - The cache folder specified by {@see PATH_CACHE} requires write permission
* - The whitelist file specified by {@see WHITELIST} requires write permission
*
* @link http://php.net/supported-versions.php PHP Supported Versions
* @link http://php.net/manual/en/book.openssl.php OpenSSL
* @link http://php.net/manual/en/book.libxml.php libxml
* @link http://php.net/manual/en/book.mbstring.php Multibyte String (mbstring)
* @link http://php.net/manual/en/book.simplexml.php SimpleXML
* @link http://php.net/manual/en/book.curl.php Client URL Library (curl)
* @link http://php.net/manual/en/book.json.php JavaScript Object Notation (json)
*
* @return void
*/
public static function verifyInstallation() {
if (!extension_loaded('libxml')) {
self::reportError('"libxml" extension not loaded. Please check "php.ini"');
}
// Check PHP version
if(version_compare(PHP_VERSION, '7.4.0') === -1) {
self::reportError('RSS-Bridge requires at least PHP version 7.4.0!');
}
// extensions check
if(!extension_loaded('openssl'))
self::reportError('"openssl" extension not loaded. Please check "php.ini"');
if (!extension_loaded('mbstring')) {
self::reportError('"mbstring" extension not loaded. Please check "php.ini"');
}
if(!extension_loaded('libxml'))
self::reportError('"libxml" extension not loaded. Please check "php.ini"');
if (!extension_loaded('simplexml')) {
self::reportError('"simplexml" extension not loaded. Please check "php.ini"');
}
if(!extension_loaded('mbstring'))
self::reportError('"mbstring" extension not loaded. Please check "php.ini"');
// Allow RSS-Bridge to run without curl module in CLI mode without root certificates
if (!extension_loaded('curl') && !(php_sapi_name() === 'cli' && empty(ini_get('curl.cainfo')))) {
self::reportError('"curl" extension not loaded. Please check "php.ini"');
}
if(!extension_loaded('simplexml'))
self::reportError('"simplexml" extension not loaded. Please check "php.ini"');
if (!extension_loaded('json')) {
self::reportError('"json" extension not loaded. Please check "php.ini"');
}
}
// Allow RSS-Bridge to run without curl module in CLI mode without root certificates
if(!extension_loaded('curl') && !(php_sapi_name() === 'cli' && empty(ini_get('curl.cainfo'))))
self::reportError('"curl" extension not loaded. Please check "php.ini"');
/**
* Loads the configuration from disk and checks if the parameters are valid.
*
* Returns an error message and aborts execution if the configuration is invalid.
*
* The RSS-Bridge configuration is split into two files:
* - {@see FILE_CONFIG_DEFAULT} The default configuration file that ships
* with every release of RSS-Bridge (do not modify this file!).
* - {@see FILE_CONFIG} The local configuration file that can be modified
* by server administrators.
*
* RSS-Bridge will first load {@see FILE_CONFIG_DEFAULT} into memory and then
* replace parameters with the contents of {@see FILE_CONFIG}. That way new
* parameters are automatically initialized with default values and custom
* configurations can be reduced to the minimum set of parametes necessary
* (only the ones that changed).
*
* The configuration files must be placed in the root folder of RSS-Bridge
* (next to `index.php`).
*
* _Notice_: The configuration is stored in {@see Configuration::$config}.
*
* @return void
*/
public static function loadConfiguration()
{
if (!file_exists(FILE_CONFIG_DEFAULT)) {
self::reportError('The default configuration file is missing at ' . FILE_CONFIG_DEFAULT);
}
if(!extension_loaded('json'))
self::reportError('"json" extension not loaded. Please check "php.ini"');
Configuration::$config = parse_ini_file(FILE_CONFIG_DEFAULT, true, INI_SCANNER_TYPED);
if (!Configuration::$config) {
self::reportError('Error parsing ' . FILE_CONFIG_DEFAULT);
}
}
if (file_exists(FILE_CONFIG)) {
// Replace default configuration with custom settings
foreach (parse_ini_file(FILE_CONFIG, true, INI_SCANNER_TYPED) as $header => $section) {
foreach ($section as $key => $value) {
Configuration::$config[$header][$key] = $value;
}
}
}
/**
* Loads the configuration from disk and checks if the parameters are valid.
*
* Returns an error message and aborts execution if the configuration is invalid.
*
* The RSS-Bridge configuration is split into two files:
* - {@see FILE_CONFIG_DEFAULT} The default configuration file that ships
* with every release of RSS-Bridge (do not modify this file!).
* - {@see FILE_CONFIG} The local configuration file that can be modified
* by server administrators.
*
* RSS-Bridge will first load {@see FILE_CONFIG_DEFAULT} into memory and then
* replace parameters with the contents of {@see FILE_CONFIG}. That way new
* parameters are automatically initialized with default values and custom
* configurations can be reduced to the minimum set of parametes necessary
* (only the ones that changed).
*
* The configuration files must be placed in the root folder of RSS-Bridge
* (next to `index.php`).
*
* _Notice_: The configuration is stored in {@see Configuration::$config}.
*
* @return void
*/
public static function loadConfiguration() {
foreach (getenv() as $envkey => $value) {
// Replace all settings with their respective environment variable if available
$keyArray = explode('_', $envkey);
if ($keyArray[0] === 'RSSBRIDGE') {
$header = strtolower($keyArray[1]);
$key = strtolower($keyArray[2]);
if ($value === 'true' || $value === 'false') {
$value = filter_var($value, FILTER_VALIDATE_BOOLEAN);
}
Configuration::$config[$header][$key] = $value;
}
}
if(!file_exists(FILE_CONFIG_DEFAULT))
self::reportError('The default configuration file is missing at ' . FILE_CONFIG_DEFAULT);
if (
!is_string(self::getConfig('system', 'timezone'))
|| !in_array(self::getConfig('system', 'timezone'), timezone_identifiers_list(DateTimeZone::ALL_WITH_BC))
) {
self::reportConfigurationError('system', 'timezone');
}
Configuration::$config = parse_ini_file(FILE_CONFIG_DEFAULT, true, INI_SCANNER_TYPED);
if(!Configuration::$config)
self::reportError('Error parsing ' . FILE_CONFIG_DEFAULT);
date_default_timezone_set(self::getConfig('system', 'timezone'));
if(file_exists(FILE_CONFIG)) {
// Replace default configuration with custom settings
foreach(parse_ini_file(FILE_CONFIG, true, INI_SCANNER_TYPED) as $header => $section) {
foreach($section as $key => $value) {
Configuration::$config[$header][$key] = $value;
}
}
}
if (!is_string(self::getConfig('proxy', 'url'))) {
self::reportConfigurationError('proxy', 'url', 'Is not a valid string');
}
foreach (getenv() as $envkey => $value) {
// Replace all settings with their respective environment variable if available
$keyArray = explode('_', $envkey);
if($keyArray[0] === 'RSSBRIDGE') {
$header = strtolower($keyArray[1]);
$key = strtolower($keyArray[2]);
if($value === 'true' || $value === 'false') {
$value = filter_var($value, FILTER_VALIDATE_BOOLEAN);
}
Configuration::$config[$header][$key] = $value;
}
}
if (!empty(self::getConfig('proxy', 'url'))) {
/** URL of the proxy server */
define('PROXY_URL', self::getConfig('proxy', 'url'));
}
if(!is_string(self::getConfig('system', 'timezone'))
|| !in_array(self::getConfig('system', 'timezone'), timezone_identifiers_list(DateTimeZone::ALL_WITH_BC)))
self::reportConfigurationError('system', 'timezone');
if (!is_bool(self::getConfig('proxy', 'by_bridge'))) {
self::reportConfigurationError('proxy', 'by_bridge', 'Is not a valid Boolean');
}
date_default_timezone_set(self::getConfig('system', 'timezone'));
/** True if proxy usage can be enabled selectively for each bridge */
define('PROXY_BYBRIDGE', self::getConfig('proxy', 'by_bridge'));
if(!is_string(self::getConfig('proxy', 'url')))
self::reportConfigurationError('proxy', 'url', 'Is not a valid string');
if (!is_string(self::getConfig('proxy', 'name'))) {
self::reportConfigurationError('proxy', 'name', 'Is not a valid string');
}
if(!empty(self::getConfig('proxy', 'url'))) {
/** URL of the proxy server */
define('PROXY_URL', self::getConfig('proxy', 'url'));
}
/** Name of the proxy server */
define('PROXY_NAME', self::getConfig('proxy', 'name'));
if(!is_bool(self::getConfig('proxy', 'by_bridge')))
self::reportConfigurationError('proxy', 'by_bridge', 'Is not a valid Boolean');
if (!is_string(self::getConfig('cache', 'type'))) {
self::reportConfigurationError('cache', 'type', 'Is not a valid string');
}
/** True if proxy usage can be enabled selectively for each bridge */
define('PROXY_BYBRIDGE', self::getConfig('proxy', 'by_bridge'));
if (!is_bool(self::getConfig('cache', 'custom_timeout'))) {
self::reportConfigurationError('cache', 'custom_timeout', 'Is not a valid Boolean');
}
if(!is_string(self::getConfig('proxy', 'name')))
self::reportConfigurationError('proxy', 'name', 'Is not a valid string');
/** True if the cache timeout can be specified by the user */
define('CUSTOM_CACHE_TIMEOUT', self::getConfig('cache', 'custom_timeout'));
/** Name of the proxy server */
define('PROXY_NAME', self::getConfig('proxy', 'name'));
if (!is_bool(self::getConfig('authentication', 'enable'))) {
self::reportConfigurationError('authentication', 'enable', 'Is not a valid Boolean');
}
if(!is_string(self::getConfig('cache', 'type')))
self::reportConfigurationError('cache', 'type', 'Is not a valid string');
if (!is_string(self::getConfig('authentication', 'username'))) {
self::reportConfigurationError('authentication', 'username', 'Is not a valid string');
}
if(!is_bool(self::getConfig('cache', 'custom_timeout')))
self::reportConfigurationError('cache', 'custom_timeout', 'Is not a valid Boolean');
if (!is_string(self::getConfig('authentication', 'password'))) {
self::reportConfigurationError('authentication', 'password', 'Is not a valid string');
}
/** True if the cache timeout can be specified by the user */
define('CUSTOM_CACHE_TIMEOUT', self::getConfig('cache', 'custom_timeout'));
if (
!empty(self::getConfig('admin', 'email'))
&& !filter_var(self::getConfig('admin', 'email'), FILTER_VALIDATE_EMAIL)
) {
self::reportConfigurationError('admin', 'email', 'Is not a valid email address');
}
if(!is_bool(self::getConfig('authentication', 'enable')))
self::reportConfigurationError('authentication', 'enable', 'Is not a valid Boolean');
if (!is_bool(self::getConfig('admin', 'donations'))) {
self::reportConfigurationError('admin', 'donations', 'Is not a valid Boolean');
}
if(!is_string(self::getConfig('authentication', 'username')))
self::reportConfigurationError('authentication', 'username', 'Is not a valid string');
if (!is_string(self::getConfig('error', 'output'))) {
self::reportConfigurationError('error', 'output', 'Is not a valid String');
}
if(!is_string(self::getConfig('authentication', 'password')))
self::reportConfigurationError('authentication', 'password', 'Is not a valid string');
if (
!is_numeric(self::getConfig('error', 'report_limit'))
|| self::getConfig('error', 'report_limit') < 1
) {
self::reportConfigurationError('admin', 'report_limit', 'Value is invalid');
}
}
if(!empty(self::getConfig('admin', 'email'))
&& !filter_var(self::getConfig('admin', 'email'), FILTER_VALIDATE_EMAIL))
self::reportConfigurationError('admin', 'email', 'Is not a valid email address');
/**
* Returns the value of a parameter identified by section and key.
*
* @param string $section The section name.
* @param string $key The property name (key).
* @return mixed|null The parameter value.
*/
public static function getConfig($section, $key)
{
if (array_key_exists($section, self::$config) && array_key_exists($key, self::$config[$section])) {
return self::$config[$section][$key];
}
if(!is_bool(self::getConfig('admin', 'donations')))
self::reportConfigurationError('admin', 'donations', 'Is not a valid Boolean');
return null;
}
if(!is_string(self::getConfig('error', 'output')))
self::reportConfigurationError('error', 'output', 'Is not a valid String');
/**
* Returns the current version string of RSS-Bridge.
*
* This function returns the contents of {@see Configuration::$VERSION} for
* regular installations and the git branch name and commit id for instances
* running in a git environment.
*
* @return string The version string.
*/
public static function getVersion()
{
$headFile = PATH_ROOT . '.git/HEAD';
if(!is_numeric(self::getConfig('error', 'report_limit'))
|| self::getConfig('error', 'report_limit') < 1)
self::reportConfigurationError('admin', 'report_limit', 'Value is invalid');
// '@' is used to mute open_basedir warning
if (@is_readable($headFile)) {
$revisionHashFile = '.git/' . substr(file_get_contents($headFile), 5, -1);
$parts = explode('/', $revisionHashFile);
}
if (isset($parts[3])) {
$branchName = $parts[3];
if (file_exists($revisionHashFile)) {
return 'git.' . $branchName . '.' . substr(file_get_contents($revisionHashFile), 0, 7);
}
}
}
/**
* Returns the value of a parameter identified by section and key.
*
* @param string $section The section name.
* @param string $key The property name (key).
* @return mixed|null The parameter value.
*/
public static function getConfig($section, $key) {
if(array_key_exists($section, self::$config) && array_key_exists($key, self::$config[$section])) {
return self::$config[$section][$key];
}
return Configuration::$VERSION;
}
return null;
}
/**
* Reports an configuration error for the specified section and key to the
* user and ends execution
*
* @param string $section The section name
* @param string $key The configuration key
* @param string $message An optional message to the user
*
* @return void
*/
private static function reportConfigurationError($section, $key, $message = '')
{
$report = "Parameter [{$section}] => \"{$key}\" is invalid!" . PHP_EOL;
/**
* Returns the current version string of RSS-Bridge.
*
* This function returns the contents of {@see Configuration::$VERSION} for
* regular installations and the git branch name and commit id for instances
* running in a git environment.
*
* @return string The version string.
*/
public static function getVersion() {
if (file_exists(FILE_CONFIG)) {
$report .= 'Please check your configuration file at ' . FILE_CONFIG . PHP_EOL;
} elseif (!file_exists(FILE_CONFIG_DEFAULT)) {
$report .= 'The default configuration file is missing at ' . FILE_CONFIG_DEFAULT . PHP_EOL;
} else {
$report .= 'The default configuration file is broken.' . PHP_EOL
. 'Restore the original file from ' . REPOSITORY . PHP_EOL;
}
$headFile = PATH_ROOT . '.git/HEAD';
$report .= $message;
self::reportError($report);
}
// '@' is used to mute open_basedir warning
if(@is_readable($headFile)) {
$revisionHashFile = '.git/' . substr(file_get_contents($headFile), 5, -1);
$parts = explode('/', $revisionHashFile);
if(isset($parts[3])) {
$branchName = $parts[3];
if(file_exists($revisionHashFile)) {
return 'git.' . $branchName . '.' . substr(file_get_contents($revisionHashFile), 0, 7);
}
}
}
return Configuration::$VERSION;
}
/**
* Reports an configuration error for the specified section and key to the
* user and ends execution
*
* @param string $section The section name
* @param string $key The configuration key
* @param string $message An optional message to the user
*
* @return void
*/
private static function reportConfigurationError($section, $key, $message = '') {
$report = "Parameter [{$section}] => \"{$key}\" is invalid!" . PHP_EOL;
if(file_exists(FILE_CONFIG)) {
$report .= 'Please check your configuration file at ' . FILE_CONFIG . PHP_EOL;
} elseif(!file_exists(FILE_CONFIG_DEFAULT)) {
$report .= 'The default configuration file is missing at ' . FILE_CONFIG_DEFAULT . PHP_EOL;
} else {
$report .= 'The default configuration file is broken.' . PHP_EOL
. 'Restore the original file from ' . REPOSITORY . PHP_EOL;
}
$report .= $message;
self::reportError($report);
}
/**
* Reports an error message to the user and ends execution
*
* @param string $message The error message
*
* @return void
*/
private static function reportError($message) {
header('Content-Type: text/plain', true, 500);
die('Configuration error' . PHP_EOL . $message);
}
/**
* Reports an error message to the user and ends execution
*
* @param string $message The error message
*
* @return void
*/
private static function reportError($message)
{
header('Content-Type: text/plain', true, 500);
die('Configuration error' . PHP_EOL . $message);
}
}

View file

@ -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
*/
/**
@ -30,92 +31,93 @@
* Warning: In debug mode your server may display sensitive information! For
* security reasons it is recommended to whitelist only specific IP addresses.
*/
class Debug {
class Debug
{
/**
* Indicates if debug mode is enabled.
*
* Do not access this property directly!
* Use {@see Debug::isEnabled()} instead.
*
* @var bool
*/
private static $enabled = false;
/**
* Indicates if debug mode is enabled.
*
* Do not access this property directly!
* Use {@see Debug::isEnabled()} instead.
*
* @var bool
*/
private static $enabled = false;
/**
* Indicates if debug mode is secure.
*
* Do not access this property directly!
* Use {@see Debug::isSecure()} instead.
*
* @var bool
*/
private static $secure = false;
/**
* Indicates if debug mode is secure.
*
* Do not access this property directly!
* Use {@see Debug::isSecure()} instead.
*
* @var bool
*/
private static $secure = false;
/**
* Returns true if debug mode is enabled
*
* If debug mode is enabled, sets `display_errors = 1` and `error_reporting = E_ALL`
*
* @return bool True if enabled.
*/
public static function isEnabled()
{
static $firstCall = true; // Initialized on first call
/**
* Returns true if debug mode is enabled
*
* If debug mode is enabled, sets `display_errors = 1` and `error_reporting = E_ALL`
*
* @return bool True if enabled.
*/
public static function isEnabled() {
static $firstCall = true; // Initialized on first call
if ($firstCall && file_exists(PATH_ROOT . 'DEBUG')) {
$debug_whitelist = trim(file_get_contents(PATH_ROOT . 'DEBUG'));
if($firstCall && file_exists(PATH_ROOT . 'DEBUG')) {
self::$enabled = empty($debug_whitelist) || in_array(
$_SERVER['REMOTE_ADDR'],
explode("\n", str_replace("\r", '', $debug_whitelist))
);
$debug_whitelist = trim(file_get_contents(PATH_ROOT . 'DEBUG'));
if (self::$enabled) {
ini_set('display_errors', '1');
error_reporting(E_ALL);
self::$enabled = empty($debug_whitelist) || in_array($_SERVER['REMOTE_ADDR'],
explode("\n", str_replace("\r", '', $debug_whitelist)
)
);
self::$secure = !empty($debug_whitelist);
}
if(self::$enabled) {
ini_set('display_errors', '1');
error_reporting(E_ALL);
$firstCall = false; // Skip check on next call
}
self::$secure = !empty($debug_whitelist);
}
return self::$enabled;
}
$firstCall = false; // Skip check on next call
/**
* Returns true if debug mode is enabled only for specific IP addresses.
*
* Notice: The security flag is set by {@see Debug::isEnabled()}. If this
* function is called before {@see Debug::isEnabled()}, the default value is
* false!
*
* @return bool True if debug mode is secure
*/
public static function isSecure()
{
return self::$secure;
}
}
/**
* Adds a debug message to error_log if debug mode is enabled
*
* @param string $text The message to add to error_log
*/
public static function log($text)
{
if (!self::isEnabled()) {
return;
}
return self::$enabled;
}
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3);
$calling = end($backtrace);
$message = $calling['file'] . ':'
. $calling['line'] . ' class '
. (isset($calling['class']) ? $calling['class'] : '<no-class>') . '->'
. $calling['function'] . ' - '
. $text;
/**
* Returns true if debug mode is enabled only for specific IP addresses.
*
* Notice: The security flag is set by {@see Debug::isEnabled()}. If this
* function is called before {@see Debug::isEnabled()}, the default value is
* false!
*
* @return bool True if debug mode is secure
*/
public static function isSecure() {
return self::$secure;
}
/**
* Adds a debug message to error_log if debug mode is enabled
*
* @param string $text The message to add to error_log
*/
public static function log($text) {
if(!self::isEnabled()) {
return;
}
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3);
$calling = end($backtrace);
$message = $calling['file'] . ':'
. $calling['line'] . ' class '
. (isset($calling['class']) ? $calling['class'] : '<no-class>') . '->'
. $calling['function'] . ' - '
. $text;
error_log($message);
}
error_log($message);
}
}

View file

@ -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,18 +7,19 @@
* 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
*/
/**
* Builds a GitHub search query to find open bugs for the current bridge
*/
function buildGitHubSearchQuery($bridgeName){
return REPOSITORY
. 'issues?q='
. urlencode('is:issue is:open ' . $bridgeName);
function buildGitHubSearchQuery($bridgeName)
{
return REPOSITORY
. 'issues?q='
. urlencode('is:issue is:open ' . $bridgeName);
}
/**
@ -33,86 +35,87 @@ function buildGitHubSearchQuery($bridgeName){
*
* @todo This function belongs inside a class
*/
function buildGitHubIssueQuery($title, $body, $labels = null, $maintainer = null){
if(!isset($title) || !isset($body) || empty($title) || empty($body)) {
return null;
}
function buildGitHubIssueQuery($title, $body, $labels = null, $maintainer = null)
{
if (!isset($title) || !isset($body) || empty($title) || empty($body)) {
return null;
}
// Add title and body
$uri = REPOSITORY
. 'issues/new?title='
. urlencode($title)
. '&body='
. urlencode($body);
// Add title and body
$uri = REPOSITORY
. 'issues/new?title='
. urlencode($title)
. '&body='
. urlencode($body);
// Add labels
if(!is_null($labels) && is_array($labels) && count($labels) > 0) {
if(count($lables) === 1) {
$uri .= '&labels=' . urlencode($labels[0]);
} else {
foreach($labels as $label) {
$uri .= '&labels[]=' . urlencode($label);
}
}
} elseif(!is_null($labels) && is_string($labels)) {
$uri .= '&labels=' . urlencode($labels);
}
// Add labels
if (!is_null($labels) && is_array($labels) && count($labels) > 0) {
if (count($lables) === 1) {
$uri .= '&labels=' . urlencode($labels[0]);
} else {
foreach ($labels as $label) {
$uri .= '&labels[]=' . urlencode($label);
}
}
} elseif (!is_null($labels) && is_string($labels)) {
$uri .= '&labels=' . urlencode($labels);
}
// Add maintainer
if(!empty($maintainer)) {
$uri .= '&assignee=' . urlencode($maintainer);
}
// Add maintainer
if (!empty($maintainer)) {
$uri .= '&assignee=' . urlencode($maintainer);
}
return $uri;
return $uri;
}
function buildBridgeException(\Throwable $e, BridgeInterface $bridge): string
{
$title = $bridge->getName() . ' failed with error ' . $e->getCode();
$title = $bridge->getName() . ' failed with error ' . $e->getCode();
// Build a GitHub compatible message
$body = 'Error message: `'
. $e->getMessage()
. "`\nQuery string: `"
. (isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '')
. "`\nVersion: `"
. Configuration::getVersion()
. '`';
// Build a GitHub compatible message
$body = 'Error message: `'
. $e->getMessage()
. "`\nQuery string: `"
. (isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '')
. "`\nVersion: `"
. Configuration::getVersion()
. '`';
$body_html = nl2br($body);
$link = buildGitHubIssueQuery($title, $body, 'Bridge-Broken', $bridge->getMaintainer());
$searchQuery = buildGitHubSearchQuery($bridge::NAME);
$body_html = nl2br($body);
$link = buildGitHubIssueQuery($title, $body, 'Bridge-Broken', $bridge->getMaintainer());
$searchQuery = buildGitHubSearchQuery($bridge::NAME);
$header = buildHeader($e, $bridge);
$message = <<<EOD
$header = buildHeader($e, $bridge);
$message = <<<EOD
<strong>{$bridge->getName()}</strong> was unable to receive or process the
remote website's content!<br>
{$body_html}
EOD;
$section = buildSection($e, $bridge, $message, $link, $searchQuery);
$section = buildSection($e, $bridge, $message, $link, $searchQuery);
return $section;
return $section;
}
function buildTransformException(\Throwable $e, BridgeInterface $bridge): string
{
$title = $bridge->getName() . ' failed with error ' . $e->getCode();
$title = $bridge->getName() . ' failed with error ' . $e->getCode();
// Build a GitHub compatible message
$body = 'Error message: `'
. $e->getMessage()
. "`\nQuery string: `"
. (isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '')
. '`';
// Build a GitHub compatible message
$body = 'Error message: `'
. $e->getMessage()
. "`\nQuery string: `"
. (isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '')
. '`';
$link = buildGitHubIssueQuery($title, $body, 'Bridge-Broken', $bridge->getMaintainer());
$searchQuery = buildGitHubSearchQuery($bridge::NAME);
$header = buildHeader($e, $bridge);
$message = "RSS-Bridge was unable to transform the contents returned by
$link = buildGitHubIssueQuery($title, $body, 'Bridge-Broken', $bridge->getMaintainer());
$searchQuery = buildGitHubSearchQuery($bridge::NAME);
$header = buildHeader($e, $bridge);
$message = "RSS-Bridge was unable to transform the contents returned by
<strong>{$bridge->getName()}</strong>!";
$section = buildSection($e, $bridge, $message, $link, $searchQuery);
$section = buildSection($e, $bridge, $message, $link, $searchQuery);
return buildPage($title, $header, $section);
return buildPage($title, $header, $section);
}
/**
@ -124,8 +127,9 @@ function buildTransformException(\Throwable $e, BridgeInterface $bridge): string
*
* @todo This function belongs inside a class
*/
function buildHeader($e, $bridge){
return <<<EOD
function buildHeader($e, $bridge)
{
return <<<EOD
<header>
<h1>Error {$e->getCode()}</h1>
<h2>{$e->getMessage()}</h2>
@ -146,8 +150,9 @@ EOD;
*
* @todo This function belongs inside a class
*/
function buildSection($e, $bridge, $message, $link, $searchQuery){
return <<<EOD
function buildSection($e, $bridge, $message, $link, $searchQuery)
{
return <<<EOD
<section>
<p class="exception-message">{$message}</p>
<div class="advice">
@ -178,8 +183,9 @@ EOD;
*
* @todo This function belongs inside a class
*/
function buildPage($title, $header, $section){
return <<<EOD
function buildPage($title, $header, $section)
{
return <<<EOD
<!DOCTYPE html>
<html lang="en">
<head>

View file

@ -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,65 +7,67 @@
* 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
*/
/**
* Abstract class for factories.
*/
abstract class FactoryAbstract {
abstract class FactoryAbstract
{
/**
* Holds the working directory
*
* @var string
*/
private $workingDir = null;
/**
* Holds the working directory
*
* @var string
*/
private $workingDir = null;
/**
* Set the working directory.
*
* @param string $dir The working directory.
* @return void
*/
public function setWorkingDir($dir)
{
$this->workingDir = null;
/**
* Set the working directory.
*
* @param string $dir The working directory.
* @return void
*/
public function setWorkingDir($dir) {
$this->workingDir = null;
if (!is_string($dir)) {
throw new \InvalidArgumentException('Working directory must be a string!');
}
if(!is_string($dir)) {
throw new \InvalidArgumentException('Working directory must be a string!');
}
if (!file_exists($dir)) {
throw new \Exception('Working directory does not exist!');
}
if(!file_exists($dir)) {
throw new \Exception('Working directory does not exist!');
}
if (!is_dir($dir)) {
throw new \InvalidArgumentException($dir . ' is not a directory!');
}
if(!is_dir($dir)) {
throw new \InvalidArgumentException($dir . ' is not a directory!');
}
$this->workingDir = realpath($dir) . '/';
}
$this->workingDir = realpath($dir) . '/';
}
/**
* Get the working directory
*
* @return string The working directory.
*/
public function getWorkingDir()
{
if (is_null($this->workingDir)) {
throw new \LogicException('Working directory is not set!');
}
/**
* Get the working directory
*
* @return string The working directory.
*/
public function getWorkingDir() {
if(is_null($this->workingDir)) {
throw new \LogicException('Working directory is not set!');
}
return $this->workingDir;
}
return $this->workingDir;
}
/**
* Creates a new instance for the object specified by name.
*
* @param string $name The name of the object to create.
* @return object The object instance
*/
abstract public function create($name);
/**
* Creates a new instance for the object specified by name.
*
* @param string $name The name of the object to create.
* @return object The object instance
*/
abstract public function create($name);
}

View file

@ -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
*/
/**
@ -32,406 +33,452 @@
* @todo The parsing functions should all be private. This class is complicated
* enough without having to consider children overriding functions.
*/
abstract class FeedExpander extends BridgeAbstract {
abstract class FeedExpander extends BridgeAbstract
{
/** Indicates an RSS 1.0 feed */
const FEED_TYPE_RSS_1_0 = 'RSS_1_0';
/** Indicates an RSS 1.0 feed */
const FEED_TYPE_RSS_1_0 = 'RSS_1_0';
/** Indicates an RSS 2.0 feed */
const FEED_TYPE_RSS_2_0 = 'RSS_2_0';
/** Indicates an RSS 2.0 feed */
const FEED_TYPE_RSS_2_0 = 'RSS_2_0';
/** Indicates an Atom 1.0 feed */
const FEED_TYPE_ATOM_1_0 = 'ATOM_1_0';
/** Indicates an Atom 1.0 feed */
const FEED_TYPE_ATOM_1_0 = 'ATOM_1_0';
/**
* Holds the title of the current feed
*
* @var string
*/
private $title;
/**
* Holds the title of the current feed
*
* @var string
*/
private $title;
/**
* Holds the URI of the feed
*
* @var string
*/
private $uri;
/**
* Holds the URI of the feed
*
* @var string
*/
private $uri;
/**
* Holds the icon of the feed
*
*/
private $icon;
/**
* Holds the icon of the feed
*
*/
private $icon;
/**
* Holds the feed type during internal operations.
*
* @var string
*/
private $feedType;
/**
* Holds the feed type during internal operations.
*
* @var string
*/
private $feedType;
/**
* Collects data from an existing feed.
*
* Children should call this function in {@see BridgeInterface::collectData()}
* to extract a feed.
*
* @param string $url URL to the feed.
* @param int $maxItems Maximum number of items to collect from the feed
* (`-1`: no limit).
* @return self
*/
public function collectExpandableDatas($url, $maxItems = -1)
{
if (empty($url)) {
returnServerError('There is no $url for this RSS expander');
}
/**
* Collects data from an existing feed.
*
* Children should call this function in {@see BridgeInterface::collectData()}
* to extract a feed.
*
* @param string $url URL to the feed.
* @param int $maxItems Maximum number of items to collect from the feed
* (`-1`: no limit).
* @return self
*/
public function collectExpandableDatas($url, $maxItems = -1){
if(empty($url)) {
returnServerError('There is no $url for this RSS expander');
}
Debug::log('Loading from ' . $url);
Debug::log('Loading from ' . $url);
/* Notice we do not use cache here on purpose:
* we want a fresh view of the RSS stream each time
*/
/* Notice we do not use cache here on purpose:
* we want a fresh view of the RSS stream each time
*/
$mimeTypes = [
MrssFormat::MIME_TYPE,
AtomFormat::MIME_TYPE,
'*/*',
];
$httpHeaders = ['Accept: ' . implode(', ', $mimeTypes)];
$content = getContents($url, $httpHeaders)
or returnServerError('Could not request ' . $url);
$rssContent = simplexml_load_string(trim($content));
$mimeTypes = [
MrssFormat::MIME_TYPE,
AtomFormat::MIME_TYPE,
'*/*',
];
$httpHeaders = ['Accept: ' . implode(', ', $mimeTypes)];
$content = getContents($url, $httpHeaders)
or returnServerError('Could not request ' . $url);
$rssContent = simplexml_load_string(trim($content));
if ($rssContent === false) {
throw new \Exception('Unable to parse string as xml');
}
if ($rssContent === false) {
throw new \Exception('Unable to parse string as xml');
}
Debug::log('Detecting feed format/version');
switch (true) {
case isset($rssContent->item[0]):
Debug::log('Detected RSS 1.0 format');
$this->feedType = self::FEED_TYPE_RSS_1_0;
$this->collectRss1($rssContent, $maxItems);
break;
case isset($rssContent->channel[0]):
Debug::log('Detected RSS 0.9x or 2.0 format');
$this->feedType = self::FEED_TYPE_RSS_2_0;
$this->collectRss2($rssContent, $maxItems);
break;
case isset($rssContent->entry[0]):
Debug::log('Detected ATOM format');
$this->feedType = self::FEED_TYPE_ATOM_1_0;
$this->collectAtom1($rssContent, $maxItems);
break;
default:
Debug::log('Unknown feed format/version');
returnServerError('The feed format is unknown!');
break;
}
Debug::log('Detecting feed format/version');
switch(true) {
case isset($rssContent->item[0]):
Debug::log('Detected RSS 1.0 format');
$this->feedType = self::FEED_TYPE_RSS_1_0;
$this->collectRss1($rssContent, $maxItems);
break;
case isset($rssContent->channel[0]):
Debug::log('Detected RSS 0.9x or 2.0 format');
$this->feedType = self::FEED_TYPE_RSS_2_0;
$this->collectRss2($rssContent, $maxItems);
break;
case isset($rssContent->entry[0]):
Debug::log('Detected ATOM format');
$this->feedType = self::FEED_TYPE_ATOM_1_0;
$this->collectAtom1($rssContent, $maxItems);
break;
default:
Debug::log('Unknown feed format/version');
returnServerError('The feed format is unknown!');
break;
}
return $this;
}
return $this;
}
/**
* Collect data from a RSS 1.0 compatible feed
*
* @link http://web.resource.org/rss/1.0/spec RDF Site Summary (RSS) 1.0
*
* @param string $rssContent The RSS content
* @param int $maxItems Maximum number of items to collect from the feed
* (`-1`: no limit).
* @return void
*
* @todo Instead of passing $maxItems to all functions, just add all items
* and remove excessive items later.
*/
protected function collectRss1($rssContent, $maxItems)
{
$this->loadRss2Data($rssContent->channel[0]);
foreach ($rssContent->item as $item) {
Debug::log('parsing item ' . var_export($item, true));
$tmp_item = $this->parseItem($item);
if (!empty($tmp_item)) {
$this->items[] = $tmp_item;
}
if ($maxItems !== -1 && count($this->items) >= $maxItems) {
break;
}
}
}
/**
* Collect data from a RSS 1.0 compatible feed
*
* @link http://web.resource.org/rss/1.0/spec RDF Site Summary (RSS) 1.0
*
* @param string $rssContent The RSS content
* @param int $maxItems Maximum number of items to collect from the feed
* (`-1`: no limit).
* @return void
*
* @todo Instead of passing $maxItems to all functions, just add all items
* and remove excessive items later.
*/
protected function collectRss1($rssContent, $maxItems){
$this->loadRss2Data($rssContent->channel[0]);
foreach($rssContent->item as $item) {
Debug::log('parsing item ' . var_export($item, true));
$tmp_item = $this->parseItem($item);
if (!empty($tmp_item)) {
$this->items[] = $tmp_item;
}
if($maxItems !== -1 && count($this->items) >= $maxItems) break;
}
}
/**
* Collect data from a RSS 2.0 compatible feed
*
* @link http://www.rssboard.org/rss-specification RSS 2.0 Specification
*
* @param object $rssContent The RSS content
* @param int $maxItems Maximum number of items to collect from the feed
* (`-1`: no limit).
* @return void
*
* @todo Instead of passing $maxItems to all functions, just add all items
* and remove excessive items later.
*/
protected function collectRss2($rssContent, $maxItems)
{
$rssContent = $rssContent->channel[0];
Debug::log('RSS content is ===========\n'
. var_export($rssContent, true)
. '===========');
/**
* Collect data from a RSS 2.0 compatible feed
*
* @link http://www.rssboard.org/rss-specification RSS 2.0 Specification
*
* @param object $rssContent The RSS content
* @param int $maxItems Maximum number of items to collect from the feed
* (`-1`: no limit).
* @return void
*
* @todo Instead of passing $maxItems to all functions, just add all items
* and remove excessive items later.
*/
protected function collectRss2($rssContent, $maxItems){
$rssContent = $rssContent->channel[0];
Debug::log('RSS content is ===========\n'
. var_export($rssContent, true)
. '===========');
$this->loadRss2Data($rssContent);
foreach ($rssContent->item as $item) {
Debug::log('parsing item ' . var_export($item, true));
$tmp_item = $this->parseItem($item);
if (!empty($tmp_item)) {
$this->items[] = $tmp_item;
}
if ($maxItems !== -1 && count($this->items) >= $maxItems) {
break;
}
}
}
$this->loadRss2Data($rssContent);
foreach($rssContent->item as $item) {
Debug::log('parsing item ' . var_export($item, true));
$tmp_item = $this->parseItem($item);
if (!empty($tmp_item)) {
$this->items[] = $tmp_item;
}
if($maxItems !== -1 && count($this->items) >= $maxItems) break;
}
}
/**
* Collect data from a Atom 1.0 compatible feed
*
* @link https://tools.ietf.org/html/rfc4287 The Atom Syndication Format
*
* @param object $content The Atom content
* @param int $maxItems Maximum number of items to collect from the feed
* (`-1`: no limit).
* @return void
*
* @todo Instead of passing $maxItems to all functions, just add all items
* and remove excessive items later.
*/
protected function collectAtom1($content, $maxItems)
{
$this->loadAtomData($content);
foreach ($content->entry as $item) {
Debug::log('parsing item ' . var_export($item, true));
$tmp_item = $this->parseItem($item);
if (!empty($tmp_item)) {
$this->items[] = $tmp_item;
}
if ($maxItems !== -1 && count($this->items) >= $maxItems) {
break;
}
}
}
/**
* Collect data from a Atom 1.0 compatible feed
*
* @link https://tools.ietf.org/html/rfc4287 The Atom Syndication Format
*
* @param object $content The Atom content
* @param int $maxItems Maximum number of items to collect from the feed
* (`-1`: no limit).
* @return void
*
* @todo Instead of passing $maxItems to all functions, just add all items
* and remove excessive items later.
*/
protected function collectAtom1($content, $maxItems){
$this->loadAtomData($content);
foreach($content->entry as $item) {
Debug::log('parsing item ' . var_export($item, true));
$tmp_item = $this->parseItem($item);
if (!empty($tmp_item)) {
$this->items[] = $tmp_item;
}
if($maxItems !== -1 && count($this->items) >= $maxItems) break;
}
}
/**
* Load RSS 2.0 feed data into RSS-Bridge
*
* @param object $rssContent The RSS content
* @return void
*
* @todo set title, link, description, language, and so on
*/
protected function loadRss2Data($rssContent)
{
$this->title = trim((string)$rssContent->title);
$this->uri = trim((string)$rssContent->link);
/**
* Load RSS 2.0 feed data into RSS-Bridge
*
* @param object $rssContent The RSS content
* @return void
*
* @todo set title, link, description, language, and so on
*/
protected function loadRss2Data($rssContent){
$this->title = trim((string)$rssContent->title);
$this->uri = trim((string)$rssContent->link);
if (!empty($rssContent->image)) {
$this->icon = trim((string)$rssContent->image->url);
}
}
if (!empty($rssContent->image)) {
$this->icon = trim((string)$rssContent->image->url);
}
}
/**
* Load Atom feed data into RSS-Bridge
*
* @param object $content The Atom content
* @return void
*/
protected function loadAtomData($content)
{
$this->title = (string)$content->title;
/**
* Load Atom feed data into RSS-Bridge
*
* @param object $content The Atom content
* @return void
*/
protected function loadAtomData($content){
$this->title = (string)$content->title;
// Find best link (only one, or first of 'alternate')
if (!isset($content->link)) {
$this->uri = '';
} elseif (count($content->link) === 1) {
$this->uri = (string)$content->link[0]['href'];
} else {
$this->uri = '';
foreach ($content->link as $link) {
if (strtolower($link['rel']) === 'alternate') {
$this->uri = (string)$link['href'];
break;
}
}
}
// Find best link (only one, or first of 'alternate')
if(!isset($content->link)) {
$this->uri = '';
} elseif (count($content->link) === 1) {
$this->uri = (string)$content->link[0]['href'];
} else {
$this->uri = '';
foreach($content->link as $link) {
if(strtolower($link['rel']) === 'alternate') {
$this->uri = (string)$link['href'];
break;
}
}
}
if (!empty($content->icon)) {
$this->icon = (string)$content->icon;
} elseif (!empty($content->logo)) {
$this->icon = (string)$content->logo;
}
}
if(!empty($content->icon)) {
$this->icon = (string)$content->icon;
} elseif(!empty($content->logo)) {
$this->icon = (string)$content->logo;
}
}
/**
* Parse the contents of a single Atom feed item into a RSS-Bridge item for
* further transformation.
*
* @param object $feedItem A single feed item
* @return object The RSS-Bridge item
*
* @todo To reduce confusion, the RSS-Bridge item should maybe have a class
* of its own?
*/
protected function parseATOMItem($feedItem)
{
// Some ATOM entries also contain RSS 2.0 fields
$item = $this->parseRss2Item($feedItem);
/**
* Parse the contents of a single Atom feed item into a RSS-Bridge item for
* further transformation.
*
* @param object $feedItem A single feed item
* @return object The RSS-Bridge item
*
* @todo To reduce confusion, the RSS-Bridge item should maybe have a class
* of its own?
*/
protected function parseATOMItem($feedItem){
// Some ATOM entries also contain RSS 2.0 fields
$item = $this->parseRss2Item($feedItem);
if (isset($feedItem->id)) {
$item['uri'] = (string)$feedItem->id;
}
if (isset($feedItem->title)) {
$item['title'] = (string)$feedItem->title;
}
if (isset($feedItem->updated)) {
$item['timestamp'] = strtotime((string)$feedItem->updated);
}
if (isset($feedItem->author)) {
$item['author'] = (string)$feedItem->author->name;
}
if (isset($feedItem->content)) {
$item['content'] = (string)$feedItem->content;
}
if(isset($feedItem->id)) $item['uri'] = (string)$feedItem->id;
if(isset($feedItem->title)) $item['title'] = (string)$feedItem->title;
if(isset($feedItem->updated)) $item['timestamp'] = strtotime((string)$feedItem->updated);
if(isset($feedItem->author)) $item['author'] = (string)$feedItem->author->name;
if(isset($feedItem->content)) $item['content'] = (string)$feedItem->content;
//When "link" field is present, URL is more reliable than "id" field
if (count($feedItem->link) === 1) {
$item['uri'] = (string)$feedItem->link[0]['href'];
} else {
foreach ($feedItem->link as $link) {
if (strtolower($link['rel']) === 'alternate') {
$item['uri'] = (string)$link['href'];
}
if (strtolower($link['rel']) === 'enclosure') {
$item['enclosures'][] = (string)$link['href'];
}
}
}
//When "link" field is present, URL is more reliable than "id" field
if (count($feedItem->link) === 1) {
$item['uri'] = (string)$feedItem->link[0]['href'];
} else {
foreach($feedItem->link as $link) {
if(strtolower($link['rel']) === 'alternate') {
$item['uri'] = (string)$link['href'];
}
if(strtolower($link['rel']) === 'enclosure') {
$item['enclosures'][] = (string)$link['href'];
}
}
}
return $item;
}
return $item;
}
/**
* Parse the contents of a single RSS 0.91 feed item into a RSS-Bridge item
* for further transformation.
*
* @param object $feedItem A single feed item
* @return object The RSS-Bridge item
*
* @todo To reduce confusion, the RSS-Bridge item should maybe have a class
* of its own?
*/
protected function parseRss091Item($feedItem)
{
$item = [];
if (isset($feedItem->link)) {
$item['uri'] = (string)$feedItem->link;
}
if (isset($feedItem->title)) {
$item['title'] = (string)$feedItem->title;
}
// rss 0.91 doesn't support timestamps
// rss 0.91 doesn't support authors
// rss 0.91 doesn't support enclosures
if (isset($feedItem->description)) {
$item['content'] = (string)$feedItem->description;
}
return $item;
}
/**
* Parse the contents of a single RSS 0.91 feed item into a RSS-Bridge item
* for further transformation.
*
* @param object $feedItem A single feed item
* @return object The RSS-Bridge item
*
* @todo To reduce confusion, the RSS-Bridge item should maybe have a class
* of its own?
*/
protected function parseRss091Item($feedItem){
$item = array();
if(isset($feedItem->link)) $item['uri'] = (string)$feedItem->link;
if(isset($feedItem->title)) $item['title'] = (string)$feedItem->title;
// rss 0.91 doesn't support timestamps
// rss 0.91 doesn't support authors
// rss 0.91 doesn't support enclosures
if(isset($feedItem->description)) $item['content'] = (string)$feedItem->description;
return $item;
}
/**
* Parse the contents of a single RSS 1.0 feed item into a RSS-Bridge item
* for further transformation.
*
* @param object $feedItem A single feed item
* @return object The RSS-Bridge item
*
* @todo To reduce confusion, the RSS-Bridge item should maybe have a class
* of its own?
*/
protected function parseRss1Item($feedItem)
{
// 1.0 adds optional elements around the 0.91 standard
$item = $this->parseRss091Item($feedItem);
/**
* Parse the contents of a single RSS 1.0 feed item into a RSS-Bridge item
* for further transformation.
*
* @param object $feedItem A single feed item
* @return object The RSS-Bridge item
*
* @todo To reduce confusion, the RSS-Bridge item should maybe have a class
* of its own?
*/
protected function parseRss1Item($feedItem){
// 1.0 adds optional elements around the 0.91 standard
$item = $this->parseRss091Item($feedItem);
$namespaces = $feedItem->getNamespaces(true);
if (isset($namespaces['dc'])) {
$dc = $feedItem->children($namespaces['dc']);
if (isset($dc->date)) {
$item['timestamp'] = strtotime((string)$dc->date);
}
if (isset($dc->creator)) {
$item['author'] = (string)$dc->creator;
}
}
$namespaces = $feedItem->getNamespaces(true);
if(isset($namespaces['dc'])) {
$dc = $feedItem->children($namespaces['dc']);
if(isset($dc->date)) $item['timestamp'] = strtotime((string)$dc->date);
if(isset($dc->creator)) $item['author'] = (string)$dc->creator;
}
return $item;
}
return $item;
}
/**
* Parse the contents of a single RSS 2.0 feed item into a RSS-Bridge item
* for further transformation.
*
* @param object $feedItem A single feed item
* @return object The RSS-Bridge item
*
* @todo To reduce confusion, the RSS-Bridge item should maybe have a class
* of its own?
*/
protected function parseRss2Item($feedItem)
{
// Primary data is compatible to 0.91 with some additional data
$item = $this->parseRss091Item($feedItem);
/**
* Parse the contents of a single RSS 2.0 feed item into a RSS-Bridge item
* for further transformation.
*
* @param object $feedItem A single feed item
* @return object The RSS-Bridge item
*
* @todo To reduce confusion, the RSS-Bridge item should maybe have a class
* of its own?
*/
protected function parseRss2Item($feedItem){
// Primary data is compatible to 0.91 with some additional data
$item = $this->parseRss091Item($feedItem);
$namespaces = $feedItem->getNamespaces(true);
if (isset($namespaces['dc'])) {
$dc = $feedItem->children($namespaces['dc']);
}
if (isset($namespaces['media'])) {
$media = $feedItem->children($namespaces['media']);
}
$namespaces = $feedItem->getNamespaces(true);
if(isset($namespaces['dc'])) $dc = $feedItem->children($namespaces['dc']);
if(isset($namespaces['media'])) $media = $feedItem->children($namespaces['media']);
if (isset($feedItem->guid)) {
foreach ($feedItem->guid->attributes() as $attribute => $value) {
if (
$attribute === 'isPermaLink'
&& ($value === 'true' || (
filter_var($feedItem->guid, FILTER_VALIDATE_URL)
&& (empty($item['uri']) || !filter_var($item['uri'], FILTER_VALIDATE_URL))
)
)
) {
$item['uri'] = (string)$feedItem->guid;
break;
}
}
}
if(isset($feedItem->guid)) {
foreach($feedItem->guid->attributes() as $attribute => $value) {
if($attribute === 'isPermaLink'
&& ($value === 'true' || (
filter_var($feedItem->guid, FILTER_VALIDATE_URL)
&& (empty($item['uri']) || !filter_var($item['uri'], FILTER_VALIDATE_URL))
)
)
) {
$item['uri'] = (string)$feedItem->guid;
break;
}
}
}
if (isset($feedItem->pubDate)) {
$item['timestamp'] = strtotime((string)$feedItem->pubDate);
} elseif (isset($dc->date)) {
$item['timestamp'] = strtotime((string)$dc->date);
}
if(isset($feedItem->pubDate)) {
$item['timestamp'] = strtotime((string)$feedItem->pubDate);
} elseif(isset($dc->date)) {
$item['timestamp'] = strtotime((string)$dc->date);
}
if (isset($feedItem->author)) {
$item['author'] = (string)$feedItem->author;
} elseif (isset($feedItem->creator)) {
$item['author'] = (string)$feedItem->creator;
} elseif (isset($dc->creator)) {
$item['author'] = (string)$dc->creator;
} elseif (isset($media->credit)) {
$item['author'] = (string)$media->credit;
}
if(isset($feedItem->author)) {
$item['author'] = (string)$feedItem->author;
} elseif (isset($feedItem->creator)) {
$item['author'] = (string)$feedItem->creator;
} elseif(isset($dc->creator)) {
$item['author'] = (string)$dc->creator;
} elseif(isset($media->credit)) {
$item['author'] = (string)$media->credit;
}
if (isset($feedItem->enclosure) && !empty($feedItem->enclosure['url'])) {
$item['enclosures'] = [(string)$feedItem->enclosure['url']];
}
if(isset($feedItem->enclosure) && !empty($feedItem->enclosure['url'])) {
$item['enclosures'] = array((string)$feedItem->enclosure['url']);
}
return $item;
}
return $item;
}
/**
* Parse the contents of a single feed item, depending on the current feed
* type, into a RSS-Bridge item.
*
* @param object $item The current feed item
* @return object A RSS-Bridge item, with (hopefully) the whole content
*/
protected function parseItem($item)
{
switch ($this->feedType) {
case self::FEED_TYPE_RSS_1_0:
return $this->parseRss1Item($item);
break;
case self::FEED_TYPE_RSS_2_0:
return $this->parseRss2Item($item);
break;
case self::FEED_TYPE_ATOM_1_0:
return $this->parseATOMItem($item);
break;
default:
returnClientError('Unknown version ' . $this->getInput('version') . '!');
}
}
/**
* Parse the contents of a single feed item, depending on the current feed
* type, into a RSS-Bridge item.
*
* @param object $item The current feed item
* @return object A RSS-Bridge item, with (hopefully) the whole content
*/
protected function parseItem($item){
switch($this->feedType) {
case self::FEED_TYPE_RSS_1_0:
return $this->parseRss1Item($item);
break;
case self::FEED_TYPE_RSS_2_0:
return $this->parseRss2Item($item);
break;
case self::FEED_TYPE_ATOM_1_0:
return $this->parseATOMItem($item);
break;
default: returnClientError('Unknown version ' . $this->getInput('version') . '!');
}
}
/** {@inheritdoc} */
public function getURI()
{
return !empty($this->uri) ? $this->uri : parent::getURI();
}
/** {@inheritdoc} */
public function getURI(){
return !empty($this->uri) ? $this->uri : parent::getURI();
}
/** {@inheritdoc} */
public function getName()
{
return !empty($this->title) ? $this->title : parent::getName();
}
/** {@inheritdoc} */
public function getName(){
return !empty($this->title) ? $this->title : parent::getName();
}
/** {@inheritdoc} */
public function getIcon(){
return !empty($this->icon) ? $this->icon : parent::getIcon();
}
/** {@inheritdoc} */
public function getIcon()
{
return !empty($this->icon) ? $this->icon : parent::getIcon();
}
}

File diff suppressed because it is too large Load diff

View file

@ -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 https://unlicense.org/ UNLICENSE
* @link https://github.com/rss-bridge/rss-bridge
* @package Core
* @license https://unlicense.org/ UNLICENSE
* @link https://github.com/rss-bridge/rss-bridge
*/
/**
@ -16,126 +17,135 @@
*
* This class implements {@see FormatInterface}
*/
abstract class FormatAbstract implements FormatInterface {
abstract class FormatAbstract implements FormatInterface
{
/** The default charset (UTF-8) */
const DEFAULT_CHARSET = 'UTF-8';
/** The default charset (UTF-8) */
const DEFAULT_CHARSET = 'UTF-8';
/** MIME type of format output */
const MIME_TYPE = 'text/plain';
/** MIME type of format output */
const MIME_TYPE = 'text/plain';
/** @var string $charset The charset */
protected $charset;
/** @var string $charset The charset */
protected $charset;
/** @var array $items The items */
protected $items;
/** @var array $items The items */
protected $items;
/**
* @var int $lastModified A timestamp to indicate the last modified time of
* the output data.
*/
protected $lastModified;
/**
* @var int $lastModified A timestamp to indicate the last modified time of
* the output data.
*/
protected $lastModified;
/** @var array $extraInfos The extra infos */
protected $extraInfos;
/** @var array $extraInfos The extra infos */
protected $extraInfos;
/** {@inheritdoc} */
public function getMimeType()
{
return static::MIME_TYPE;
}
/** {@inheritdoc} */
public function getMimeType(){
return static::MIME_TYPE;
}
/**
* {@inheritdoc}
*
* @param string $charset {@inheritdoc}
*/
public function setCharset($charset)
{
$this->charset = $charset;
/**
* {@inheritdoc}
*
* @param string $charset {@inheritdoc}
*/
public function setCharset($charset){
$this->charset = $charset;
return $this;
}
return $this;
}
/** {@inheritdoc} */
public function getCharset()
{
$charset = $this->charset;
/** {@inheritdoc} */
public function getCharset(){
$charset = $this->charset;
return is_null($charset) ? static::DEFAULT_CHARSET : $charset;
}
return is_null($charset) ? static::DEFAULT_CHARSET : $charset;
}
/**
* Set the last modified time
*
* @param int $lastModified The last modified time
* @return void
*/
public function setLastModified($lastModified)
{
$this->lastModified = $lastModified;
}
/**
* Set the last modified time
*
* @param int $lastModified The last modified time
* @return void
*/
public function setLastModified($lastModified){
$this->lastModified = $lastModified;
}
/**
* {@inheritdoc}
*
* @param array $items {@inheritdoc}
*/
public function setItems(array $items)
{
$this->items = $items;
/**
* {@inheritdoc}
*
* @param array $items {@inheritdoc}
*/
public function setItems(array $items){
$this->items = $items;
return $this;
}
return $this;
}
/** {@inheritdoc} */
public function getItems()
{
if (!is_array($this->items)) {
throw new \LogicException('Feed the ' . get_class($this) . ' with "setItems" method before !');
}
/** {@inheritdoc} */
public function getItems(){
if(!is_array($this->items))
throw new \LogicException('Feed the ' . get_class($this) . ' with "setItems" method before !');
return $this->items;
}
return $this->items;
}
/**
* {@inheritdoc}
*
* @param array $extraInfos {@inheritdoc}
*/
public function setExtraInfos(array $extraInfos = [])
{
foreach (['name', 'uri', 'icon', 'donationUri'] as $infoName) {
if (!isset($extraInfos[$infoName])) {
$extraInfos[$infoName] = '';
}
}
/**
* {@inheritdoc}
*
* @param array $extraInfos {@inheritdoc}
*/
public function setExtraInfos(array $extraInfos = array()){
foreach(array('name', 'uri', 'icon', 'donationUri') as $infoName) {
if(!isset($extraInfos[$infoName])) {
$extraInfos[$infoName] = '';
}
}
$this->extraInfos = $extraInfos;
$this->extraInfos = $extraInfos;
return $this;
}
return $this;
}
/** {@inheritdoc} */
public function getExtraInfos()
{
if (is_null($this->extraInfos)) { // No extra info ?
$this->setExtraInfos(); // Define with default value
}
/** {@inheritdoc} */
public function getExtraInfos(){
if(is_null($this->extraInfos)) { // No extra info ?
$this->setExtraInfos(); // Define with default value
}
return $this->extraInfos;
}
return $this->extraInfos;
}
/**
* Sanitize HTML while leaving it functional.
*
* Keeps HTML as-is (with clickable hyperlinks) while reducing annoying and
* potentially dangerous things.
*
* @param string $html The HTML content
* @return string The sanitized HTML content
*
* @todo This belongs into `html.php`
* @todo Maybe switch to http://htmlpurifier.org/
* @todo Maybe switch to http://www.bioinformatics.org/phplabware/internal_utilities/htmLawed/index.php
*/
protected function sanitizeHtml(string $html): string
{
$html = str_replace('<script', '<&zwnj;script', $html); // Disable scripts, but leave them visible.
$html = str_replace('<iframe', '<&zwnj;iframe', $html);
$html = str_replace('<link', '<&zwnj;link', $html);
// We leave alone object and embed so that videos can play in RSS readers.
return $html;
}
/**
* Sanitize HTML while leaving it functional.
*
* Keeps HTML as-is (with clickable hyperlinks) while reducing annoying and
* potentially dangerous things.
*
* @param string $html The HTML content
* @return string The sanitized HTML content
*
* @todo This belongs into `html.php`
* @todo Maybe switch to http://htmlpurifier.org/
* @todo Maybe switch to http://www.bioinformatics.org/phplabware/internal_utilities/htmLawed/index.php
*/
protected function sanitizeHtml(string $html): string
{
$html = str_replace('<script', '<&zwnj;script', $html); // Disable scripts, but leave them visible.
$html = str_replace('<iframe', '<&zwnj;iframe', $html);
$html = str_replace('<link', '<&zwnj;link', $html);
// We leave alone object and embed so that videos can play in RSS readers.
return $html;
}
}

View file

@ -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,65 +7,66 @@
* 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
*/
class FormatFactory
{
private $folder;
private $formatNames;
private $folder;
private $formatNames;
public function __construct(string $folder = PATH_LIB_FORMATS)
{
$this->folder = $folder;
public function __construct(string $folder = PATH_LIB_FORMATS)
{
$this->folder = $folder;
// create format names
foreach(scandir($this->folder) as $file) {
if(preg_match('/^([^.]+)Format\.php$/U', $file, $m)) {
$this->formatNames[] = $m[1];
}
}
}
// create format names
foreach (scandir($this->folder) as $file) {
if (preg_match('/^([^.]+)Format\.php$/U', $file, $m)) {
$this->formatNames[] = $m[1];
}
}
}
/**
* @throws \InvalidArgumentException
* @param string $name The name of the format e.g. "Atom", "Mrss" or "Json"
*/
public function create(string $name): FormatInterface
{
if (! preg_match('/^[a-zA-Z0-9-]*$/', $name)) {
throw new \InvalidArgumentException('Format name invalid!');
}
$name = $this->sanitizeFormatName($name);
if ($name === null) {
throw new \InvalidArgumentException('Unknown format given!');
}
$className = '\\' . $name . 'Format';
return new $className;
}
/**
* @throws \InvalidArgumentException
* @param string $name The name of the format e.g. "Atom", "Mrss" or "Json"
*/
public function create(string $name): FormatInterface
{
if (! preg_match('/^[a-zA-Z0-9-]*$/', $name)) {
throw new \InvalidArgumentException('Format name invalid!');
}
$name = $this->sanitizeFormatName($name);
if ($name === null) {
throw new \InvalidArgumentException('Unknown format given!');
}
$className = '\\' . $name . 'Format';
return new $className();
}
public function getFormatNames(): array
{
return $this->formatNames;
}
public function getFormatNames(): array
{
return $this->formatNames;
}
protected function sanitizeFormatName(string $name) {
$name = ucfirst(strtolower($name));
protected function sanitizeFormatName(string $name)
{
$name = ucfirst(strtolower($name));
// Trim trailing '.php' if exists
if (preg_match('/(.+)(?:\.php)/', $name, $matches)) {
$name = $matches[1];
}
// Trim trailing '.php' if exists
if (preg_match('/(.+)(?:\.php)/', $name, $matches)) {
$name = $matches[1];
}
// Trim trailing 'Format' if exists
if (preg_match('/(.+)(?:Format)/i', $name, $matches)) {
$name = $matches[1];
}
if (in_array($name, $this->formatNames)) {
return $name;
}
return null;
}
// Trim trailing 'Format' if exists
if (preg_match('/(.+)(?:Format)/i', $name, $matches)) {
$name = $matches[1];
}
if (in_array($name, $this->formatNames)) {
return $name;
}
return null;
}
}

View file

@ -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
*/
/**
@ -18,66 +19,67 @@
* @todo Explain parameters and return values in more detail
* @todo Return self more often (to allow call chaining)
*/
interface FormatInterface {
/**
* Generate a string representation of the current data
*
* @return string The string representation
*/
public function stringify();
interface FormatInterface
{
/**
* Generate a string representation of the current data
*
* @return string The string representation
*/
public function stringify();
/**
* Set items
*
* @param array $bridges The items
* @return self The format object
*
* @todo Rename parameter `$bridges` to `$items`
*/
public function setItems(array $bridges);
/**
* Set items
*
* @param array $bridges The items
* @return self The format object
*
* @todo Rename parameter `$bridges` to `$items`
*/
public function setItems(array $bridges);
/**
* Return items
*
* @throws \LogicException if the items are not set
* @return array The items
*/
public function getItems();
/**
* Return items
*
* @throws \LogicException if the items are not set
* @return array The items
*/
public function getItems();
/**
* Set extra information
*
* @param array $infos Extra information
* @return self The format object
*/
public function setExtraInfos(array $infos);
/**
* Set extra information
*
* @param array $infos Extra information
* @return self The format object
*/
public function setExtraInfos(array $infos);
/**
* Return extra information
*
* @return array Extra information
*/
public function getExtraInfos();
/**
* Return extra information
*
* @return array Extra information
*/
public function getExtraInfos();
/**
* Return MIME type
*
* @return string The MIME type
*/
public function getMimeType();
/**
* Return MIME type
*
* @return string The MIME type
*/
public function getMimeType();
/**
* Set charset
*
* @param string $charset The charset
* @return self The format object
*/
public function setCharset($charset);
/**
* Set charset
*
* @param string $charset The charset
* @return self The format object
*/
public function setCharset($charset);
/**
* Return current charset
*
* @return string The charset
*/
public function getCharset();
/**
* Return current charset
*
* @return string The charset
*/
public function getCharset();
}

View file

@ -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,234 +7,259 @@
* 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
*/
/**
* Validator for bridge parameters
*/
class ParameterValidator {
class ParameterValidator
{
/**
* Holds the list of invalid parameters
*
* @var array
*/
private $invalid = [];
/**
* Holds the list of invalid parameters
*
* @var array
*/
private $invalid = array();
/**
* Add item to list of invalid parameters
*
* @param string $name The name of the parameter
* @param string $reason The reason for that parameter being invalid
* @return void
*/
private function addInvalidParameter($name, $reason)
{
$this->invalid[] = [
'name' => $name,
'reason' => $reason
];
}
/**
* Add item to list of invalid parameters
*
* @param string $name The name of the parameter
* @param string $reason The reason for that parameter being invalid
* @return void
*/
private function addInvalidParameter($name, $reason){
$this->invalid[] = array(
'name' => $name,
'reason' => $reason
);
}
/**
* Return list of invalid parameters.
*
* Each element is an array of 'name' and 'reason'.
*
* @return array List of invalid parameters
*/
public function getInvalidParameters()
{
return $this->invalid;
}
/**
* Return list of invalid parameters.
*
* Each element is an array of 'name' and 'reason'.
*
* @return array List of invalid parameters
*/
public function getInvalidParameters() {
return $this->invalid;
}
/**
* Validate value for a text input
*
* @param string $value The value of a text input
* @param string|null $pattern (optional) A regex pattern
* @return string|null The filtered value or null if the value is invalid
*/
private function validateTextValue($value, $pattern = null)
{
if (!is_null($pattern)) {
$filteredValue = filter_var(
$value,
FILTER_VALIDATE_REGEXP,
['options' => [
'regexp' => '/^' . $pattern . '$/'
]
]
);
} else {
$filteredValue = filter_var($value);
}
/**
* Validate value for a text input
*
* @param string $value The value of a text input
* @param string|null $pattern (optional) A regex pattern
* @return string|null The filtered value or null if the value is invalid
*/
private function validateTextValue($value, $pattern = null){
if(!is_null($pattern)) {
$filteredValue = filter_var($value,
FILTER_VALIDATE_REGEXP,
array('options' => array(
'regexp' => '/^' . $pattern . '$/'
)
));
} else {
$filteredValue = filter_var($value);
}
if ($filteredValue === false) {
return null;
}
if($filteredValue === false)
return null;
return $filteredValue;
}
return $filteredValue;
}
/**
* Validate value for a number input
*
* @param int $value The value of a number input
* @return int|null The filtered value or null if the value is invalid
*/
private function validateNumberValue($value)
{
$filteredValue = filter_var($value, FILTER_VALIDATE_INT);
/**
* Validate value for a number input
*
* @param int $value The value of a number input
* @return int|null The filtered value or null if the value is invalid
*/
private function validateNumberValue($value){
$filteredValue = filter_var($value, FILTER_VALIDATE_INT);
if ($filteredValue === false) {
return null;
}
if($filteredValue === false)
return null;
return $filteredValue;
}
return $filteredValue;
}
/**
* Validate value for a checkbox
*
* @param bool $value The value of a checkbox
* @return bool The filtered value
*/
private function validateCheckboxValue($value)
{
return filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
}
/**
* Validate value for a checkbox
*
* @param bool $value The value of a checkbox
* @return bool The filtered value
*/
private function validateCheckboxValue($value){
return filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
}
/**
* Validate value for a list
*
* @param string $value The value of a list
* @param array $expectedValues A list of expected values
* @return string|null The filtered value or null if the value is invalid
*/
private function validateListValue($value, $expectedValues)
{
$filteredValue = filter_var($value);
/**
* Validate value for a list
*
* @param string $value The value of a list
* @param array $expectedValues A list of expected values
* @return string|null The filtered value or null if the value is invalid
*/
private function validateListValue($value, $expectedValues){
$filteredValue = filter_var($value);
if ($filteredValue === false) {
return null;
}
if($filteredValue === false)
return null;
if (!in_array($filteredValue, $expectedValues)) { // Check sub-values?
foreach ($expectedValues as $subName => $subValue) {
if (is_array($subValue) && in_array($filteredValue, $subValue)) {
return $filteredValue;
}
}
return null;
}
if(!in_array($filteredValue, $expectedValues)) { // Check sub-values?
foreach($expectedValues as $subName => $subValue) {
if(is_array($subValue) && in_array($filteredValue, $subValue))
return $filteredValue;
}
return null;
}
return $filteredValue;
}
return $filteredValue;
}
/**
* Check if all required parameters are satisfied
*
* @param array $data (ref) A list of input values
* @param array $parameters The bridge parameters
* @return bool True if all parameters are satisfied
*/
public function validateData(&$data, $parameters)
{
if (!is_array($data)) {
return false;
}
/**
* Check if all required parameters are satisfied
*
* @param array $data (ref) A list of input values
* @param array $parameters The bridge parameters
* @return bool True if all parameters are satisfied
*/
public function validateData(&$data, $parameters){
foreach ($data as $name => $value) {
// Some RSS readers add a cache-busting parameter (_=<timestamp>) to feed URLs, detect and ignore them.
if ($name === '_') {
continue;
}
if(!is_array($data))
return false;
$registered = false;
foreach ($parameters as $context => $set) {
if (array_key_exists($name, $set)) {
$registered = true;
if (!isset($set[$name]['type'])) {
$set[$name]['type'] = 'text';
}
foreach($data as $name => $value) {
// Some RSS readers add a cache-busting parameter (_=<timestamp>) to feed URLs, detect and ignore them.
if ($name === '_') continue;
switch ($set[$name]['type']) {
case 'number':
$data[$name] = $this->validateNumberValue($value);
break;
case 'checkbox':
$data[$name] = $this->validateCheckboxValue($value);
break;
case 'list':
$data[$name] = $this->validateListValue($value, $set[$name]['values']);
break;
default:
case 'text':
if (isset($set[$name]['pattern'])) {
$data[$name] = $this->validateTextValue($value, $set[$name]['pattern']);
} else {
$data[$name] = $this->validateTextValue($value);
}
break;
}
$registered = false;
foreach($parameters as $context => $set) {
if(array_key_exists($name, $set)) {
$registered = true;
if(!isset($set[$name]['type'])) {
$set[$name]['type'] = 'text';
}
if (is_null($data[$name]) && isset($set[$name]['required']) && $set[$name]['required']) {
$this->addInvalidParameter($name, 'Parameter is invalid!');
}
}
}
switch($set[$name]['type']) {
case 'number':
$data[$name] = $this->validateNumberValue($value);
break;
case 'checkbox':
$data[$name] = $this->validateCheckboxValue($value);
break;
case 'list':
$data[$name] = $this->validateListValue($value, $set[$name]['values']);
break;
default:
case 'text':
if(isset($set[$name]['pattern'])) {
$data[$name] = $this->validateTextValue($value, $set[$name]['pattern']);
} else {
$data[$name] = $this->validateTextValue($value);
}
break;
}
if (!$registered) {
$this->addInvalidParameter($name, 'Parameter is not registered!');
}
}
if(is_null($data[$name]) && isset($set[$name]['required']) && $set[$name]['required']) {
$this->addInvalidParameter($name, 'Parameter is invalid!');
}
}
}
return empty($this->invalid);
}
if(!$registered) {
$this->addInvalidParameter($name, 'Parameter is not registered!');
}
}
/**
* Get the name of the context matching the provided inputs
*
* @param array $data Associative array of user data
* @param array $parameters Array of bridge parameters
* @return string|null Returns the context name or null if no match was found
*/
public function getQueriedContext($data, $parameters)
{
$queriedContexts = [];
return empty($this->invalid);
}
// Detect matching context
foreach ($parameters as $context => $set) {
$queriedContexts[$context] = null;
/**
* Get the name of the context matching the provided inputs
*
* @param array $data Associative array of user data
* @param array $parameters Array of bridge parameters
* @return string|null Returns the context name or null if no match was found
*/
public function getQueriedContext($data, $parameters){
$queriedContexts = array();
// Ensure all user data exist in the current context
$notInContext = array_diff_key($data, $set);
if (array_key_exists('global', $parameters)) {
$notInContext = array_diff_key($notInContext, $parameters['global']);
}
if (sizeof($notInContext) > 0) {
continue;
}
// Detect matching context
foreach($parameters as $context => $set) {
$queriedContexts[$context] = null;
// Check if all parameters of the context are satisfied
foreach ($set as $id => $properties) {
if (isset($data[$id]) && !empty($data[$id])) {
$queriedContexts[$context] = true;
} elseif (
isset($properties['type'])
&& ($properties['type'] === 'checkbox' || $properties['type'] === 'list')
) {
continue;
} elseif (isset($properties['required']) && $properties['required'] === true) {
$queriedContexts[$context] = false;
break;
}
}
}
// Ensure all user data exist in the current context
$notInContext = array_diff_key($data, $set);
if(array_key_exists('global', $parameters))
$notInContext = array_diff_key($notInContext, $parameters['global']);
if(sizeof($notInContext) > 0)
continue;
// Abort if one of the globally required parameters is not satisfied
if (
array_key_exists('global', $parameters)
&& $queriedContexts['global'] === false
) {
return null;
}
unset($queriedContexts['global']);
// Check if all parameters of the context are satisfied
foreach($set as $id => $properties) {
if(isset($data[$id]) && !empty($data[$id])) {
$queriedContexts[$context] = true;
} elseif (isset($properties['type'])
&& ($properties['type'] === 'checkbox' || $properties['type'] === 'list')) {
continue;
} elseif(isset($properties['required']) && $properties['required'] === true) {
$queriedContexts[$context] = false;
break;
}
}
}
// Abort if one of the globally required parameters is not satisfied
if(array_key_exists('global', $parameters)
&& $queriedContexts['global'] === false) {
return null;
}
unset($queriedContexts['global']);
switch(array_sum($queriedContexts)) {
case 0: // Found no match, is there a context without parameters?
if(isset($data['context'])) return $data['context'];
foreach($queriedContexts as $context => $queried) {
if(is_null($queried)) {
return $context;
}
}
return null;
case 1: // Found unique match
return array_search(true, $queriedContexts);
default: return false;
}
}
switch (array_sum($queriedContexts)) {
case 0: // Found no match, is there a context without parameters?
if (isset($data['context'])) {
return $data['context'];
}
foreach ($queriedContexts as $context => $queried) {
if (is_null($queried)) {
return $context;
}
}
return null;
case 1: // Found unique match
return array_search(true, $queriedContexts);
default:
return false;
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,48 +1,50 @@
<?php
final class HttpException extends \Exception {}
final class HttpException extends \Exception
{
}
// todo: move this somewhere useful, possibly into a function
const RSSBRIDGE_HTTP_STATUS_CODES = [
'100' => 'Continue',
'101' => 'Switching Protocols',
'200' => 'OK',
'201' => 'Created',
'202' => 'Accepted',
'203' => 'Non-Authoritative Information',
'204' => 'No Content',
'205' => 'Reset Content',
'206' => 'Partial Content',
'300' => 'Multiple Choices',
'302' => 'Found',
'303' => 'See Other',
'304' => 'Not Modified',
'305' => 'Use Proxy',
'400' => 'Bad Request',
'401' => 'Unauthorized',
'402' => 'Payment Required',
'403' => 'Forbidden',
'404' => 'Not Found',
'405' => 'Method Not Allowed',
'406' => 'Not Acceptable',
'407' => 'Proxy Authentication Required',
'408' => 'Request Timeout',
'409' => 'Conflict',
'410' => 'Gone',
'411' => 'Length Required',
'412' => 'Precondition Failed',
'413' => 'Request Entity Too Large',
'414' => 'Request-URI Too Long',
'415' => 'Unsupported Media Type',
'416' => 'Requested Range Not Satisfiable',
'417' => 'Expectation Failed',
'429' => 'Too Many Requests',
'500' => 'Internal Server Error',
'501' => 'Not Implemented',
'502' => 'Bad Gateway',
'503' => 'Service Unavailable',
'504' => 'Gateway Timeout',
'505' => 'HTTP Version Not Supported'
'100' => 'Continue',
'101' => 'Switching Protocols',
'200' => 'OK',
'201' => 'Created',
'202' => 'Accepted',
'203' => 'Non-Authoritative Information',
'204' => 'No Content',
'205' => 'Reset Content',
'206' => 'Partial Content',
'300' => 'Multiple Choices',
'302' => 'Found',
'303' => 'See Other',
'304' => 'Not Modified',
'305' => 'Use Proxy',
'400' => 'Bad Request',
'401' => 'Unauthorized',
'402' => 'Payment Required',
'403' => 'Forbidden',
'404' => 'Not Found',
'405' => 'Method Not Allowed',
'406' => 'Not Acceptable',
'407' => 'Proxy Authentication Required',
'408' => 'Request Timeout',
'409' => 'Conflict',
'410' => 'Gone',
'411' => 'Length Required',
'412' => 'Precondition Failed',
'413' => 'Request Entity Too Large',
'414' => 'Request-URI Too Long',
'415' => 'Unsupported Media Type',
'416' => 'Requested Range Not Satisfiable',
'417' => 'Expectation Failed',
'429' => 'Too Many Requests',
'500' => 'Internal Server Error',
'501' => 'Not Implemented',
'502' => 'Bad Gateway',
'503' => 'Service Unavailable',
'504' => 'Gateway Timeout',
'505' => 'HTTP Version Not Supported'
];
/**
@ -61,70 +63,70 @@ const RSSBRIDGE_HTTP_STATUS_CODES = [
* @return string|array
*/
function getContents(
string $url,
array $httpHeaders = [],
array $curlOptions = [],
bool $returnFull = false
string $url,
array $httpHeaders = [],
array $curlOptions = [],
bool $returnFull = false
) {
$cacheFactory = new CacheFactory();
$cacheFactory = new CacheFactory();
$cache = $cacheFactory->create(Configuration::getConfig('cache', 'type'));
$cache->setScope('server');
$cache->purgeCache(86400); // 24 hours (forced)
$cache->setKey([$url]);
$cache = $cacheFactory->create(Configuration::getConfig('cache', 'type'));
$cache->setScope('server');
$cache->purgeCache(86400); // 24 hours (forced)
$cache->setKey([$url]);
$config = [
'headers' => $httpHeaders,
'curl_options' => $curlOptions,
];
if (defined('PROXY_URL') && !defined('NOPROXY')) {
$config['proxy'] = PROXY_URL;
}
if(!Debug::isEnabled() && $cache->getTime()) {
$config['if_not_modified_since'] = $cache->getTime();
}
$config = [
'headers' => $httpHeaders,
'curl_options' => $curlOptions,
];
if (defined('PROXY_URL') && !defined('NOPROXY')) {
$config['proxy'] = PROXY_URL;
}
if (!Debug::isEnabled() && $cache->getTime()) {
$config['if_not_modified_since'] = $cache->getTime();
}
$result = _http_request($url, $config);
$response = [
'code' => $result['code'],
'status_lines' => $result['status_lines'],
'header' => $result['headers'],
'content' => $result['body'],
];
$result = _http_request($url, $config);
$response = [
'code' => $result['code'],
'status_lines' => $result['status_lines'],
'header' => $result['headers'],
'content' => $result['body'],
];
switch($result['code']) {
case 200:
case 201:
case 202:
if(isset($result['headers']['cache-control'])) {
$cachecontrol = $result['headers']['cache-control'];
$lastValue = array_pop($cachecontrol);
$directives = explode(',', $lastValue);
$directives = array_map('trim', $directives);
if(in_array('no-cache', $directives) || in_array('no-store', $directives)) {
// Don't cache as instructed by the server
break;
}
}
$cache->saveData($result['body']);
break;
case 304: // Not Modified
$response['content'] = $cache->loadData();
break;
default:
throw new HttpException(
sprintf(
'%s %s',
$result['code'],
RSSBRIDGE_HTTP_STATUS_CODES[$result['code']] ?? ''
),
$result['code']
);
}
if ($returnFull === true) {
return $response;
}
return $response['content'];
switch ($result['code']) {
case 200:
case 201:
case 202:
if (isset($result['headers']['cache-control'])) {
$cachecontrol = $result['headers']['cache-control'];
$lastValue = array_pop($cachecontrol);
$directives = explode(',', $lastValue);
$directives = array_map('trim', $directives);
if (in_array('no-cache', $directives) || in_array('no-store', $directives)) {
// Don't cache as instructed by the server
break;
}
}
$cache->saveData($result['body']);
break;
case 304: // Not Modified
$response['content'] = $cache->loadData();
break;
default:
throw new HttpException(
sprintf(
'%s %s',
$result['code'],
RSSBRIDGE_HTTP_STATUS_CODES[$result['code']] ?? ''
),
$result['code']
);
}
if ($returnFull === true) {
return $response;
}
return $response['content'];
}
/**
@ -136,85 +138,85 @@ function getContents(
*/
function _http_request(string $url, array $config = []): array
{
$defaults = [
'useragent' => Configuration::getConfig('http', 'useragent'),
'timeout' => Configuration::getConfig('http', 'timeout'),
'headers' => [],
'proxy' => null,
'curl_options' => [],
'if_not_modified_since' => null,
'retries' => 3,
];
$config = array_merge($defaults, $config);
$defaults = [
'useragent' => Configuration::getConfig('http', 'useragent'),
'timeout' => Configuration::getConfig('http', 'timeout'),
'headers' => [],
'proxy' => null,
'curl_options' => [],
'if_not_modified_since' => null,
'retries' => 3,
];
$config = array_merge($defaults, $config);
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_MAXREDIRS, 5);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_HTTPHEADER, $config['headers']);
curl_setopt($ch, CURLOPT_USERAGENT, $config['useragent']);
curl_setopt($ch, CURLOPT_TIMEOUT, $config['timeout']);
curl_setopt($ch, CURLOPT_ENCODING, '');
curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
if($config['proxy']) {
curl_setopt($ch, CURLOPT_PROXY, $config['proxy']);
}
if (curl_setopt_array($ch, $config['curl_options']) === false) {
throw new \Exception('Tried to set an illegal curl option');
}
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_MAXREDIRS, 5);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_HTTPHEADER, $config['headers']);
curl_setopt($ch, CURLOPT_USERAGENT, $config['useragent']);
curl_setopt($ch, CURLOPT_TIMEOUT, $config['timeout']);
curl_setopt($ch, CURLOPT_ENCODING, '');
curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
if ($config['proxy']) {
curl_setopt($ch, CURLOPT_PROXY, $config['proxy']);
}
if (curl_setopt_array($ch, $config['curl_options']) === false) {
throw new \Exception('Tried to set an illegal curl option');
}
if ($config['if_not_modified_since']) {
curl_setopt($ch, CURLOPT_TIMEVALUE, $config['if_not_modified_since']);
curl_setopt($ch, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE);
}
if ($config['if_not_modified_since']) {
curl_setopt($ch, CURLOPT_TIMEVALUE, $config['if_not_modified_since']);
curl_setopt($ch, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE);
}
$responseStatusLines = [];
$responseHeaders = [];
curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($ch, $rawHeader) use (&$responseHeaders, &$responseStatusLines) {
$len = strlen($rawHeader);
if ($rawHeader === "\r\n") {
return $len;
}
if (preg_match('#^HTTP/(2|1.1|1.0)#', $rawHeader)) {
$responseStatusLines[] = $rawHeader;
return $len;
}
$header = explode(':', $rawHeader);
if (count($header) === 1) {
return $len;
}
$name = mb_strtolower(trim($header[0]));
$value = trim(implode(':', array_slice($header, 1)));
if (!isset($responseHeaders[$name])) {
$responseHeaders[$name] = [];
}
$responseHeaders[$name][] = $value;
return $len;
});
$responseStatusLines = [];
$responseHeaders = [];
curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($ch, $rawHeader) use (&$responseHeaders, &$responseStatusLines) {
$len = strlen($rawHeader);
if ($rawHeader === "\r\n") {
return $len;
}
if (preg_match('#^HTTP/(2|1.1|1.0)#', $rawHeader)) {
$responseStatusLines[] = $rawHeader;
return $len;
}
$header = explode(':', $rawHeader);
if (count($header) === 1) {
return $len;
}
$name = mb_strtolower(trim($header[0]));
$value = trim(implode(':', array_slice($header, 1)));
if (!isset($responseHeaders[$name])) {
$responseHeaders[$name] = [];
}
$responseHeaders[$name][] = $value;
return $len;
});
$attempts = 0;
while(true) {
$attempts++;
$data = curl_exec($ch);
if ($data !== false) {
// The network call was successful, so break out of the loop
break;
}
if ($attempts > $config['retries']) {
// Finally give up
throw new HttpException(sprintf('%s (%s)', curl_error($ch), curl_errno($ch)));
}
}
$attempts = 0;
while (true) {
$attempts++;
$data = curl_exec($ch);
if ($data !== false) {
// The network call was successful, so break out of the loop
break;
}
if ($attempts > $config['retries']) {
// Finally give up
throw new HttpException(sprintf('%s (%s)', curl_error($ch), curl_errno($ch)));
}
}
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return [
'code' => $statusCode,
'status_lines' => $responseStatusLines,
'headers' => $responseHeaders,
'body' => $data,
];
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return [
'code' => $statusCode,
'status_lines' => $responseStatusLines,
'headers' => $responseHeaders,
'body' => $data,
];
}
/**
@ -243,28 +245,31 @@ function _http_request(string $url, array $config = []): array
* tags when returning plaintext.
* @return false|simple_html_dom Contents as simplehtmldom object.
*/
function getSimpleHTMLDOM($url,
$header = array(),
$opts = array(),
$lowercase = true,
$forceTagsClosed = true,
$target_charset = DEFAULT_TARGET_CHARSET,
$stripRN = true,
$defaultBRText = DEFAULT_BR_TEXT,
$defaultSpanText = DEFAULT_SPAN_TEXT){
$content = getContents(
$url,
$header ?? [],
$opts ?? []
);
return str_get_html($content,
$lowercase,
$forceTagsClosed,
$target_charset,
$stripRN,
$defaultBRText,
$defaultSpanText);
function getSimpleHTMLDOM(
$url,
$header = [],
$opts = [],
$lowercase = true,
$forceTagsClosed = true,
$target_charset = DEFAULT_TARGET_CHARSET,
$stripRN = true,
$defaultBRText = DEFAULT_BR_TEXT,
$defaultSpanText = DEFAULT_SPAN_TEXT
) {
$content = getContents(
$url,
$header ?? [],
$opts ?? []
);
return str_get_html(
$content,
$lowercase,
$forceTagsClosed,
$target_charset,
$stripRN,
$defaultBRText,
$defaultSpanText
);
}
/**
@ -297,53 +302,58 @@ function getSimpleHTMLDOM($url,
* tags when returning plaintext.
* @return false|simple_html_dom Contents as simplehtmldom object.
*/
function getSimpleHTMLDOMCached($url,
$duration = 86400,
$header = array(),
$opts = array(),
$lowercase = true,
$forceTagsClosed = true,
$target_charset = DEFAULT_TARGET_CHARSET,
$stripRN = true,
$defaultBRText = DEFAULT_BR_TEXT,
$defaultSpanText = DEFAULT_SPAN_TEXT){
function getSimpleHTMLDOMCached(
$url,
$duration = 86400,
$header = [],
$opts = [],
$lowercase = true,
$forceTagsClosed = true,
$target_charset = DEFAULT_TARGET_CHARSET,
$stripRN = true,
$defaultBRText = DEFAULT_BR_TEXT,
$defaultSpanText = DEFAULT_SPAN_TEXT
) {
Debug::log('Caching url ' . $url . ', duration ' . $duration);
Debug::log('Caching url ' . $url . ', duration ' . $duration);
// Initialize cache
$cacheFac = new CacheFactory();
// Initialize cache
$cacheFac = new CacheFactory();
$cache = $cacheFac->create(Configuration::getConfig('cache', 'type'));
$cache->setScope('pages');
$cache->purgeCache(86400); // 24 hours (forced)
$cache = $cacheFac->create(Configuration::getConfig('cache', 'type'));
$cache->setScope('pages');
$cache->purgeCache(86400); // 24 hours (forced)
$params = [$url];
$cache->setKey($params);
$params = array($url);
$cache->setKey($params);
// Determine if cached file is within duration
$time = $cache->getTime();
if (
$time !== false
&& (time() - $duration < $time)
&& !Debug::isEnabled()
) { // Contents within duration
$content = $cache->loadData();
} else { // Content not within duration
$content = getContents(
$url,
$header ?? [],
$opts ?? []
);
if ($content !== false) {
$cache->saveData($content);
}
}
// Determine if cached file is within duration
$time = $cache->getTime();
if($time !== false
&& (time() - $duration < $time)
&& !Debug::isEnabled()) { // Contents within duration
$content = $cache->loadData();
} else { // Content not within duration
$content = getContents(
$url,
$header ?? [],
$opts ?? []
);
if($content !== false) {
$cache->saveData($content);
}
}
return str_get_html($content,
$lowercase,
$forceTagsClosed,
$target_charset,
$stripRN,
$defaultBRText,
$defaultSpanText);
return str_get_html(
$content,
$lowercase,
$forceTagsClosed,
$target_charset,
$stripRN,
$defaultBRText,
$defaultSpanText
);
}
/**
@ -360,49 +370,53 @@ function getSimpleHTMLDOMCached($url,
* @param string $url The URL or path to the file.
* @return string The MIME type of the file.
*/
function getMimeType($url) {
static $mime = null;
function getMimeType($url)
{
static $mime = null;
if (is_null($mime)) {
// Default values, overriden by /etc/mime.types when present
$mime = array(
'jpg' => 'image/jpeg',
'gif' => 'image/gif',
'png' => 'image/png',
'image' => 'image/*',
'mp3' => 'audio/mpeg',
);
// '@' is used to mute open_basedir warning, see issue #818
if (@is_readable('/etc/mime.types')) {
$file = fopen('/etc/mime.types', 'r');
while(($line = fgets($file)) !== false) {
$line = trim(preg_replace('/#.*/', '', $line));
if(!$line)
continue;
$parts = preg_split('/\s+/', $line);
if(count($parts) == 1)
continue;
$type = array_shift($parts);
foreach($parts as $part)
$mime[$part] = $type;
}
fclose($file);
}
}
if (is_null($mime)) {
// Default values, overriden by /etc/mime.types when present
$mime = [
'jpg' => 'image/jpeg',
'gif' => 'image/gif',
'png' => 'image/png',
'image' => 'image/*',
'mp3' => 'audio/mpeg',
];
// '@' is used to mute open_basedir warning, see issue #818
if (@is_readable('/etc/mime.types')) {
$file = fopen('/etc/mime.types', 'r');
while (($line = fgets($file)) !== false) {
$line = trim(preg_replace('/#.*/', '', $line));
if (!$line) {
continue;
}
$parts = preg_split('/\s+/', $line);
if (count($parts) == 1) {
continue;
}
$type = array_shift($parts);
foreach ($parts as $part) {
$mime[$part] = $type;
}
}
fclose($file);
}
}
if (strpos($url, '?') !== false) {
$url_temp = substr($url, 0, strpos($url, '?'));
if (strpos($url, '#') !== false) {
$anchor = substr($url, strpos($url, '#'));
$url_temp .= $anchor;
}
$url = $url_temp;
}
if (strpos($url, '?') !== false) {
$url_temp = substr($url, 0, strpos($url, '?'));
if (strpos($url, '#') !== false) {
$anchor = substr($url, strpos($url, '#'));
$url_temp .= $anchor;
}
$url = $url_temp;
}
$ext = strtolower(pathinfo($url, PATHINFO_EXTENSION));
if (!empty($mime[$ext])) {
return $mime[$ext];
}
$ext = strtolower(pathinfo($url, PATHINFO_EXTENSION));
if (!empty($mime[$ext])) {
return $mime[$ext];
}
return 'application/octet-stream';
return 'application/octet-stream';
}

View file

@ -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
*/
/**
@ -20,8 +21,9 @@
* @link https://en.wikipedia.org/wiki/List_of_HTTP_status_codes List of HTTP
* status codes
*/
function returnError($message, $code){
throw new \Exception($message, $code);
function returnError($message, $code)
{
throw new \Exception($message, $code);
}
/**
@ -29,8 +31,9 @@ function returnError($message, $code){
*
* @param string $message The error message
*/
function returnClientError($message){
returnError($message, 400);
function returnClientError($message)
{
returnError($message, 400);
}
/**
@ -38,8 +41,9 @@ function returnClientError($message){
*
* @param string $message The error message
*/
function returnServerError($message){
returnError($message, 500);
function returnServerError($message)
{
returnError($message, 500);
}
/**
@ -50,27 +54,28 @@ function returnServerError($message){
*
* @return int The total number the same error has appeared
*/
function logBridgeError($bridgeName, $code) {
$cacheFac = new CacheFactory();
function logBridgeError($bridgeName, $code)
{
$cacheFac = new CacheFactory();
$cache = $cacheFac->create(Configuration::getConfig('cache', 'type'));
$cache->setScope('error_reporting');
$cache->setkey($bridgeName . '_' . $code);
$cache->purgeCache(86400); // 24 hours
$cache = $cacheFac->create(Configuration::getConfig('cache', 'type'));
$cache->setScope('error_reporting');
$cache->setkey($bridgeName . '_' . $code);
$cache->purgeCache(86400); // 24 hours
if($report = $cache->loadData()) {
$report = json_decode($report, true);
$report['time'] = time();
$report['count']++;
} else {
$report = array(
'error' => $code,
'time' => time(),
'count' => 1,
);
}
if ($report = $cache->loadData()) {
$report = json_decode($report, true);
$report['time'] = time();
$report['count']++;
} else {
$report = [
'error' => $code,
'time' => time(),
'count' => 1,
];
}
$cache->saveData(json_encode($report));
$cache->saveData(json_encode($report));
return $report['count'];
return $report['count'];
}

View file

@ -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
*/
/**
@ -25,27 +26,29 @@
* @todo Check if this implementation is still necessary, because simplehtmldom
* already removes some of the tags (search for `remove_noise` in simple_html_dom.php).
*/
function sanitize($html,
$tags_to_remove = array('script', 'iframe', 'input', 'form'),
$attributes_to_keep = array('title', 'href', 'src'),
$text_to_keep = array()){
function sanitize(
$html,
$tags_to_remove = ['script', 'iframe', 'input', 'form'],
$attributes_to_keep = ['title', 'href', 'src'],
$text_to_keep = []
) {
$htmlContent = str_get_html($html);
$htmlContent = str_get_html($html);
foreach ($htmlContent->find('*') as $element) {
if (in_array($element->tag, $text_to_keep)) {
$element->outertext = $element->plaintext;
} elseif (in_array($element->tag, $tags_to_remove)) {
$element->outertext = '';
} else {
foreach ($element->getAllAttributes() as $attributeName => $attribute) {
if (!in_array($attributeName, $attributes_to_keep)) {
$element->removeAttribute($attributeName);
}
}
}
}
foreach($htmlContent->find('*') as $element) {
if(in_array($element->tag, $text_to_keep)) {
$element->outertext = $element->plaintext;
} elseif(in_array($element->tag, $tags_to_remove)) {
$element->outertext = '';
} else {
foreach($element->getAllAttributes() as $attributeName => $attribute) {
if(!in_array($attributeName, $attributes_to_keep))
$element->removeAttribute($attributeName);
}
}
}
return $htmlContent;
return $htmlContent;
}
/**
@ -74,23 +77,18 @@ function sanitize($html,
* @param string $htmlContent The HTML content
* @return string The HTML content with all ocurrences replaced
*/
function backgroundToImg($htmlContent) {
function backgroundToImg($htmlContent)
{
$regex = '/background-image[ ]{0,}:[ ]{0,}url\([\'"]{0,}(.*?)[\'"]{0,}\)/';
$htmlContent = str_get_html($htmlContent);
$regex = '/background-image[ ]{0,}:[ ]{0,}url\([\'"]{0,}(.*?)[\'"]{0,}\)/';
$htmlContent = str_get_html($htmlContent);
foreach($htmlContent->find('*') as $element) {
if(preg_match($regex, $element->style, $matches) > 0) {
$element->outertext = '<img style="display:block;" src="' . $matches[1] . '" />';
}
}
return $htmlContent;
foreach ($htmlContent->find('*') as $element) {
if (preg_match($regex, $element->style, $matches) > 0) {
$element->outertext = '<img style="display:block;" src="' . $matches[1] . '" />';
}
}
return $htmlContent;
}
/**
@ -104,26 +102,27 @@ function backgroundToImg($htmlContent) {
* @param string $server Fully qualified URL to the page containing relative links
* @return object Content with fixed URLs.
*/
function defaultLinkTo($content, $server){
$string_convert = false;
if (is_string($content)) {
$string_convert = true;
$content = str_get_html($content);
}
function defaultLinkTo($content, $server)
{
$string_convert = false;
if (is_string($content)) {
$string_convert = true;
$content = str_get_html($content);
}
foreach($content->find('img') as $image) {
$image->src = urljoin($server, $image->src);
}
foreach ($content->find('img') as $image) {
$image->src = urljoin($server, $image->src);
}
foreach($content->find('a') as $anchor) {
$anchor->href = urljoin($server, $anchor->href);
}
foreach ($content->find('a') as $anchor) {
$anchor->href = urljoin($server, $anchor->href);
}
if ($string_convert) {
$content = $content->outertext;
}
if ($string_convert) {
$content = $content->outertext;
}
return $content;
return $content;
}
/**
@ -135,12 +134,13 @@ function defaultLinkTo($content, $server){
* @return string|bool Extracted string, e.g. `John Doe`, or false if the
* delimiters were not found.
*/
function extractFromDelimiters($string, $start, $end) {
if (strpos($string, $start) !== false) {
$section_retrieved = substr($string, strpos($string, $start) + strlen($start));
$section_retrieved = substr($section_retrieved, 0, strpos($section_retrieved, $end));
return $section_retrieved;
} return false;
function extractFromDelimiters($string, $start, $end)
{
if (strpos($string, $start) !== false) {
$section_retrieved = substr($string, strpos($string, $start) + strlen($start));
$section_retrieved = substr($section_retrieved, 0, strpos($section_retrieved, $end));
return $section_retrieved;
} return false;
}
/**
@ -151,13 +151,14 @@ function extractFromDelimiters($string, $start, $end) {
* @param string $end End delimiter, e.g. `</script>`
* @return string Cleaned string, e.g. `foobar`
*/
function stripWithDelimiters($string, $start, $end) {
while(strpos($string, $start) !== false) {
$section_to_remove = substr($string, strpos($string, $start));
$section_to_remove = substr($section_to_remove, 0, strpos($section_to_remove, $end) + strlen($end));
$string = str_replace($section_to_remove, '', $string);
}
return $string;
function stripWithDelimiters($string, $start, $end)
{
while (strpos($string, $start) !== false) {
$section_to_remove = substr($string, strpos($string, $start));
$section_to_remove = substr($section_to_remove, 0, strpos($section_to_remove, $end) + strlen($end));
$string = str_replace($section_to_remove, '', $string);
}
return $string;
}
/**
@ -170,28 +171,29 @@ function stripWithDelimiters($string, $start, $end) {
*
* @todo This function needs more documentation to make it maintainable.
*/
function stripRecursiveHTMLSection($string, $tag_name, $tag_start){
$open_tag = '<' . $tag_name;
$close_tag = '</' . $tag_name . '>';
$close_tag_length = strlen($close_tag);
if(strpos($tag_start, $open_tag) === 0) {
while(strpos($string, $tag_start) !== false) {
$max_recursion = 100;
$section_to_remove = null;
$section_start = strpos($string, $tag_start);
$search_offset = $section_start;
do {
$max_recursion--;
$section_end = strpos($string, $close_tag, $search_offset);
$search_offset = $section_end + $close_tag_length;
$section_to_remove = substr($string, $section_start, $section_end - $section_start + $close_tag_length);
$open_tag_count = substr_count($section_to_remove, $open_tag);
$close_tag_count = substr_count($section_to_remove, $close_tag);
} while ($open_tag_count > $close_tag_count && $max_recursion > 0);
$string = str_replace($section_to_remove, '', $string);
}
}
return $string;
function stripRecursiveHTMLSection($string, $tag_name, $tag_start)
{
$open_tag = '<' . $tag_name;
$close_tag = '</' . $tag_name . '>';
$close_tag_length = strlen($close_tag);
if (strpos($tag_start, $open_tag) === 0) {
while (strpos($string, $tag_start) !== false) {
$max_recursion = 100;
$section_to_remove = null;
$section_start = strpos($string, $tag_start);
$search_offset = $section_start;
do {
$max_recursion--;
$section_end = strpos($string, $close_tag, $search_offset);
$search_offset = $section_end + $close_tag_length;
$section_to_remove = substr($string, $section_start, $section_end - $section_start + $close_tag_length);
$open_tag_count = substr_count($section_to_remove, $open_tag);
$close_tag_count = substr_count($section_to_remove, $close_tag);
} while ($open_tag_count > $close_tag_count && $max_recursion > 0);
$string = str_replace($section_to_remove, '', $string);
}
}
return $string;
}
/**
@ -202,8 +204,8 @@ function stripRecursiveHTMLSection($string, $tag_name, $tag_start){
* @param string $string Input string in Markdown format
* @return string output string in HTML format
*/
function markdownToHtml($string) {
$Parsedown = new Parsedown();
return $Parsedown->text($string);
function markdownToHtml($string)
{
$Parsedown = new Parsedown();
return $Parsedown->text($string);
}

View file

@ -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
*/
// based on https://github.com/laravel/framework/blob/8.x/src/Illuminate/Support/Str.php
@ -34,19 +35,22 @@
// THE SOFTWARE.
if (!function_exists('str_starts_with')) {
function str_starts_with($haystack, $needle) {
return (string)$needle !== '' && strncmp($haystack, $needle, strlen($needle)) === 0;
}
function str_starts_with($haystack, $needle)
{
return (string)$needle !== '' && strncmp($haystack, $needle, strlen($needle)) === 0;
}
}
if (!function_exists('str_ends_with')) {
function str_ends_with($haystack, $needle) {
return $needle !== '' && substr($haystack, -strlen($needle)) === (string)$needle;
}
function str_ends_with($haystack, $needle)
{
return $needle !== '' && substr($haystack, -strlen($needle)) === (string)$needle;
}
}
if (!function_exists('str_contains')) {
function str_contains($haystack, $needle) {
return $needle !== '' && mb_strpos($haystack, $needle) !== false;
}
function str_contains($haystack, $needle)
{
return $needle !== '' && mb_strpos($haystack, $needle) !== false;
}
}

View file

@ -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
*/
/** Path to the root folder of RSS-Bridge (where index.php is located) */
@ -64,19 +65,19 @@ require_once PATH_LIB_VENDOR . 'php-urljoin/src/urljoin.php';
require_once PATH_LIB_VENDOR . 'simplehtmldom/simple_html_dom.php';
spl_autoload_register(function ($className) {
$folders = [
__DIR__ . '/../actions/',
__DIR__ . '/../bridges/',
__DIR__ . '/../caches/',
__DIR__ . '/../formats/',
__DIR__ . '/../lib/',
];
foreach ($folders as $folder) {
$file = $folder . $className . '.php';
if (is_file($file)) {
require $file;
}
}
$folders = [
__DIR__ . '/../actions/',
__DIR__ . '/../bridges/',
__DIR__ . '/../caches/',
__DIR__ . '/../formats/',
__DIR__ . '/../lib/',
];
foreach ($folders as $folder) {
$file = $folder . $className . '.php';
if (is_file($file)) {
require $file;
}
}
});
Configuration::verifyInstallation();