diff --git a/.gitignore b/.gitignore index a848b5f8..d970fed9 100644 --- a/.gitignore +++ b/.gitignore @@ -236,3 +236,7 @@ config.ini.php #Builder .buildconfig + +#Auth +.htaccess +.htpasswd diff --git a/.travis.yml b/.travis.yml index 55210788..841ac5db 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,41 +4,43 @@ language: php install: - composer global require dealerdirect/phpcodesniffer-composer-installer; - composer global require phpcompatibility/php-compatibility; - # Use PHPUnit 6 for unit tests (stable), requires PHP 7 - - if [[ $TRAVIS_PHP_VERSION == "7.0" ]]; then - composer global require phpunit/phpunit ^6; - fi - # Use latest PHPUnit on nightly to detect breaking changes - - if [[ $TRAVIS_PHP_VERSION == "nightly" ]]; then - composer global require phpunit/phpunit; + - if [[ "$PHPUNIT" ]]; then + composer global require phpunit/phpunit ^$PHPUNIT; fi script: - phpenv rehash # Run PHP_CodeSniffer on all versions - ~/.config/composer/vendor/bin/phpcs . --standard=phpcs.xml --warning-severity=0 --extensions=php -p; - # Check PHP compatibility for the lowest supported version - - if [[ $TRAVIS_PHP_VERSION == "5.6" ]]; then + # Check PHP compatibility for the lowest and highest supported version + - if [[ $TRAVIS_PHP_VERSION == "5.6" || $TRAVIS_PHP_VERSION == "7.3" ]]; then ~/.config/composer/vendor/bin/phpcs . --standard=phpcompatibility.xml --extensions=php -p; fi - # Run unit tests (stable) - - if [[ $TRAVIS_PHP_VERSION == "7.0" ]]; then - phpunit --configuration=phpunit.xml --include-path=lib/; - fi - # Run unit tests (latest/nightly) - # Check PHP compatibility for all versions, starting at the lowest supported version in order to detect breaking changes - - if [[ $TRAVIS_PHP_VERSION == "nightly" ]]; then - phpunit --configuration=phpunit.xml --include-path=lib/; - ~/.config/composer/vendor/bin/phpcs . --standard=PHPCompatibility --extensions=php -p --runtime-set testVersion 5.6-; + # Run unit tests on highest major version + - if [[ ${TRAVIS_PHP_VERSION:0:1} == "7" ]]; then + ~/.config/composer/vendor/bin/phpunit --configuration=phpunit.xml --include-path=lib/; fi +php: + - 7.3 + +env: + - PHPUNIT=6 + - PHPUNIT=7 + - PHPUNIT=8 + matrix: fast_finish: true include: - php: 5.6 + env: PHPUNIT= - php: 7.0 - - php: nightly + - php: 7.1 + - php: 7.2 allow_failures: - - php: nightly + - php: 7.3 + env: PHPUNIT=7 + - php: 7.3 + env: PHPUNIT=8 diff --git a/README.md b/README.md index 4e2c7b1c..7ed74d81 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,7 @@ RSS-Bridge requires PHP 5.6 or higher with following extensions enabled: - [`simplexml`](https://secure.php.net/manual/en/book.simplexml.php) - [`curl`](https://secure.php.net/manual/en/book.curl.php) - [`json`](https://secure.php.net/manual/en/book.json.php) + - [`sqlite3`](http://php.net/manual/en/book.sqlite3.php) (only when using SQLiteCache) Find more information on our [Wiki](https://github.com/rss-bridge/rss-bridge/wiki) @@ -111,41 +112,16 @@ https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8 --> * [16mhz](https://github.com/16mhz) + * [adamchainz](https://github.com/adamchainz) * [Ahiles3005](https://github.com/Ahiles3005) * [Albirew](https://github.com/Albirew) + * [aledeg](https://github.com/aledeg) + * [alexAubin](https://github.com/alexAubin) * [AmauryCarrade](https://github.com/AmauryCarrade) * [AntoineTurmel](https://github.com/AntoineTurmel) * [ArthurHoaro](https://github.com/ArthurHoaro) * [Astalaseven](https://github.com/Astalaseven) * [Astyan-42](https://github.com/Astyan-42) - * [Daiyousei](https://github.com/Daiyousei) - * [Djuuu](https://github.com/Djuuu) - * [Draeli](https://github.com/Draeli) - * [EtienneM](https://github.com/EtienneM) - * [Frenzie](https://github.com/Frenzie) - * [Ginko-Aloe](https://github.com/Ginko-Aloe) - * [Glandos](https://github.com/Glandos) - * [GregThib](https://github.com/GregThib) - * [Grummfy](https://github.com/Grummfy) - * [JackNUMBER](https://github.com/JackNUMBER) - * [JeremyRand](https://github.com/JeremyRand) - * [Jocker666z](https://github.com/Jocker666z) - * [LogMANOriginal](https://github.com/LogMANOriginal) - * [MonsieurPoutounours](https://github.com/MonsieurPoutounours) - * [Nono-m0le](https://github.com/Nono-m0le) - * [ORelio](https://github.com/ORelio) - * [PaulVayssiere](https://github.com/PaulVayssiere) - * [Piranhaplant](https://github.com/Piranhaplant) - * [Riduidel](https://github.com/Riduidel) - * [Roliga](https://github.com/Roliga) - * [Strubbl](https://github.com/Strubbl) - * [TheRadialActive](https://github.com/TheRadialActive) - * [TwizzyDizzy](https://github.com/TwizzyDizzy) - * [WalterBarrett](https://github.com/WalterBarrett) - * [ZeNairolf](https://github.com/ZeNairolf) - * [adamchainz](https://github.com/adamchainz) - * [aledeg](https://github.com/aledeg) - * [alexAubin](https://github.com/alexAubin) * [az5he6ch](https://github.com/az5he6ch) * [b1nj](https://github.com/b1nj) * [benasse](https://github.com/benasse) @@ -156,21 +132,37 @@ https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8 * [corenting](https://github.com/corenting) * [couraudt](https://github.com/couraudt) * [da2x](https://github.com/da2x) + * [Daiyousei](https://github.com/Daiyousei) * [disk0x](https://github.com/disk0x) - * [eMerzh](https://github.com/eMerzh) + * [Djuuu](https://github.com/Djuuu) + * [Draeli](https://github.com/Draeli) * [em92](https://github.com/em92) + * [eMerzh](https://github.com/eMerzh) + * [EtienneM](https://github.com/EtienneM) * [fluffy-critter](https://github.com/fluffy-critter) + * [Frenzie](https://github.com/Frenzie) * [fulmeek](https://github.com/fulmeek) + * [Ginko-Aloe](https://github.com/Ginko-Aloe) + * [Glandos](https://github.com/Glandos) + * [GregThib](https://github.com/GregThib) * [griffaurel](https://github.com/griffaurel) + * [Grummfy](https://github.com/Grummfy) * [hunhejj](https://github.com/hunhejj) * [j0k3r](https://github.com/j0k3r) + * [JackNUMBER](https://github.com/JackNUMBER) * [jdigilio](https://github.com/jdigilio) + * [JeremyRand](https://github.com/JeremyRand) + * [Jocker666z](https://github.com/Jocker666z) + * [klimplant](https://github.com/klimplant) * [kranack](https://github.com/kranack) * [kraoc](https://github.com/kraoc) + * [l1n](https://github.com/l1n) * [laBecasse](https://github.com/laBecasse) * [lagaisse](https://github.com/lagaisse) * [lalannev](https://github.com/lalannev) * [ldidry](https://github.com/ldidry) + * [Limero](https://github.com/Limero) + * [LogMANOriginal](https://github.com/LogMANOriginal) * [lorenzos](https://github.com/lorenzos) * [m0zes](https://github.com/m0zes) * [matthewseal](https://github.com/matthewseal) @@ -180,28 +172,40 @@ https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8 * [metaMMA](https://github.com/metaMMA) * [mickael-bertrand](https://github.com/mickael-bertrand) * [mitsukarenai](https://github.com/mitsukarenai) + * [MonsieurPoutounours](https://github.com/MonsieurPoutounours) * [mr-flibble](https://github.com/mr-flibble) * [mro](https://github.com/mro) * [mxmehl](https://github.com/mxmehl) * [nel50n](https://github.com/nel50n) * [niawag](https://github.com/niawag) + * [Nono-m0le](https://github.com/Nono-m0le) + * [ORelio](https://github.com/ORelio) + * [PaulVayssiere](https://github.com/PaulVayssiere) * [pellaeon](https://github.com/pellaeon) + * [Piranhaplant](https://github.com/Piranhaplant) * [pit-fgfjiudghdf](https://github.com/pit-fgfjiudghdf) * [pitchoule](https://github.com/pitchoule) * [pmaziere](https://github.com/pmaziere) * [prysme01](https://github.com/prysme01) * [quentinus95](https://github.com/quentinus95) - * [qwertygc](https://github.com/qwertygc) * [regisenguehard](https://github.com/regisenguehard) + * [Riduidel](https://github.com/Riduidel) * [rogerdc](https://github.com/rogerdc) + * [Roliga](https://github.com/Roliga) * [sebsauvage](https://github.com/sebsauvage) + * [somini](https://github.com/somini) + * [squeek502](https://github.com/squeek502) + * [Strubbl](https://github.com/Strubbl) * [sublimz](https://github.com/sublimz) * [sysadminstory](https://github.com/sysadminstory) * [tameroski](https://github.com/tameroski) * [teromene](https://github.com/teromene) + * [TheRadialActive](https://github.com/TheRadialActive) * [triatic](https://github.com/triatic) + * [WalterBarrett](https://github.com/WalterBarrett) * [wtuuju](https://github.com/wtuuju) * [yardenac](https://github.com/yardenac) + * [ZeNairolf](https://github.com/ZeNairolf) Licenses === diff --git a/actions/DetectAction.php b/actions/DetectAction.php new file mode 100644 index 00000000..2ad79a27 --- /dev/null +++ b/actions/DetectAction.php @@ -0,0 +1,50 @@ +userData['url'] + or returnClientError('You must specify a url!'); + + $format = $this->userData['format'] + or returnClientError('You must specify a format!'); + + foreach(Bridge::getBridgeNames() as $bridgeName) { + + if(!Bridge::isWhitelisted($bridgeName)) { + continue; + } + + $bridge = Bridge::create($bridgeName); + + if($bridge === false) { + continue; + } + + $bridgeParams = $bridge->detectParameters($targetURL); + + if(is_null($bridgeParams)) { + continue; + } + + $bridgeParams['bridge'] = $bridgeName; + $bridgeParams['format'] = $format; + + header('Location: ?action=display&' . http_build_query($bridgeParams), true, 301); + die(); + + } + + returnClientError('No bridge found for given URL: ' . $targetURL); + } +} diff --git a/actions/DisplayAction.php b/actions/DisplayAction.php new file mode 100644 index 00000000..b223b757 --- /dev/null +++ b/actions/DisplayAction.php @@ -0,0 +1,234 @@ +userData) ? $this->userData['bridge'] : null; + + $format = $this->userData['format'] + or returnClientError('You must specify a format!'); + + // DEPRECATED: 'nameFormat' scheme is replaced by 'name' in format parameter values + // this is to keep compatibility until futher complete removal + if(($pos = strpos($format, 'Format')) === (strlen($format) - strlen('Format'))) { + $format = substr($format, 0, $pos); + } + + // whitelist control + if(!Bridge::isWhitelisted($bridge)) { + throw new \Exception('This bridge is not whitelisted', 401); + die; + } + + // Data retrieval + $bridge = Bridge::create($bridge); + + $noproxy = array_key_exists('_noproxy', $this->userData) + && filter_var($this->userData['_noproxy'], FILTER_VALIDATE_BOOLEAN); + + if(defined('PROXY_URL') && PROXY_BYBRIDGE && $noproxy) { + define('NOPROXY', true); + } + + // Cache timeout + $cache_timeout = -1; + if(array_key_exists('_cache_timeout', $this->userData)) { + + if(!CUSTOM_CACHE_TIMEOUT) { + unset($this->userData['_cache_timeout']); + $uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) . '?' . http_build_query($this->userData); + header('Location: ' . $uri, true, 301); + die(); + } + + $cache_timeout = filter_var($this->userData['_cache_timeout'], FILTER_VALIDATE_INT); + + } else { + $cache_timeout = $bridge->getCacheTimeout(); + } + + // Remove parameters that don't concern bridges + $bridge_params = array_diff_key( + $this->userData, + array_fill_keys( + array( + 'action', + 'bridge', + 'format', + '_noproxy', + '_cache_timeout', + '_error_time' + ), '') + ); + + // Remove parameters that don't concern caches + $cache_params = array_diff_key( + $this->userData, + array_fill_keys( + array( + 'action', + 'format', + '_noproxy', + '_cache_timeout', + '_error_time' + ), '') + ); + + // Initialize cache + $cache = Cache::create(Configuration::getConfig('cache', 'type')); + $cache->setPath(PATH_CACHE); + $cache->purgeCache(86400); // 24 hours + $cache->setParameters($cache_params); + + $items = array(); + $infos = array(); + $mtime = $cache->getTime(); + + if($mtime !== false + && (time() - $cache_timeout < $mtime) + && !Debug::isEnabled()) { // Load cached data + + // Send "Not Modified" response if client supports it + // Implementation based on https://stackoverflow.com/a/10847262 + if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) { + $stime = strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']); + + if($mtime <= $stime) { // Cached data is older or same + header('Last-Modified: ' . gmdate('D, d M Y H:i:s ', $mtime) . 'GMT', true, 304); + die(); + } + } + + $cached = $cache->loadData(); + + if(isset($cached['items']) && isset($cached['extraInfos'])) { + foreach($cached['items'] as $item) { + $items[] = new \FeedItem($item); + } + + $infos = $cached['extraInfos']; + } + + } else { // Collect new data + + try { + $bridge->setDatas($bridge_params); + $bridge->collectData(); + + $items = $bridge->getItems(); + + // Transform "legacy" items to FeedItems if necessary. + // Remove this code when support for "legacy" items ends! + if(isset($items[0]) && is_array($items[0])) { + $feedItems = array(); + + foreach($items as $item) { + $feedItems[] = new \FeedItem($item); + } + + $items = $feedItems; + } + + $infos = array( + 'name' => $bridge->getName(), + 'uri' => $bridge->getURI(), + 'icon' => $bridge->getIcon() + ); + } catch(Error $e) { + error_log($e); + + $item = new \FeedItem(); + + // Create "new" error message every 24 hours + $this->userData['_error_time'] = urlencode((int)(time() / 86400)); + + // Error 0 is a special case (i.e. "trying to get property of non-object") + if($e->getCode() === 0) { + $item->setTitle( + 'Bridge encountered an unexpected situation! (' + . $this->userData['_error_time'] + . ')' + ); + } else { + $item->setTitle( + 'Bridge returned error ' + . $e->getCode() + . '! (' + . $this->userData['_error_time'] + . ')' + ); + } + + $item->setURI( + (isset($_SERVER['REQUEST_URI']) ? parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) : '') + . '?' + . http_build_query($this->userData) + ); + + $item->setTimestamp(time()); + $item->setContent(buildBridgeException($e, $bridge)); + + $items[] = $item; + } catch(Exception $e) { + error_log($e); + + $item = new \FeedItem(); + + // Create "new" error message every 24 hours + $this->userData['_error_time'] = urlencode((int)(time() / 86400)); + + $item->setURI( + (isset($_SERVER['REQUEST_URI']) ? parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) : '') + . '?' + . http_build_query($this->userData) + ); + + $item->setTitle( + 'Bridge returned error ' + . $e->getCode() + . '! (' + . $this->userData['_error_time'] + . ')' + ); + $item->setTimestamp(time()); + $item->setContent(buildBridgeException($e, $bridge)); + + $items[] = $item; + } + + // Store data in cache + $cache->saveData(array( + 'items' => array_map(function($i){ return $i->toArray(); }, $items), + 'extraInfos' => $infos + )); + + } + + // Data transformation + try { + $format = Format::create($format); + $format->setItems($items); + $format->setExtraInfos($infos); + $format->setLastModified($cache->getTime()); + $format->display(); + } catch(Error $e) { + error_log($e); + header('Content-Type: text/html', true, $e->getCode()); + die(buildTransformException($e, $bridge)); + } catch(Exception $e) { + error_log($e); + header('Content-Type: text/html', true, $e->getCode()); + die(buildTransformException($e, $bridge)); + } + } +} diff --git a/actions/ListAction.php b/actions/ListAction.php new file mode 100644 index 00000000..03e06119 --- /dev/null +++ b/actions/ListAction.php @@ -0,0 +1,53 @@ +bridges = array(); + $list->total = 0; + + foreach(Bridge::getBridgeNames() as $bridgeName) { + + $bridge = Bridge::create($bridgeName); + + if($bridge === false) { // Broken bridge, show as inactive + + $list->bridges[$bridgeName] = array( + 'status' => 'inactive' + ); + + continue; + + } + + $status = Bridge::isWhitelisted($bridgeName) ? 'active' : 'inactive'; + + $list->bridges[$bridgeName] = array( + 'status' => $status, + 'uri' => $bridge->getURI(), + 'name' => $bridge->getName(), + 'icon' => $bridge->getIcon(), + 'parameters' => $bridge->getParameters(), + 'maintainer' => $bridge->getMaintainer(), + 'description' => $bridge->getDescription() + ); + + } + + $list->total = count($list->bridges); + + header('Content-Type: application/json'); + echo json_encode($list, JSON_PRETTY_PRINT); + } +} diff --git a/bridges/AllocineFRBridge.php b/bridges/AllocineFRBridge.php index 50a41ec7..17da9031 100644 --- a/bridges/AllocineFRBridge.php +++ b/bridges/AllocineFRBridge.php @@ -10,7 +10,6 @@ class AllocineFRBridge extends BridgeAbstract { 'category' => array( 'name' => 'category', 'type' => 'list', - 'required' => true, 'exampleValue' => 'Faux Raccord', 'title' => 'Select your category', 'values' => array( diff --git a/bridges/AmazonBridge.php b/bridges/AmazonBridge.php index c9d4dc9c..bcd83dcc 100644 --- a/bridges/AmazonBridge.php +++ b/bridges/AmazonBridge.php @@ -16,7 +16,6 @@ class AmazonBridge extends BridgeAbstract { 'sort' => array( 'name' => 'Sort by', 'type' => 'list', - 'required' => false, 'values' => array( 'Relevance' => 'relevanceblender', 'Price: Low to High' => 'price-asc-rank', @@ -29,7 +28,6 @@ class AmazonBridge extends BridgeAbstract { 'tld' => array( 'name' => 'Country', 'type' => 'list', - 'required' => true, 'values' => array( 'Australia' => 'com.au', 'Brazil' => 'com.br', diff --git a/bridges/AmazonPriceTrackerBridge.php b/bridges/AmazonPriceTrackerBridge.php index e31a03bb..6fa11c91 100644 --- a/bridges/AmazonPriceTrackerBridge.php +++ b/bridges/AmazonPriceTrackerBridge.php @@ -19,7 +19,6 @@ class AmazonPriceTrackerBridge extends BridgeAbstract { 'tld' => array( 'name' => 'Country', 'type' => 'list', - 'required' => true, 'values' => array( 'Australia' => 'com.au', 'Brazil' => 'com.br', diff --git a/bridges/AppleMusicBridge.php b/bridges/AppleMusicBridge.php new file mode 100644 index 00000000..5a4f40a4 --- /dev/null +++ b/bridges/AppleMusicBridge.php @@ -0,0 +1,62 @@ + [ + 'name' => 'Artist URL', + 'exampleValue' => 'https://itunes.apple.com/us/artist/dunderpatrullen/329796274', + 'required' => true, + ], + 'imgSize' => [ + 'name' => 'Image size for thumbnails (in px)', + 'type' => 'number', + 'defaultValue' => 512, + 'required' => true, + ] + ]]; + const CACHE_TIMEOUT = 21600; // 6 hours + + public function collectData() { + $url = $this->getInput('url'); + $html = getSimpleHTMLDOM($url) + or returnServerError('Could not request: ' . $url); + + $imgSize = $this->getInput('imgSize'); + + // Grab the json data from the page + $html = $html->find('script[id=shoebox-ember-data-store]', 0); + $html = strstr($html, '{'); + $html = substr($html, 0, -9); + $json = json_decode($html); + + // Loop through each object + foreach ($json->included as $obj) { + if ($obj->type === 'lockup/album') { + $this->items[] = [ + 'title' => $obj->attributes->artistName . ' - ' . $obj->attributes->name, + 'uri' => $obj->attributes->url, + 'timestamp' => $obj->attributes->releaseDate, + 'enclosures' => $obj->relationships->artwork->data->id, + ]; + } elseif ($obj->type === 'image') { + $images[$obj->id] = $obj->attributes->url; + } + } + + // Add the images to each item + foreach ($this->items as &$item) { + $item['enclosures'] = [ + str_replace('{w}x{h}bb.{f}', $imgSize . 'x0w.jpg', $images[$item['enclosures']]), + ]; + } + + // Sort the order to put the latest albums first + usort($this->items, function($a, $b){ + return $a['timestamp'] < $b['timestamp']; + }); + } +} diff --git a/bridges/AsahiShimbunAJWBridge.php b/bridges/AsahiShimbunAJWBridge.php new file mode 100644 index 00000000..0ceb0381 --- /dev/null +++ b/bridges/AsahiShimbunAJWBridge.php @@ -0,0 +1,72 @@ + array( + 'type' => 'list', + 'name' => 'Section', + 'values' => array( + 'Japan » Social Affairs' => 'japan/social', + 'Japan » People' => 'japan/people', + 'Japan » 3/11 Disaster' => 'japan/0311disaster', + 'Japan » Sci & Tech' => 'japan/sci_tech', + 'Politics' => 'politics', + 'Business' => 'business', + 'Culture » Style' => 'culture/style', + 'Culture » Movies' => 'culture/movies', + 'Culture » Manga & Anime' => 'culture/manga_anime', + 'Asia » China' => 'asia/china', + 'Asia » Korean Peninsula' => 'asia/korean_peninsula', + 'Asia » Around Asia' => 'asia/around_asia', + 'Opinion » Editorial' => 'opinion/editorial', + 'Opinion » Vox Populi' => 'opinion/vox', + ), + 'defaultValue' => 'Politics', + ) + ) + ); + + private function getSectionURI($section) { + return self::getURI() . $section . '/'; + } + + public function collectData() { + $html = getSimpleHTMLDOM($this->getSectionURI($this->getInput('section'))) + or returnServerError('Could not load content'); + + foreach($html->find('#MainInner li a') as $element) { + if ($element->parent()->class == 'HeadlineTopImage-S') { + Debug::log('Skip Headline, it is repeated below'); + continue; + } + $item = array(); + + $item['uri'] = self::BASE_URI . $element->href; + $e_lead = $element->find('span.Lead', 0); + if ($e_lead) { + $item['content'] = $e_lead->innertext; + $e_lead->outertext = ''; + } else { + $item['content'] = $element->innertext; + } + $e_date = $element->find('span.EnDate', 0); + if ($e_date) { + $item['timestamp'] = strtotime($e_date->innertext); + $e_date->outertext = ''; + } + $e_video = $element->find('span.EnVideo', 0); + if ($e_video) { + $e_video->outertext = ''; + $element->innertext = "VIDEO: $element->innertext"; + } + $item['title'] = $element->innertext; + + $this->items[] = $item; + } + } +} diff --git a/bridges/AutoJMBridge.php b/bridges/AutoJMBridge.php index 598f0431..e8490d95 100644 --- a/bridges/AutoJMBridge.php +++ b/bridges/AutoJMBridge.php @@ -3,63 +3,195 @@ class AutoJMBridge extends BridgeAbstract { const NAME = 'AutoJM'; - const URI = 'http://www.autojm.fr/'; + const URI = 'https://www.autojm.fr/'; const DESCRIPTION = 'Suivre les offres de véhicules proposés par AutoJM en fonction des critères de filtrages'; const MAINTAINER = 'sysadminstory'; const PARAMETERS = array( 'Afficher les offres de véhicules disponible en fonction des critères du site AutoJM' => array( 'url' => array( - 'name' => 'URL de la recherche', + 'name' => 'URL du modèle', 'type' => 'text', 'required' => true, 'title' => 'URL d\'une recherche avec filtre de véhicules sans le http://www.autojm.fr/', - 'exampleValue' => 'gammes/index/398?order_by=finition_asc&energie[]=3&transmission[]=2&dispo=all' + 'exampleValue' => 'achat-voitures-neuves-peugeot-nouvelle-308-5p' + ), + 'isDispo' => array( + 'name' => 'Disponibilité', + 'type' => 'list', + 'values' => array( + '-' => '', + 'En stock' => 1, + 'Sur commande' => 0 + ), + 'title' => 'Critère de disponibilité' + ), + 'energy' => array( + 'name' => 'Carburant', + 'type' => 'list', + 'values' => array( + '-' => '', + 'Diesel' => 1, + 'Essence' => 3, + 'Hybride' => 5 + ), + 'title' => 'Carburant' + ), + 'transmission' => array( + 'name' => 'Transmission', + 'type' => 'list', + 'values' => array( + '-' => '', + 'Automatique' => 1, + 'Manuelle' => 2 + ), + 'title' => 'Transmission' + ), + 'priceMin' => array( + 'name' => 'Prix minimum', + 'type' => 'number', + 'required' => false, + 'title' => 'Prix minimum du véhicule', + 'exampleValue' => '10000', + 'defaultValue' => '0' + ), + 'priceMax' => array( + 'name' => 'Prix maximum', + 'type' => 'number', + 'required' => false, + 'title' => 'Prix maximum du véhicule', + 'exampleValue' => '15000', + 'defaultValue' => '150000' ) ) ); const CACHE_TIMEOUT = 3600; public function getIcon() { - return self::URI . 'assets/images/favicon.ico'; + return self::URI . 'favicon.ico'; } - public function collectData() { - $html = getSimpleHTMLDOM(self::URI . $this->getInput('url')) - or returnServerError('Could not request AutoJM.'); - $list = $html->find('div[class*=ligne_modele]'); - foreach($list as $element) { - $image = $element->find('img[class=width-100]', 0)->src; - $serie = $element->find('div[class=serie]', 0)->find('span', 0)->plaintext; - $url = $element->find('div[class=serie]', 0)->find('a[class=btn_ligne color-black]', 0)->href; - if($element->find('div[class*=hasStock-info]', 0) != null) { - $dispo = 'Disponible'; - } else { - $dispo = 'Sur commande'; - } - $carburant = str_replace('dispo |', '', $element->find('div[class=carburant]', 0)->plaintext); - $transmission = $element->find('div[class*=bv]', 0)->plaintext; - $places = $element->find('div[class*=places]', 0)->plaintext; - $portes = $element->find('div[class*=nb_portes]', 0)->plaintext; - $carosserie = $element->find('div[class*=coloris]', 0)->plaintext; - $remise = $element->find('div[class*=remise]', 0)->plaintext; - $prix = $element->find('div[class*=prixjm]', 0)->plaintext; - - $item = array(); - $item['uri'] = $url; - $item['title'] = $serie; - $item['content'] = '

' . $serie . '

'; - $item['content'] .= ''; - - $this->items[] = $item; + public function getName() { + switch($this->queriedContext) { + case 'Afficher les offres de véhicules disponible en fonction des critères du site AutoJM': + $html = getSimpleHTMLDOMCached(self::URI . $this->getInput('url'), 86400); + $name = html_entity_decode($html->find('title', 0)->plaintext); + return $name; + break; + default: + return parent::getName(); } } + + public function collectData() { + + $model_url = self::URI . $this->getInput('url'); + + // Get the session cookies and the form token + $this->getInitialParameters($model_url); + + // Build the form + $post_data = array( + 'form[isDispo]' => $this->getInput('isDispo'), + 'form[energy]' => $this->getInput('energy'), + 'form[transmission]' => $this->getInput('transmission'), + 'form[priceMin]' => $this->getInput('priceMin'), + 'form[priceMin]' => $this->getInput('priceMin'), + 'form[_token]' => $this->token + ); + + // Set the Form request content type + $header = array( + 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8', + ); + + // Set the curl options (POST query and content, and session cookies + $curl_opts = array( + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => http_build_query($post_data), + CURLOPT_COOKIE => $this->cookies + ); + + // Get the JSON content of the form + $json = getContents($model_url, $header, $curl_opts) + or returnServerError('Could not request AutoJM.'); + + // Extract the HTML content from the JSON result + $data = json_decode($json); + $html = str_get_html($data->content); + + // Go through every finisha of the model + $list = $html->find('h2'); + foreach ($list as $finish) { + $finish_name = $finish->plaintext; + $motorizations = $finish->next_sibling()->find('li'); + foreach ($motorizations as $element) { + $image = $element->find('div[class=block-product-image]', 0)->{'data-ga-banner'}; + $serie = $element->find('span[class=model]', 0)->plaintext; + $url = self::URI . substr($element->find('a', 0)->href, 1); + if ($element->find('span[class*=block-product-nbModel]', 0) != null) { + $availability = 'En Stock'; + } else { + $availability = 'Sur commande'; + } + $discount_html = $element->find('span[class*=tag--promo]', 0); + if ($discount_html != null) { + $discount = $discount_html->plaintext; + } else { + $discount = 'inconnue'; + } + $price = $element->find('span[class=price red h1]', 0)->plaintext; + $item = array(); + $item['title'] = $finish_name . ' ' . $serie; + $item['content'] = '

' + . $finish_name . ' ' . $serie . '

'; + $item['content'] .= ''; + + // Add a fictionnal anchor to the RSS element URL, based on the item content ; + // As the URL could be identical even if the price change, some RSS reader will not show those offers as new items + $item['uri'] = $url . '#' . md5($item['content']); + + $this->items[] = $item; + } + } + } + + /** + * Gets the session cookie and the form token + * + * @param string $pageURL The URL from which to get the values + */ + private function getInitialParameters($pageURL) { + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $pageURL); + curl_setopt($ch, CURLOPT_HEADER, true); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $data = curl_exec($ch); + + // Separate the response header and the content + $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $header = substr($data, 0, $headerSize); + $content = substr($data, $headerSize); + curl_close($ch); + + // Extract the cookies from the headers + $cookies = ''; + $http_response_header = explode("\r\n", $header); + foreach ($http_response_header as $hdr) { + if (strpos($hdr, 'Set-Cookie') !== false) { + $cLine = explode(':', $hdr)[1]; + $cLine = explode(';', $cLine)[0]; + $cookies .= ';' . $cLine; + } + } + $this->cookies = trim(substr($cookies, 1)); + + // Get the token from the content + $html = str_get_html($content); + $token = $html->find('input[type=hidden][id=form__token]', 0); + $this->token = $token->value; + } } diff --git a/bridges/BakaUpdatesMangaReleasesBridge.php b/bridges/BakaUpdatesMangaReleasesBridge.php index e6da64b6..cde9be84 100644 --- a/bridges/BakaUpdatesMangaReleasesBridge.php +++ b/bridges/BakaUpdatesMangaReleasesBridge.php @@ -12,6 +12,7 @@ class BakaUpdatesMangaReleasesBridge extends BridgeAbstract { 'exampleValue' => '12345' ) )); + const LIMIT_COLS = 5; const LIMIT_ITEMS = 10; private $feedName = ''; @@ -20,21 +21,21 @@ class BakaUpdatesMangaReleasesBridge extends BridgeAbstract { $html = getSimpleHTMLDOM($this->getURI()) or returnServerError('Series not found'); - $objTitle = $html->find('td[class="text pad"]', 1); - if ($objTitle) - $this->feedName = $objTitle->plaintext; - - $itemlist = $html->find('td#main_content table table table tr'); - if (!$itemlist) + // content is an unstructured pile of divs, ugly to parse + $cols = $html->find('div#main_content div.row > div.text'); + if (!$cols) returnServerError('No releases'); - $limit = self::LIMIT_ITEMS; - foreach($itemlist as $element) { - $cols = $element->find('td[class="text pad"]'); - if (!$cols) - continue; - if ($limit <= 0) - break; + $rows = array_slice( + array_chunk($cols, self::LIMIT_COLS), 0, self::LIMIT_ITEMS + ); + + if (isset($rows[0][1])) { + $this->feedName = $this->filterHTML($rows[0][1]->plaintext); + } + + foreach($rows as $cols) { + if (count($cols) < self::LIMIT_COLS) continue; $item = array(); $title = array(); @@ -47,8 +48,8 @@ class BakaUpdatesMangaReleasesBridge extends BridgeAbstract { $objTitle = $cols[1]; if ($objTitle) { - $title[] = html_entity_decode($objTitle->plaintext); - $item['content'] .= '

Series: ' . $objTitle->innertext . '

'; + $title[] = $this->filterHTML($objTitle->plaintext); + $item['content'] .= '

Series: ' . $this->filterText($objTitle->innertext) . '

'; } $objVolume = $cols[2]; @@ -61,18 +62,15 @@ class BakaUpdatesMangaReleasesBridge extends BridgeAbstract { $objAuthor = $cols[4]; if ($objAuthor && !empty($objAuthor->plaintext)) { - $item['author'] = html_entity_decode($objAuthor->plaintext); - $item['content'] .= '

Groups: ' . $objAuthor->innertext . '

'; + $item['author'] = $this->filterHTML($objAuthor->plaintext); + $item['content'] .= '

Groups: ' . $this->filterText($objAuthor->innertext) . '

'; } - $item['title'] = implode(' ', $title); - $item['uri'] = $this->getURI() . '#' . hash('sha1', $item['title']); + $item['title'] = implode(' ', $title); + $item['uri'] = $this->getURI(); + $item['uid'] = hash('sha1', $item['title']); $this->items[] = $item; - - if(count($this->items) >= $limit) { - break; - } } } @@ -90,4 +88,12 @@ class BakaUpdatesMangaReleasesBridge extends BridgeAbstract { } return parent::getName(); } + + private function filterText($text) { + return rtrim($text, '*'); + } + + private function filterHTML($text) { + return $this->filterText(html_entity_decode($text)); + } } diff --git a/bridges/BandcampBridge.php b/bridges/BandcampBridge.php index 9c8d436e..6c75ed5e 100644 --- a/bridges/BandcampBridge.php +++ b/bridges/BandcampBridge.php @@ -13,48 +13,72 @@ class BandcampBridge extends BridgeAbstract { 'required' => true ) )); + const IMGURI = 'https://f4.bcbits.com/'; + const IMGSIZE_300PX = 23; + const IMGSIZE_700PX = 16; public function getIcon() { return 'https://s4.bcbits.com/img/bc_favicon.ico'; } public function collectData(){ - $html = getSimpleHTMLDOM($this->getURI()) - or returnServerError('No results for this query.'); + $url = self::URI . 'api/hub/1/dig_deeper'; + $data = $this->buildRequestJson(); + $header = array( + 'Content-Type: application/json', + 'Content-Length: ' . strlen($data) + ); + $opts = array( + CURLOPT_CUSTOMREQUEST => 'POST', + CURLOPT_POSTFIELDS => $data + ); + $content = getContents($url, $header, $opts) + or returnServerError('Could not complete request to: ' . $url); - foreach($html->find('li.item') as $release) { - $script = $release->find('div.art', 0)->getAttribute('onclick'); - $uri = ltrim($script, "return 'url("); - $uri = rtrim($uri, "')"); + $json = json_decode($content); - $item = array(); - $item['author'] = $release->find('div.itemsubtext', 0)->plaintext - . ' - ' - . $release->find('div.itemtext', 0)->plaintext; + if ($json->ok !== true) { + returnServerError('Invalid response'); + } - $item['title'] = $release->find('div.itemsubtext', 0)->plaintext - . ' - ' - . $release->find('div.itemtext', 0)->plaintext; + foreach ($json->items as $entry) { + $url = $entry->tralbum_url; + $artist = $entry->artist; + $title = $entry->title; + // e.g. record label is the releaser, but not the artist + $releaser = $entry->band_name !== $entry->artist ? $entry->band_name : null; - $item['content'] = '
' - . $release->find('div.itemsubtext', 0)->plaintext - . ' - ' - . $release->find('div.itemtext', 0)->plaintext; + $full_title = $artist . ' - ' . $title; + $full_artist = $artist; + if (isset($releaser)) { + $full_title .= ' (' . $releaser . ')'; + $full_artist .= ' (' . $releaser . ')'; + } + $small_img = $this->getImageUrl($entry->art_id, self::IMGSIZE_300PX); + $img = $this->getImageUrl($entry->art_id, self::IMGSIZE_700PX); - $item['id'] = $release->find('a', 0)->getAttribute('href'); - $item['uri'] = $release->find('a', 0)->getAttribute('href'); + $item = array( + 'uri' => $url, + 'author' => $full_artist, + 'title' => $full_title + ); + $item['content'] = "
$full_title"; + $item['enclosures'] = array($img); $this->items[] = $item; } } - public function getURI(){ - if(!is_null($this->getInput('tag'))) { - return self::URI . 'tag/' . urlencode($this->getInput('tag')) . '?sort_field=date'; - } + private function buildRequestJson(){ + $requestJson = array( + 'tag' => $this->getInput('tag'), + 'page' => 1, + 'sort' => 'date' + ); + return json_encode($requestJson); + } - return parent::getURI(); + private function getImageUrl($id, $size){ + return self::IMGURI . 'img/a' . $id . '_' . $size . '.jpg'; } public function getName(){ diff --git a/bridges/BingSearchBridge.php b/bridges/BingSearchBridge.php new file mode 100644 index 00000000..eb8a5fc9 --- /dev/null +++ b/bridges/BingSearchBridge.php @@ -0,0 +1,119 @@ + array( + 'category' => array( + 'name' => 'Categories', + 'type' => 'list', + 'values' => self::IMAGE_DISCOVER_CATEGORIES + ), + 'image_size' => array( + 'name' => 'Image size', + 'type' => 'list', + 'values' => array( + 'Small' => 'turl', + 'Full size' => 'imgurl' + ) + ) + ) + ); + + const IMAGE_DISCOVER_CATEGORIES = array( + 'Abstract' => 'abstract', + 'Animals' => 'animals', + 'Anime' => 'anime', + 'Architecture' => 'architecture', + 'Arts and Crafts' => 'arts-and-crafts', + 'Beauty' => 'beauty', + 'Cars and Motorcycles' => 'cars-and-motorcycles', + 'Cats' => 'cats', + 'Celebrities' => 'celebrities', + 'Comics' => 'comics', + 'DIY' => 'diy', + 'Dogs' => 'dogs', + 'Fitness' => 'fitness', + 'Food and Drink' => 'food-and-drink', + 'Funny' => 'funny', + 'Gadgets' => 'gadgets', + 'Gardening' => 'gardening', + 'Geeky' => 'geeky', + 'Hairstyles' => 'hairstyles', + 'Home Decor' => 'home-decor', + 'Marine Life' => 'marine-life', + 'Men\'s Fashion' => 'men%27s-fashion', + 'Nature' => 'nature', + 'Outdoors' => 'outdoors', + 'Parenting' => 'parenting', + 'Phone Wallpapers' => 'phone-wallpapers', + 'Photography' => 'photography', + 'Quotes' => 'quotes', + 'Recipes' => 'recipes', + 'Snow' => 'snow', + 'Tattoos' => 'tattoos', + 'Travel' => 'travel', + 'Video Games' => 'video-games', + 'Weddings' => 'weddings', + 'Women\'s Fashion' => 'women%27s-fashion', + ); + + public function getIcon() + { + return 'https://www.bing.com/sa/simg/bing_p_rr_teal_min.ico'; + } + + public function collectData() + { + $this->items = $this->imageDiscover($this->getInput('category')); + } + + public function getName() + { + if ($this->getInput('category')) { + if (self::IMAGE_DISCOVER_CATEGORIES[$this->getInput('categories')] !== null) { + $category = self::IMAGE_DISCOVER_CATEGORIES[$this->getInput('categories')]; + } else { + $category = 'Unknown'; + } + + return 'Best ' . $category . ' - Bing Image Discover'; + } + return parent::getName(); + } + + private function imageDiscover($category) + { + $html = getSimpleHTMLDOM(self::URI . '/discover/' . $category) + or returnServerError('Could not request ' . self::NAME); + $sizeKey = $this->getInput('image_size'); + + $items = []; + foreach ($html->find('a.iusc') as $element) { + $data = json_decode(htmlspecialchars_decode($element->getAttribute('m')), true); + + $item = array(); + $item['title'] = basename(rtrim($data['imgurl'], '/')); + $item['uri'] = $data['imgurl']; + $item['content'] = ' + ' . $item['title'] . ' +

Source:

'; + $item['enclosures'] = $data['imgurl']; + + $items[] = $item; + } + return $items; + } + + private function curUrl($url) + { + if (strlen($url) <= 80) { + return $url; + } + return substr($url, 0, 80) . '...'; + } +} diff --git a/bridges/BundesbankBridge.php b/bridges/BundesbankBridge.php index d21f22b6..b64a6425 100644 --- a/bridges/BundesbankBridge.php +++ b/bridges/BundesbankBridge.php @@ -17,7 +17,6 @@ class BundesbankBridge extends BridgeAbstract { self::PARAM_LANG => array( 'name' => 'Language', 'type' => 'list', - 'required' => true, 'defaultValue' => self::LANG_DE, 'values' => array( 'English' => self::LANG_EN, diff --git a/bridges/CachetBridge.php b/bridges/CachetBridge.php new file mode 100644 index 00000000..a60b8f73 --- /dev/null +++ b/bridges/CachetBridge.php @@ -0,0 +1,134 @@ + array( + 'name' => 'Cachet installation', + 'type' => 'text', + 'required' => true, + 'title' => 'The URL of the Cachet installation', + 'exampleValue' => 'https://demo.cachethq.io/', + ), 'additional_info' => array( + 'name' => 'Additional Timestamps', + 'type' => 'checkbox', + 'title' => 'Whether to include the given timestamps' + ) + ) + ); + const CACHE_TIMEOUT = 300; + + private $componentCache = []; + + public function getURI() { + return $this->getInput('host') === null ? 'https://cachethq.io/' : $this->getInput('host'); + } + + /** + * Validates the ping request to the cache API + * + * @param string $ping + * @return boolean + */ + private function validatePing($ping) { + $ping = json_decode($ping); + if ($ping === null) { + return false; + } + return $ping->data === 'Pong!'; + } + + /** + * Returns the component name of a cachat component + * + * @param integer $id + * @return string + */ + private function getComponentName($id) { + if ($id === 0) { + return ''; + } + if (array_key_exists($id, $this->componentCache)) { + return $this->componentCache[$id]; + } + + $component = getContents($this->getURI() . '/api/v1/components/' . $id); + $component = json_decode($component); + if ($component === null) { + return ''; + } + return $component->data->name; + } + + public function collectData() { + $ping = getContents(urljoin($this->getURI(), '/api/v1/ping')); + if (!$this->validatePing($ping)) { + returnClientError('Provided URI is invalid!'); + } + + $url = urljoin($this->getURI(), '/api/v1/incidents?sort=id&order=desc'); + $incidents = getContents($url); + $incidents = json_decode($incidents); + if ($incidents === null) { + returnClientError('/api/v1/incidents returned no valid json'); + } + + usort($incidents->data, function ($a, $b) { + $timeA = strtotime($a->updated_at); + $timeB = strtotime($b->updated_at); + return $timeA > $timeB ? -1 : 1; + }); + + foreach ($incidents->data as $incident) { + + if (isset($incident->permalink)) { + $permalink = $incident->permalink; + } else { + $permalink = urljoin($this->getURI(), '/incident/' . $incident->id); + } + + $title = $incident->human_status . ': ' . $incident->name; + $message = ''; + if ($this->getInput('additional_info')) { + if (isset($incident->occurred_at)) { + $message .= 'Occurred at: ' . $incident->occurred_at . "\r\n"; + } + if (isset($incident->scheduled_at)) { + $message .= 'Scheduled at: ' . $incident->scheduled_at . "\r\n"; + } + if (isset($incident->created_at)) { + $message .= 'Created at: ' . $incident->created_at . "\r\n"; + } + if (isset($incident->updated_at)) { + $message .= 'Updated at: ' . $incident->updated_at . "\r\n\r\n"; + } + } + + $message .= $incident->message; + $content = nl2br($message); + $componentName = $this->getComponentName($incident->component_id); + $uidOrig = $permalink . $incident->created_at; + $uid = hash('sha512', $uidOrig); + $timestamp = strtotime($incident->created_at); + $categories = []; + $categories[] = $incident->human_status; + if ($componentName !== '') { + $categories[] = $componentName; + } + + $item = []; + $item['uri'] = $permalink; + $item['title'] = $title; + $item['timestamp'] = $timestamp; + $item['content'] = $content; + $item['uid'] = $uid; + $item['categories'] = $categories; + + $this->items[] = $item; + } + } +} diff --git a/bridges/ComboiosDePortugalBridge.php b/bridges/ComboiosDePortugalBridge.php new file mode 100644 index 00000000..610e23b3 --- /dev/null +++ b/bridges/ComboiosDePortugalBridge.php @@ -0,0 +1,22 @@ +getURI() . '/consultar-horarios/avisos') + or returnServerError('Could not load content'); + + foreach($html->find('.warnings-table a') as $element) { + $item = array(); + + $item['title'] = $element->innertext; + $item['uri'] = self::BASE_URI . implode('/', array_map('urlencode', explode('/', $element->href))); + + $this->items[] = $item; + } + } +} diff --git a/bridges/ContainerLinuxReleasesBridge.php b/bridges/ContainerLinuxReleasesBridge.php index ae438885..d2f63256 100644 --- a/bridges/ContainerLinuxReleasesBridge.php +++ b/bridges/ContainerLinuxReleasesBridge.php @@ -15,7 +15,6 @@ class ContainerLinuxReleasesBridge extends BridgeAbstract { 'channel' => [ 'name' => 'Release Channel', 'type' => 'list', - 'required' => true, 'defaultValue' => self::STABLE, 'values' => [ 'Stable' => self::STABLE, diff --git a/bridges/DealabsBridge.php b/bridges/DealabsBridge.php index 89183edb..b64bb1dc 100644 --- a/bridges/DealabsBridge.php +++ b/bridges/DealabsBridge.php @@ -15,13 +15,11 @@ class DealabsBridge extends PepperBridgeAbstract { 'hide_expired' => array( 'name' => 'Masquer les éléments expirés', 'type' => 'checkbox', - 'required' => true ), 'hide_local' => array( 'name' => 'Masquer les deals locaux', 'type' => 'checkbox', 'title' => 'Masquer les deals en magasins physiques', - 'required' => true ), 'priceFrom' => array( 'name' => 'Prix minimum', @@ -41,7 +39,6 @@ class DealabsBridge extends PepperBridgeAbstract { 'group' => array( 'name' => 'Groupe', 'type' => 'list', - 'required' => true, 'title' => 'Groupe dont il faut afficher les deals', 'values' => array( 'Abonnements internet' => 'abonnements-internet', @@ -957,7 +954,6 @@ class DealabsBridge extends PepperBridgeAbstract { 'order' => array( 'name' => 'Trier par', 'type' => 'list', - 'required' => true, 'title' => 'Ordre de tri des deals', 'values' => array( 'Du deal le plus Hot au moins Hot' => '', @@ -1380,8 +1376,11 @@ class PepperBridgeAbstract extends BridgeAbstract { // Add the Hour and minutes $date_str .= ' 00:00'; - $date = DateTime::createFromFormat('j F Y H:i', $date_str); + // In some case, the date is not recognized : as a workaround the actual date is taken + if($date === false) { + $date = new DateTime(); + } return $date->getTimestamp(); } diff --git a/bridges/DesoutterBridge.php b/bridges/DesoutterBridge.php index 14e26c26..4a7b0a94 100644 --- a/bridges/DesoutterBridge.php +++ b/bridges/DesoutterBridge.php @@ -15,7 +15,6 @@ class DesoutterBridge extends BridgeAbstract { 'news_lang' => array( 'name' => 'Language', 'type' => 'list', - 'required' => true, 'title' => 'Select your language', 'defaultValue' => 'Corporate', 'values' => array( @@ -66,7 +65,6 @@ class DesoutterBridge extends BridgeAbstract { 'industry_lang' => array( 'name' => 'Language', 'type' => 'list', - 'required' => true, 'title' => 'Select your language', 'defaultValue' => 'Corporate', 'values' => array( @@ -117,7 +115,6 @@ class DesoutterBridge extends BridgeAbstract { 'full' => array( 'name' => 'Load full articles', 'type' => 'checkbox', - 'required' => false, 'title' => 'Enable to load the full article for each item' ) ) diff --git a/bridges/EconomistBridge.php b/bridges/EconomistBridge.php new file mode 100644 index 00000000..1256be45 --- /dev/null +++ b/bridges/EconomistBridge.php @@ -0,0 +1,63 @@ +find('article') as $element) { + + $a = $element->find('a', 0); + $href = self::URI . $a->href; + $full = getSimpleHTMLDOMCached($href); + $article = $full->find('article', 0); + + $header = $article->find('h1', 0); + $author = $article->find('span[itemprop="author"]', 0); + $time = $article->find('time[itemprop="dateCreated"]', 0); + $content = $article->find('div[itemprop="description"]', 0); + + // Remove newsletter subscription box + $newsletter = $content->find('div[class="newsletter-form__message"]', 0); + if ($newsletter) + $newsletter->outertext = ''; + + $newsletterForm = $content->find('form', 0); + if ($newsletterForm) + $newsletterForm->outertext = ''; + + // Remove next and previous article URLs at the bottom + $nextprev = $content->find('div[class="blog-post__next-previous-wrapper"]', 0); + if ($nextprev) + $nextprev->outertext = ''; + + $section = [ $article->find('h3[itemprop="articleSection"]', 0)->plaintext ]; + + $item = array(); + $item['title'] = $header->find('span', 0)->innertext . ': ' + . $header->find('span', 1)->innertext; + + $item['uri'] = $href; + $item['timestamp'] = strtotime($time->datetime); + $item['author'] = $author->innertext; + $item['categories'] = $section; + + $item['content'] = '' . $content->innertext; + + $this->items[] = $item; + + if (count($this->items) >= 10) + break; + } + } +} diff --git a/bridges/ElloBridge.php b/bridges/ElloBridge.php index 22f5d309..45d33a53 100644 --- a/bridges/ElloBridge.php +++ b/bridges/ElloBridge.php @@ -120,7 +120,7 @@ class ElloBridge extends BridgeAbstract { } private function getAPIKey() { - $cache = Cache::create('FileCache'); + $cache = Cache::create(Configuration::getConfig('cache', 'type')); $cache->setPath(PATH_CACHE); $cache->setParameters(['key']); $key = $cache->loadData(); diff --git a/bridges/ExtremeDownloadBridge.php b/bridges/ExtremeDownloadBridge.php index 52729971..acdf6301 100644 --- a/bridges/ExtremeDownloadBridge.php +++ b/bridges/ExtremeDownloadBridge.php @@ -15,7 +15,6 @@ class ExtremeDownloadBridge extends BridgeAbstract { 'filter' => array( 'name' => 'Type de contenu', 'type' => 'list', - 'required' => true, 'title' => 'Type de contenu à suivre : Téléchargement, Streaming ou les deux', 'values' => array( 'Streaming et Téléchargement' => 'both', diff --git a/bridges/FDroidBridge.php b/bridges/FDroidBridge.php index b606cec1..7f54735a 100644 --- a/bridges/FDroidBridge.php +++ b/bridges/FDroidBridge.php @@ -11,7 +11,6 @@ class FDroidBridge extends BridgeAbstract { 'u' => array( 'name' => 'Widget selection', 'type' => 'list', - 'required' => true, 'values' => array( 'Latest added apps' => 'added', 'Latest updated apps' => 'updated' @@ -29,14 +28,14 @@ class FDroidBridge extends BridgeAbstract { or returnServerError('Could not request F-Droid.'); // targetting the corresponding widget based on user selection - // "updated" is the 4th widget on the page, "added" is the 5th + // "updated" is the 5th widget on the page, "added" is the 6th switch($this->getInput('u')) { case 'updated': - $html_widget = $html->find('div.sidebar-widget', 4); + $html_widget = $html->find('div.sidebar-widget', 5); break; default: - $html_widget = $html->find('div.sidebar-widget', 5); + $html_widget = $html->find('div.sidebar-widget', 6); break; } diff --git a/bridges/FacebookBridge.php b/bridges/FacebookBridge.php index 7b617057..c0901072 100644 --- a/bridges/FacebookBridge.php +++ b/bridges/FacebookBridge.php @@ -219,8 +219,7 @@ class FacebookBridge extends BridgeAbstract { $ogtitle = $html->find('meta[property="og:title"]', 0) or returnServerError('Unable to find group title!'); - return htmlspecialchars_decode($ogtitle->content, ENT_QUOTES); - + return html_entity_decode($ogtitle->content, ENT_QUOTES); } private function extractGroupURI($post) { diff --git a/bridges/FeedExpanderExampleBridge.php b/bridges/FeedExpanderExampleBridge.php index 537a6352..f5b83071 100644 --- a/bridges/FeedExpanderExampleBridge.php +++ b/bridges/FeedExpanderExampleBridge.php @@ -11,7 +11,6 @@ class FeedExpanderExampleBridge extends FeedExpander { 'version' => array( 'name' => 'Version', 'type' => 'list', - 'required' => true, 'title' => 'Select your feed format/version', 'defaultValue' => 'RSS 2.0', 'values' => array( diff --git a/bridges/FindACrewBridge.php b/bridges/FindACrewBridge.php index c245c84a..1dac775a 100644 --- a/bridges/FindACrewBridge.php +++ b/bridges/FindACrewBridge.php @@ -62,10 +62,10 @@ class FindACrewBridge extends BridgeAbstract { foreach ($annonces as $annonce) { $item = array(); - $img = parent::getURI() . $annonce->find('.css_LstPic img', 0)->getAttribute('src'); - $item['title'] = $annonce->find('.css_LstCtrls span', 0)->plaintext; - $item['uri'] = parent::getURI() . $annonce->find('.css_PnlCtrls a', 0)->href; - $content = $annonce->find('.css_LstDtl div', 2)->innertext; + $img = parent::getURI() . $annonce->find('.lst-pic img', 0)->getAttribute('src'); + $item['title'] = $annonce->find('.lst-tags span', 0)->plaintext; + $item['uri'] = parent::getURI() . $annonce->find('.lst-ctrls a', 0)->href; + $content = $annonce->find('.lst-dtl', 0)->innertext; $item['content'] = "
$content"; $item['enclosures'] = array($img); $item['categories'] = array($annonce->find('.css_AccLocCur', 0)->plaintext); diff --git a/bridges/GBAtempBridge.php b/bridges/GBAtempBridge.php index 9383be76..48a7f851 100644 --- a/bridges/GBAtempBridge.php +++ b/bridges/GBAtempBridge.php @@ -10,7 +10,6 @@ class GBAtempBridge extends BridgeAbstract { 'type' => array( 'name' => 'Type', 'type' => 'list', - 'required' => true, 'values' => array( 'News' => 'N', 'Reviews' => 'R', diff --git a/bridges/GithubSearchBridge.php b/bridges/GithubSearchBridge.php index fe8a721a..fd90934c 100644 --- a/bridges/GithubSearchBridge.php +++ b/bridges/GithubSearchBridge.php @@ -24,7 +24,7 @@ class GithubSearchBridge extends BridgeAbstract { $html = getSimpleHTMLDOM($url) or returnServerError('Error while downloading the website content'); - foreach($html->find('div.repo-list-item') as $element) { + foreach($html->find('li.repo-list-item') as $element) { $item = array(); $uri = $element->find('h3 a', 0)->href; diff --git a/bridges/GlowficBridge.php b/bridges/GlowficBridge.php new file mode 100644 index 00000000..e8975a79 --- /dev/null +++ b/bridges/GlowficBridge.php @@ -0,0 +1,88 @@ + array(), + 'Thread' => array( + 'post_id' => array( + 'name' => 'Post ID', + 'title' => 'https://www.glowfic.com/posts/', + 'type' => 'number' + ), + 'start_page' => array( + 'name' => 'Start Page', + 'title' => 'To start from an offset page', + 'type' => 'number' + ) + ) + ); + + public function collectData() { + $url = $this->getAPIURI(); + $metadata = get_headers( $url . '/replies', true ) or returnClientError('Post did not return reply headers.'); + $metadata['Last-Page'] = ceil( $metadata['Total'] / $metadata['Per-Page'] ); + if(!is_null($this->getInput('start_page')) && + $this->getInput('start_page') < 1 && $metadata['Last-Page'] - $this->getInput('start_page') > 0) { + $first_page = $metadata['Last-Page'] - $this->getInput('start_page'); + } else if(!is_null($this->getInput('start_page')) && $this->getInput('start_page') <= $metadata['Last-Page']) { + $first_page = $this->getInput('start_page'); + } else { + $first_page = 1; + } + for ($page_offset = $first_page; $page_offset <= $metadata['Last-Page']; $page_offset++) { + $jsonContents = getContents($url . '/replies?page=' . $page_offset ) or + returnClientError('Could not retrieve replies for page ' . $page_offset . '.'); + $replies = json_decode($jsonContents); + foreach ($replies as $reply) { + $item = array(); + + $item['content'] = $reply->{'content'}; + $item['uri'] = $this->getURI() . '?page=' . $page_offset . '#reply-' . $reply->{'id'}; + if ($reply->{'icon'}) { + $item['enclosures'] = array($reply->{'icon'}->{'url'}); + } + $item['author'] = $reply->{'character'}->{'screenname'} . ' (' . $reply->{'character'}->{'name'} . ')'; + $item['timestamp'] = date('r', strtotime($reply->{'created_at'})); + $item['title'] = 'Tag by ' . $reply->{'user'}->{'username'} . ' updated at ' . $reply->{'updated_at'}; + $this->items[] = $item; + } + } + } + + private function getAPIURI() { + $url = parent::getURI() . '/api/v1/posts/' . $this->getInput('post_id'); + return $url; + } + + public function getURI() { + $url = parent::getURI() . '/posts/' . $this->getInput('post_id'); + return $url; + } + + private function getPost() { + $url = $this->getAPIURI(); + $jsonPost = getContents( $url ) or returnClientError('Could not retrieve post metadata.'); + $post = json_decode($jsonPost); + return $post; + } + + public function getName(){ + if(!is_null($this->getInput('post_id'))) { + $post = $this->getPost(); + return $post->{'subject'} . ' - ' . parent::getName(); + } + return parent::getName(); + } + + public function getDescription(){ + if(!is_null($this->getInput('post_id'))) { + $post = $this->getPost(); + return $post->{'content'}; + } + return parent::getName(); + } +} diff --git a/bridges/HDWallpapersBridge.php b/bridges/HDWallpapersBridge.php index cea6e344..f1579e02 100644 --- a/bridges/HDWallpapersBridge.php +++ b/bridges/HDWallpapersBridge.php @@ -16,13 +16,13 @@ class HDWallpapersBridge extends BridgeAbstract { ), 'r' => array( 'name' => 'resolution', - 'defaultValue' => '1920x1200', - 'exampleValue' => '1920x1200, 1680x1050,…' + 'defaultValue' => 'HD', + 'exampleValue' => 'HD, 1920x1200, 1680x1050,…' ) )); public function collectData(){ - $category = $this->category; + $category = $this->getInput('c'); if(strrpos($category, 'wallpapers') !== strlen($category) - strlen('wallpapers')) { $category .= '-desktop-wallpapers'; } @@ -45,13 +45,12 @@ class HDWallpapersBridge extends BridgeAbstract { $thumbnail = $element->find('img', 0); $item = array(); - // http://www.hdwallpapers.in/download/yosemite_reflections-1680x1050.jpg $item['uri'] = self::URI . '/download' . str_replace('wallpapers.html', $this->getInput('r') . '.jpg', $element->href); $item['timestamp'] = time(); - $item['title'] = $element->find('p', 0)->text(); + $item['title'] = $element->find('em1', 0)->text(); $item['content'] = $item['title'] . '
'; + $item['enclosures'] = array($item['uri']); $this->items[] = $item; $num++; diff --git a/bridges/HeiseBridge.php b/bridges/HeiseBridge.php new file mode 100644 index 00000000..1d9d8025 --- /dev/null +++ b/bridges/HeiseBridge.php @@ -0,0 +1,75 @@ + array( + 'name' => 'Category', + 'type' => 'list', + 'values' => array( + 'Alle News' + => 'https://www.heise.de/newsticker/heise-atom.xml', + 'Top-News' + => 'https://www.heise.de/newsticker/heise-top-atom.xml', + 'Internet-Störungen' + => 'https://www.heise.de/netze/netzwerk-tools/imonitor-internet-stoerungen/feed/aktuelle-meldungen/', + 'Alle News von heise Developer' + => 'https://www.heise.de/developer/rss/news-atom.xml' + ) + ), + 'limit' => array( + 'name' => 'Limit', + 'type' => 'number', + 'required' => false, + 'title' => 'Specify number of full articles to return', + 'defaultValue' => 5 + ) + )); + const LIMIT = 5; + + public function collectData() { + $this->collectExpandableDatas( + $this->getInput('category'), + $this->getInput('limit') ?: static::LIMIT + ); + } + + protected function parseItem($feedItem) { + $item = parent::parseItem($feedItem); + $uri = $item['uri']; + + do { + $article = getSimpleHTMLDOMCached($uri) + or returnServerError('Could not open article: ' . $uri); + + $article = defaultLinkTo($article, $uri); + $item = $this->addArticleToItem($item, $article); + + if($next = $article->find('.pagination a[rel="next"]', 0)) + $uri = $next->href; + } while ($next); + + return $item; + } + + private function addArticleToItem($item, $article) { + if($author = $article->find('[itemprop="author"]', 0)) + $item['author'] = $author->plaintext; + + $content = $article->find('div[class*="article-content"]', 0); + + foreach($content->find('p, h3, ul, table, pre, img') as $element) { + $item['content'] .= $element; + } + + foreach($content->find('img') as $img) { + $item['enclosures'][] = $img->src; + } + + return $item; + } +} diff --git a/bridges/HotUKDealsBridge.php b/bridges/HotUKDealsBridge.php index 8c1b3bd1..ed2d28af 100644 --- a/bridges/HotUKDealsBridge.php +++ b/bridges/HotUKDealsBridge.php @@ -17,13 +17,11 @@ class HotUKDealsBridge extends PepperBridgeAbstract { 'hide_expired' => array( 'name' => 'Hide expired deals', 'type' => 'checkbox', - 'required' => true ), 'hide_local' => array( 'name' => 'Hide local deals', 'type' => 'checkbox', 'title' => 'Hide deals in physical store', - 'required' => true ), 'priceFrom' => array( 'name' => 'Minimal Price', @@ -43,7 +41,6 @@ class HotUKDealsBridge extends PepperBridgeAbstract { 'group' => array( 'name' => 'Group', 'type' => 'list', - 'required' => true, 'title' => 'Group whose deals must be displayed', 'values' => array( '2DS' => '2ds', @@ -1317,7 +1314,6 @@ class HotUKDealsBridge extends PepperBridgeAbstract { 'order' => array( 'name' => 'Order by', 'type' => 'list', - 'required' => true, 'title' => 'Sort order of deals', 'values' => array( 'From the most to the least hot deal' => '-hot', diff --git a/bridges/InstagramBridge.php b/bridges/InstagramBridge.php index 317fb12e..1bf86072 100644 --- a/bridges/InstagramBridge.php +++ b/bridges/InstagramBridge.php @@ -89,7 +89,7 @@ class InstagramBridge extends BridgeAbstract { if (isset($media->edge_media_to_caption->edges[0]->node->text)) { $textContent = $media->edge_media_to_caption->edges[0]->node->text; } else { - $textContent = basename($media->display_url); + $textContent = '(no text)'; } $item['title'] = ($media->is_video ? '▶ ' : '') . trim($textContent); @@ -103,10 +103,11 @@ class InstagramBridge extends BridgeAbstract { $item['content'] = $data[0]; $item['enclosures'] = $data[1]; } else { + $mediaURI = self::URI . 'p/' . $media->shortcode . '/media?size=l'; $item['content'] = ''; - $item['content'] .= '' . $item['title'] . ''; + $item['content'] .= '' . $item['title'] . ''; $item['content'] .= '

' . nl2br(htmlentities($textContent)); - $item['enclosures'] = array($media->display_url); + $item['enclosures'] = array($mediaURI); } $item['timestamp'] = $media->taken_at_timestamp; diff --git a/bridges/InstructablesBridge.php b/bridges/InstructablesBridge.php index 05f1a91c..7e7d5428 100644 --- a/bridges/InstructablesBridge.php +++ b/bridges/InstructablesBridge.php @@ -21,7 +21,6 @@ class InstructablesBridge extends BridgeAbstract { 'category' => array( 'name' => 'Category', 'type' => 'list', - 'required' => true, 'values' => array( 'Play' => array( 'All' => '/play/', @@ -240,7 +239,6 @@ class InstructablesBridge extends BridgeAbstract { 'filter' => array( 'name' => 'Filter', 'type' => 'list', - 'required' => true, 'values' => array( 'Featured' => ' ', 'Recent' => 'recent/', diff --git a/bridges/IvooxBridge.php b/bridges/IvooxBridge.php new file mode 100644 index 00000000..3cdf74bc --- /dev/null +++ b/bridges/IvooxBridge.php @@ -0,0 +1,128 @@ + array( + 's' => array( + 'name' => 'keyword', + 'exampleValue' => 'test' + ) + ) + ); + + private function ivBridgeAddItem( + $episode_link, + $podcast_name, + $episode_title, + $author_name, + $episode_description, + $publication_date, + $episode_duration) { + $item = array(); + $item['title'] = htmlspecialchars_decode($podcast_name . ': ' . $episode_title); + $item['author'] = $author_name; + $item['timestamp'] = $publication_date; + $item['uri'] = $episode_link; + $item['content'] = '' . $podcast_name . ': ' . $episode_title + . '
Duration: ' . $episode_duration + . '
Description:
' . $episode_description; + $this->items[] = $item; + } + + private function ivBridgeParseHtmlListing($html) { + $limit = 4; + $count = 0; + + foreach($html->find('div.flip-container') as $flipper) { + $linkcount = 0; + if(!empty($flipper->find( 'div.modulo-type-banner' ))) { + // ad + continue; + } + + if($count < $limit) { + foreach($flipper->find('div.header-modulo') as $element) { + foreach($element->find('a') as $link) { + if ($linkcount == 0) { + $episode_link = $link->href; + $episode_title = $link->title; + } elseif ($linkcount == 1) { + $author_link = $link->href; + $author_name = $link->title; + } elseif ($linkcount == 2) { + $podcast_link = $link->href; + $podcast_name = $link->title; + } + + $linkcount++; + } + } + + $episode_description = $flipper->find('button.btn-link', 0)->getAttribute('data-content'); + $episode_duration = $flipper->find('p.time', 0)->innertext; + $publication_date = $flipper->find('li.date', 0)->getAttribute('title'); + + // alternative date_parse_from_format + // or DateTime::createFromFormat('G:i - d \d\e M \d\e Y', $publication); + // TODO: month name translations, due function doesn't support locale + + $a = strptime($publication_date, '%H:%M - %d de %b. de %Y'); // obsolete function, uses c libraries + $publication_date = mktime(0, 0, 0, $a['tm_mon'] + 1, $a['tm_mday'], $a['tm_year'] + 1900); + + $this->ivBridgeAddItem( + $episode_link, + $podcast_name, + $episode_title, + $author_name, + $episode_description, + $publication_date, + $episode_duration + ); + $count++; + } + } + } + + public function collectData() { + + // store locale, change to spanish + $originalLocales = explode(';', setlocale(LC_ALL, 0)); + setlocale(LC_ALL, 'es_ES.utf8'); + + $xml = ''; + $html = ''; + $url_feed = ''; + if($this->getInput('s')) { /* Search modes */ + $this->request = str_replace(' ', '-', $this->getInput('s')); + $url_feed = self::URI . urlencode($this->request) . '_sb_f_1.html?o=uploaddate'; + } else { + returnClientError('Not valid mode at IvooxBridge'); + } + + $dom = getSimpleHTMLDOM($url_feed) + or returnServerError('Could not request ' . $url_feed); + $this->ivBridgeParseHtmlListing($dom); + + // restore locale + + foreach($originalLocales as $localeSetting) { + if(strpos($localeSetting, '=') !== false) { + list($category, $locale) = explode('=', $localeSetting); + } else { + $category = LC_ALL; + $locale = $localeSetting; + } + + setlocale($category, $locale); + } + } +} diff --git a/bridges/JustETFBridge.php b/bridges/JustETFBridge.php index 85318b8e..8d5b3d5a 100644 --- a/bridges/JustETFBridge.php +++ b/bridges/JustETFBridge.php @@ -34,7 +34,6 @@ class JustETFBridge extends BridgeAbstract { 'global' => array( 'lang' => array( 'name' => 'Language', - 'required' => true, 'type' => 'list', 'values' => array( 'Englisch' => 'en', diff --git a/bridges/KununuBridge.php b/bridges/KununuBridge.php index 2f4bf0b2..cb13ca35 100644 --- a/bridges/KununuBridge.php +++ b/bridges/KununuBridge.php @@ -11,7 +11,6 @@ class KununuBridge extends BridgeAbstract { 'site' => array( 'name' => 'Site', 'type' => 'list', - 'required' => true, 'title' => 'Select your site', 'values' => array( 'Austria' => 'at', @@ -23,7 +22,6 @@ class KununuBridge extends BridgeAbstract { 'full' => array( 'name' => 'Load full article', 'type' => 'checkbox', - 'required' => false, 'exampleValue' => 'checked', 'title' => 'Activate to load full article' ) diff --git a/bridges/LeMondeInformatiqueBridge.php b/bridges/LeMondeInformatiqueBridge.php index 09bcf6a3..45aa6075 100644 --- a/bridges/LeMondeInformatiqueBridge.php +++ b/bridges/LeMondeInformatiqueBridge.php @@ -20,12 +20,13 @@ class LeMondeInformatiqueBridge extends FeedExpander { str_replace( '/grande/', '/petite/', - $article_html->find('.article-image', 0)->find('img', 0)->src + $article_html->find('.article-image > img, figure > img', 0)->src ) ); //No response header sets the encoding, explicit conversion is needed or subsequent xml_encode() will fail - $item['content'] = utf8_encode($this->cleanArticle($article_html->find('div.col-primary', 0)->innertext)); + $content_node = $article_html->find('div.col-primary, div.col-sm-9', 0); + $item['content'] = utf8_encode($this->cleanArticle($content_node->innertext)); $item['author'] = utf8_encode($article_html->find('div.author-infos', 0)->find('b', 0)->plaintext); return $item; diff --git a/bridges/MangareaderBridge.php b/bridges/MangareaderBridge.php index 91537069..9ecb0feb 100644 --- a/bridges/MangareaderBridge.php +++ b/bridges/MangareaderBridge.php @@ -13,7 +13,6 @@ class MangareaderBridge extends BridgeAbstract { 'category' => array( 'name' => 'Category', 'type' => 'list', - 'required' => true, 'values' => array( 'All' => 'all', 'Action' => 'action', diff --git a/bridges/MozillaBugTrackerBridge.php b/bridges/MozillaBugTrackerBridge.php new file mode 100644 index 00000000..356bedcf --- /dev/null +++ b/bridges/MozillaBugTrackerBridge.php @@ -0,0 +1,153 @@ + array( + 'id' => array( + 'name' => 'Bug tracking ID', + 'type' => 'number', + 'required' => true, + 'title' => 'Insert bug tracking ID', + 'exampleValue' => 121241 + ), + 'limit' => array( + 'name' => 'Number of comments to return', + 'type' => 'number', + 'required' => false, + 'title' => 'Specify number of comments to return', + 'defaultValue' => -1 + ), + 'sorting' => array( + 'name' => 'Sorting', + 'type' => 'list', + 'required' => false, + 'title' => 'Defines the sorting order of the comments returned', + 'defaultValue' => 'of', + 'values' => array( + 'Oldest first' => 'of', + 'Latest first' => 'lf' + ) + ) + ) + ); + + private $bugid = ''; + private $bugdesc = ''; + + public function getIcon() { + return self::URI . '/extensions/BMO/web/images/favicon.ico'; + } + + public function collectData(){ + $limit = $this->getInput('limit'); + $sorting = $this->getInput('sorting'); + + // We use the print preview page for simplicity + $html = getSimpleHTMLDOMCached($this->getURI() . '&format=multiple', + 86400, + null, + null, + true, + true, + DEFAULT_TARGET_CHARSET, + false, // Do NOT remove line breaks + DEFAULT_BR_TEXT, + DEFAULT_SPAN_TEXT); + + if($html === false) + returnServerError('Failed to load page!'); + + // Store header information into private members + $this->bugid = $html->find('#bugzilla-body', 0)->find('a', 0)->innertext; + $this->bugdesc = $html->find('table.bugfields', 0)->find('tr', 0)->find('td', 0)->innertext; + + // Get and limit comments + $comments = $html->find('.bz_comment_table div.bz_comment'); + + if($limit > 0 && count($comments) > $limit) { + $comments = array_slice($comments, count($comments) - $limit, $limit); + } + + // Order comments + switch($sorting) { + case 'lf': $comments = array_reverse($comments, true); + case 'of': + default: // Nothing to do, keep original order + } + + foreach($comments as $comment) { + $comment = $this->inlineStyles($comment); + + $item = array(); + $item['uri'] = $this->getURI() . '#' . $comment->id; + $item['author'] = $comment->find('span.bz_comment_user', 0)->innertext; + $item['title'] = $comment->find('span.bz_comment_number', 0)->find('a', 0)->innertext; + $item['timestamp'] = strtotime($comment->find('span.bz_comment_time', 0)->innertext); + $item['content'] = $comment->find('pre.bz_comment_text', 0)->innertext; + + // Fix line breaks (they use LF) + $item['content'] = str_replace("\n", '
', $item['content']); + + // Fix relative URIs + $item['content'] = $this->replaceRelativeURI($item['content']); + + $this->items[] = $item; + } + + } + + public function getURI(){ + switch($this->queriedContext) { + case 'Bug comments': + return parent::getURI() + . '/show_bug.cgi?id=' + . $this->getInput('id'); + break; + default: return parent::getURI(); + } + } + + public function getName(){ + switch($this->queriedContext) { + case 'Bug comments': + return 'Bug ' + . $this->bugid + . ' tracker for ' + . $this->bugdesc + . ' - ' + . parent::getName(); + break; + default: return parent::getName(); + } + } + + /** + * Replaces all relative URIs with absolute ones + * + * @param string $content The source string + * @return string Returns the source string with all relative URIs replaced + * by absolute ones. + */ + private function replaceRelativeURI($content){ + return preg_replace('/href="(?!http)/', 'href="' . self::URI . '/', $content); + } + + /** + * Adds styles as attributes to tags with known classes + * + * @param object $html A simplehtmldom object + * @return object Returns the original object with styles added as + * attributes. + */ + private function inlineStyles($html){ + foreach($html->find('.bz_obsolete') as $element) { + $element->style = 'text-decoration:line-through;'; + } + + return $html; + } +} diff --git a/bridges/MozillaSecurityBridge.php b/bridges/MozillaSecurityBridge.php index 0b951a14..52672f56 100644 --- a/bridges/MozillaSecurityBridge.php +++ b/bridges/MozillaSecurityBridge.php @@ -21,7 +21,8 @@ class MozillaSecurityBridge extends BridgeAbstract { $item['title'] = $element->innertext; $item['timestamp'] = strtotime($element->innertext); $item['content'] = $element->next_sibling()->innertext; - $item['uri'] = self::URI; + $item['uri'] = self::URI . '?' . $item['timestamp']; + $item['uid'] = self::URI . '?' . $item['timestamp']; $this->items[] = $item; } } diff --git a/bridges/MydealsBridge.php b/bridges/MydealsBridge.php index aa5bb489..603f4e07 100644 --- a/bridges/MydealsBridge.php +++ b/bridges/MydealsBridge.php @@ -17,13 +17,11 @@ class MydealsBridge extends PepperBridgeAbstract { 'hide_expired' => array( 'name' => 'Abgelaufenes ausblenden', 'type' => 'checkbox', - 'required' => true ), 'hide_local' => array( 'name' => 'Lokales ausblenden', 'type' => 'checkbox', 'title' => 'Deals im physischen Geschäft ausblenden', - 'required' => true ), 'priceFrom' => array( 'name' => 'Minimaler Preis', @@ -43,7 +41,6 @@ class MydealsBridge extends PepperBridgeAbstract { 'group' => array( 'name' => 'Gruppen', 'type' => 'list', - 'required' => true, 'title' => 'Gruppe, deren Deals angezeigt werden müssen', 'values' => array( 'Elektronik' => 'elektronik', @@ -66,7 +63,6 @@ class MydealsBridge extends PepperBridgeAbstract { 'order' => array( 'name' => 'sortieren nach', 'type' => 'list', - 'required' => true, 'title' => 'Sortierung der deals', 'values' => array( 'Vom heißesten zum kältesten Deal' => '', diff --git a/bridges/NineGagBridge.php b/bridges/NineGagBridge.php index f5261356..e726c732 100644 --- a/bridges/NineGagBridge.php +++ b/bridges/NineGagBridge.php @@ -11,7 +11,6 @@ class NineGagBridge extends BridgeAbstract { 'd' => array( 'name' => 'Section', 'type' => 'list', - 'required' => true, 'values' => array( 'Hot' => 'hot', 'Trending' => 'trending', @@ -28,7 +27,6 @@ class NineGagBridge extends BridgeAbstract { 'g' => array( 'name' => 'Section', 'type' => 'list', - 'required' => true, 'values' => array( 'Animals' => 'cute', 'Anime & Manga' => 'anime-manga', @@ -88,7 +86,6 @@ class NineGagBridge extends BridgeAbstract { 't' => array( 'name' => 'Type', 'type' => 'list', - 'required' => true, 'values' => array( 'Hot' => 'hot', 'Fresh' => 'fresh', diff --git a/bridges/NotAlwaysBridge.php b/bridges/NotAlwaysBridge.php index b2f4c35a..c7758c32 100644 --- a/bridges/NotAlwaysBridge.php +++ b/bridges/NotAlwaysBridge.php @@ -21,8 +21,7 @@ class NotAlwaysBridge extends BridgeAbstract { 'Friendly' => 'friendly', 'Hopeless' => 'hopeless', 'Unfiltered' => 'unfiltered' - ), - 'required' => true + ) ) )); diff --git a/bridges/OnVaSortirBridge.php b/bridges/OnVaSortirBridge.php index ee6baf19..ed1dcb65 100644 --- a/bridges/OnVaSortirBridge.php +++ b/bridges/OnVaSortirBridge.php @@ -9,7 +9,6 @@ class OnVaSortirBridge extends FeedExpander { 'city' => array( 'name' => 'City', 'type' => 'list', - 'required' => true, 'values' => array( 'Agen' => 'Agen', 'Ajaccio' => 'Ajaccio', diff --git a/bridges/OneFortuneADayBridge.php b/bridges/OneFortuneADayBridge.php index ed0b5ec6..62fe767d 100644 --- a/bridges/OneFortuneADayBridge.php +++ b/bridges/OneFortuneADayBridge.php @@ -35,25 +35,33 @@ class OneFortuneADayBridge extends BridgeAbstract { '23:00' => 23, ), 'defaultValue' => 5 + ), + 'lucky' => array( + 'name' => 'Lucky number (optional)', + 'type' => 'text' ) )); const LIMIT_ITEMS = 7; const DAY_SECS = 86400; + public function getDescription(){ + return self::DESCRIPTION . '
Set a lucky number to get your personal quotes, like ' . mt_rand(); + } + public function collectData() { $time = gmmktime((int)$this->getInput('time'), 0, 0); if ($time > time()) $time -= self::DAY_SECS; for ($i = self::LIMIT_ITEMS; $i > 0; --$i) { - $seed = date('Ymd', $time); + $seed = gmdate('Ymd', $time) . $this->getInput('lucky'); $quote = $this->getQuote($seed); $item['title'] = strftime('%A, %x', $time); $item['content'] = htmlentities($quote, ENT_QUOTES, 'UTF-8'); $item['timestamp'] = $time; - $item['uri'] = 'urn:sha1:' . hash('sha1', $seed); + $item['uid'] = hash('sha1', $seed); $this->items[] = $item; diff --git a/bridges/OpenClassroomsBridge.php b/bridges/OpenClassroomsBridge.php index 5f0daca4..4db7bc19 100644 --- a/bridges/OpenClassroomsBridge.php +++ b/bridges/OpenClassroomsBridge.php @@ -11,7 +11,6 @@ class OpenClassroomsBridge extends BridgeAbstract { 'u' => array( 'name' => 'Catégorie', 'type' => 'list', - 'required' => true, 'values' => array( 'Arts & Culture' => 'arts', 'Code' => 'code', diff --git a/bridges/RadioMelodieBridge.php b/bridges/RadioMelodieBridge.php index ca033fd9..03a14a43 100644 --- a/bridges/RadioMelodieBridge.php +++ b/bridges/RadioMelodieBridge.php @@ -1,34 +1,88 @@ find('div[class=actuitem]'); + $list = $html->find('div[class=actu_col1]', 0)->children();; foreach($list as $element) { - $item = array(); + if($element->tag == 'a') { + $articleURL = self::URI . $element->href; + $article = getSimpleHTMLDOM($articleURL); - // Get picture URL - $pictureHTML = $element->find('div[class=picture]'); - preg_match( - '/background-image:url\((.*)\);/', - $pictureHTML[0]->getAttribute('style'), - $pictures); - $pictureURL = $pictures[1]; + // Initialise arrays + $item = array(); + $audio = array(); + $picture = array(); - $item['enclosures'] = array($pictureURL); - $item['uri'] = self::URI . $element->parent()->href; - $item['title'] = $element->find('h3', 0)->plaintext; - $item['content'] = $element->find('p', 0)->plaintext . '
'; - $this->items[] = $item; + // Get the Main picture URL + $picture[] = $this->rewriteImage($article->find('img[id=picturearticle]', 0)->src); + $audioHTML = $article->find('div[class=sm2-playlist-wrapper]'); + + // Remove the audio placeholder under the Audio player with an