1
0
mirror of https://github.com/RSS-Bridge/rss-bridge.git synced 2025-08-16 13:34:11 +02:00

Compare commits

..

1 Commits

Author SHA1 Message Date
Teromene
db25a68072 Revert "[CommonDreamBridge] Promote to secure bridge (fix #777) (#909)"
This reverts commit 8c97953211.
2018-11-05 17:32:28 +01:00
58 changed files with 962 additions and 3159 deletions

View File

@@ -4,4 +4,5 @@ DEBUG
Dockerfile
whitelist.txt
phpcs.xml
CHANGELOG.md
CONTRIBUTING.md

14
.gitattributes vendored
View File

@@ -20,17 +20,3 @@
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain
# Ignore files in git archive (i.e. GitHub release builds)
Dockerfile export-ignore
.travis.yml export-ignore
.github/ export-ignore
.gitattributes export-ignore
.gitignore export-ignore
.dockerignore export-ignore
scalingo.json export-ignore
phpunit.xml export-ignore
phpcs.xml export-ignore
phpcompatibility.xml export-ignore
tests/ export-ignore
cache/.gitkeep export-ignore

View File

@@ -1,61 +0,0 @@
---
name: Bridge request template
about: Use this template for requesting a new bridge
---
# Bridge request
<!--
This is a bridge request. Start by adding a descriptive title (i.e. `Bridge request for GitHub`). Use the "Preview" button to see a preview of your request. Make sure your request is complete before submitting!
Notice: This comment is only visible to you while you work on your request. Please do not remove any of the lines in the template (you may add your own outside the "<!--" and "- ->" lines!)
-->
## General information
<!--
Please describe what you expect from the bridge. Whenever possible provide sample links and screenshots (you can just paste them here) to express your expectations and help others understand your request. If possible, mark relevant areas in your screenshot. Use the following questions for reference:
-->
- _Host URI for the bridge_ (i.e. `https://github.com`):
- Which information would you like to see?
- How should the information be displayed/formatted?
- Which of the following parameters do you expect?
- [X] Title
- [X] URI (link to the original article)
- [ ] Author
- [ ] Timestamp
- [X] Content (the content of the article)
- [ ] Enclosures (pictures, videos, etc...)
- [ ] Categories (categories, tags, etc...)
## Options
<!--Select options from the list below. Add your own option if one is missing:-->
- [ ] Limit number of returned items
- _Default limit_: 5
- [ ] Load full articles
- _Cache articles_ (articles are stored in a local cache on first request): yes
- _Cache timeout_ (max = 24 hours): 24 hours
- [X] Balance requests (RSS-Bridge uses cached versions to reduce bandwith usage)
- _Timeout_ (default = 5 minutes, max = 24 hours): 5 minutes
<!--Be aware that some options might not be available for your specific request due to technical limitations!-->
<!--
## Additional notes
Keep in mind that opening a request does not guarantee the bridge being implemented! That depends entirely on the interest and time of others to make the bridge for you.
You can also implement your own bridge (with support of the community if needed). Find more information in the [RSS-Bridge Wiki](https://github.com/RSS-Bridge/rss-bridge/wiki/For-developers) developer section.
-->

View File

@@ -1,36 +1,28 @@
dist: trusty
sudo: false
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 == "hhvm" ]]; then
composer global require squizlabs/PHP_CodeSniffer;
else
pear channel-update pear.php.net;
pear install PHP_CodeSniffer;
fi
- 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;
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
~/.config/composer/vendor/bin/phpcs . --standard=phpcompatibility.xml --warning-severity=0 --extensions=php -p;
- if [[ $TRAVIS_PHP_VERSION == "hhvm" ]]; then
/home/travis/.composer/vendor/bin/phpcs . --standard=phpcs.xml --warning-severity=0 --extensions=php -p;
else
phpcs . --standard=phpcs.xml --warning-severity=0 --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 --warning-severity=0 --extensions=php -p --runtime-set testVersion 5.6-;
fi
matrix:
fast_finish: true
@@ -38,7 +30,9 @@ matrix:
include:
- php: 5.6
- php: 7.0
- php: hhvm
- php: nightly
allow_failures:
- php: hhvm
- php: nightly

263
CHANGELOG.md Normal file
View File

@@ -0,0 +1,263 @@
rss-bridge Changelog
===
RSS-Bridge 2017-08-19
==
## General changes
* whitelist: Do case-insensitive whitelist matching
* [FeedExpander] Fix Serialization of 'SimpleXMLElement' is not allowed
* [FeedExpander] Remove whitespace from source content
* [index] Add GET parameter 'q' for search queries
- **Example**: You can now add `&q=Twitter` to load into the search field
* [index] Check permissions for cache folder and whitelist file
* [index] Show bridge options when loading with URL fragment
- **Example**: You can now add `#bridge-Twitter` to load the card with all
parameters visible
* [style] Center search cursor and hide placeholder
* [validation] Fix error on undefined optional numeric value
## Modified bridges
* [DanbooruBridge] Allow descendant classes to override tag collection
* [DribbbleBridge] Add dribble bridge listing last dribble popular shots (#558)
* [FacebookBridge] Fix &amp; in URLs
* [GelbooruBridge] Fix bridge not getting tags correctly
* [GoComicsBridge] Fix for page structure changes (#568)
* [LeBonCoinBridge] Fix bridge is marked executable
* [LWNprevBridge] Fix everchanging url
* [YoutubeBridge] Fix error on certain keywords
* [YoutubeBridge] Fix issues loading playlists
## Removed bridges
* VineBridge
RSS-Bridge 2017-08-03
==
## Important changes
* RSS-Bridge now has [contribution guidelines](CONTRIBUTING.md)
* [phpcs rules](phpcs.xml) follow the [contribution guidelines](CONTRIBUTING.md)
## General changes
* Added a search bar to make searching for bridges easier
* Added user friendly error page for when a bridge fails
* Added caching of extraInfos (name, uri)
* Added an indicator to warn for bridges using HTTP instead of HTTPS
* Various bug fixes and improvements
## Modified bridges
* AllocineFRBridge] Update Faux Raccord link
* [DanbooruBridge] Fix broken URI
* [DuckDuckGoBridge] Disable DuckDuckGo redirects so that the links returned are correct.
* [FacebookBridge] Add option to hide posts with facebook videos
* [FacebookBridge] Add requester languages to HTTP header
* [FacebookBridge] Handle summary posts
* [FacebookBridge] Replace 'novideo' with 'media_type'
* [FilterBridge] Initial implementation of basic title permit and block
* [FlickrTagBridge] Fix and improve bridge by using the FlickrExploreBridge approach
* [GooglePlusPostBridge] Autofix user names
* [GooglePlusPostBridge] Fix bridge implementation
* [GooglePlusPostBridge] Fix content loading
* [InstagramBridge] Add option to filter for videos and pictures
* [LWNprevBridge] full rewrite
* [MangareaderBridge] Fix double forward slashes
* [NasaApodBridge] Use HTTPS instead of HTTP
* [PinterestBridge] Fix checkbox not working
* [PinterestBridge] Fix implementation after DOM changes
* [RTBFBridge] Update URI
* [SexactuBridge] Fix URI and timestamp
* [SexactuBridge] Use most modern version of bridge api and cached pages (#504)
* [ShanaprojectBridge] Don't throw error if timestamp is missing
* [TwitterBridge] Add option to hide retweets
* [TwitterBridge] Avoid empty content caused by new login policy
* [TwitterBridge] Fix double slashes in URI
* [TwitterBridge] Fix missing spaces
* [TwitterBridge] Fix title includes anchors in plaintext format
* [TwitterBridge] ignore promoted tweets
* [TwitterBridge] Optimize returned image sizes
* [TwitterBridge] Show quotes and pictures
* [WebfailBridge] Properly handle gifs (DOM changed)
* [YoutubeBridge] Improve readability of feed contents
* [YoutubeBridge] Improve URL handling in video descriptions
## New bridges
* AmazonBridge
* DiceBridge
* EtsyBridge
* FB2Bridge
* FilterBridge
* FlickrBridge
* GithubSearchBridge
* GoComicsBridge
* KATBridge
* KernelBugTrackerBridge
* MixCloudBridge
* MoinMoinBridge
* RainbowSixSiegeBridge
* SteamBridge
* TheTVDBBridge
* Torrent9Bridge
* UsbekEtRicaBridge
* WikiLeaksBridge
* WordPressPluginUpdateBridge
Alpha 0.2
===
## Important changes
* RSS-Bridge has been [UNLICENSED](UNLICENSE)
* RSS-Bridge is now a community-managed project on [GitHub](https://github.com/rss-bridge/rss-bridge)
* RSS-Bridge now has a [Wiki](https://github.com/rss-bridge/rss-bridge/wiki)
* RSS-Bridge now supports [Travis-CI](https://travis-ci.org)
## General changes
* Added [CHANGELOG](CHANGELOG.md) (this file)
* Added [PHP Simple HTML DOM Parser](http://simplehtmldom.sourceforge.net) to [vendor](vendor/simplehtmldom/)
* Added cache purging function (cache will be force-purged after 24 hours or as defined by bridge)
* Added new format [MrssFormat](formats/MrssFormat.php)
* Added parameter `author` - for display of the feed author name - to all formats
* Added new abstraction of the BridgeInterface:
- [FeedExpander](https://github.com/RSS-Bridge/rss-bridge/wiki/Bridge-API)
* Added optional support for proxy usage on each individual bridge
* Added support for [custom bridge parameter](https://github.com/RSS-Bridge/rss-bridge/wiki/BridgeAbstract#format-specifications) (text, number, list, checkbox)
* Changed design of the welcome screen
* Changed design of HtmlFormat
* Changed behavior of debug mode:
- Enable debug mode by placing a file called "DEBUG" in the root folder
- Debug mode automatically disables cache file loading
* Changed implementation of bridges - see [Wiki](https://github.com/rss-bridge/rss-bridge/wiki)
- Changed comment-style metadata to constants
- Added support for multiple utilizations per bridge
- Changed the parameter loading algorithm to be loaded by RSS-Bridge core
* Improved checks for PHP version, configuration and extensions
* Many bug fixes
## Modified Bridges
* FlickrExploreBridge
* GoogleSearchBridge
* TwitterBridge
## New Bridges
* ABCTabsBridge
* AcrimedBridge
* AllocineFRBridge
* AnimeUltimeBridge
* Arte7Bridge
* AskfmBridge
* BandcampBridge
* BastaBridge
* BlaguesDeMerdeBridge
* BooruprojectBridge
* CADBridge
* CNETBridge
* CastorusBridge
* CollegeDeFranceBridge
* CommonDreamsBridge
* CopieDoubleBridge
* CourrierInternationalBridge
* CpasbienBridge
* CryptomeBridge
* DailymotionBridge
* DanbooruBridge
* DansTonChatBridge
* DauphineLibereBridge
* DemoBridge
* DeveloppezDotComBridge
* DilbertBridge
* DollbooruBridge
* DuckDuckGoBridge
* EZTVBridge
* EliteDangerousGalnetBridge
* ElsevierBridge
* EstCeQuonMetEnProdBridge
* FacebookBridge
* FierPandaBridge
* FlickrTagBridge
* FootitoBridge
* FourchanBridge
* FuturaSciencesBridge
* GBAtempBridge
* GelbooruBridge
* GiphyBridge
* GithubIssueBridge
* GizmodoBridge
* GooglePlusPostBridge
* HDWallpapersBridge
* HentaiHavenBridge
* IdenticaBridge
* InstagramBridge
* IsoHuntBridge
* JapanExpoBridge
* KonachanBridge
* KoreusBridge
* KununuBridge
* LWNprevBridge
* LeBonCoinBridge
* LegifranceJOBridge
* LeMondeInformatiqueBridge
* LesJoiesDuCodeBridge
* LichessBridge
* LinkedInCompanyBridge
* LolibooruBridge
* MangareaderBridge
* MilbooruBridge
* MoebooruBridge
* MondeDiploBridge
* MsnMondeBridge
* MspabooruBridge
* NasaApodBridge
* NeuviemeArtBridge
* NextInpactBridge
* NextgovBridge
* NiceMatinBridge
* NovelUpdatesBridge
* OpenClassroomsBridge
* ParuVenduImmoBridge
* PickyWallpapersBridge
* PinterestBridge
* PlanetLibreBridge
* RTBFBridge
* ReadComicsBridge
* Releases3DSBridge
* ReporterreBridge
* Rue89Bridge
* Rule34Bridge
* Rule34pahealBridge
* SafebooruBridge
* SakugabooruBridge
* ScmbBridge
* ScoopItBridge
* SensCritiqueBridge
* SexactuBridge
* ShanaprojectBridge
* Shimmie2Bridge
* SoundcloudBridge
* StripeAPIChangeLogBridge
* SuperbWallpapersBridge
* T411Bridge
* TagBoardBridge
* TbibBridge
* TheCodingLoveBridge
* TheHackerNewsBridge
* ThePirateBayBridge
* UnsplashBridge
* ViadeoCompanyBridge
* VineBridge
* VkBridge
* WallpaperStopBridge
* WebfailBridge
* WeLiveSecurityBridge
* WhydBridge
* WikipediaBridge
* WordPressBridge
* WorldOfTanksBridge
* XbooruBridge
* YandereBridge
* YoutubeBridge
* ZDNetBridge
Alpha 0.1
===
* First tagged version.
* Includes refactoring.
* Unstable.

View File

@@ -43,7 +43,5 @@ Note that all pull-requests must pass all tests before they can be merged.
* [Use PascalCase for class names](https://github.com/RSS-Bridge/rss-bridge/wiki/Classes#use-pascalcase-for-class-names)
* [Do not use final statements inside final classes](https://github.com/RSS-Bridge/rss-bridge/wiki/Classes#do-not-use-final-statements-inside-final-classes)
* [Do not override methods to call their parent](https://github.com/RSS-Bridge/rss-bridge/wiki/Classes#do-not-override-methods-to-call-their-parent)
* [abstract and final declarations MUST precede the visibility declaration](https://github.com/RSS-Bridge/rss-bridge/wiki/Classes#abstract-and-final-declarations-must-precede-the-visibility-declaration)
* [static declaration MUST come after the visibility declaration](https://github.com/RSS-Bridge/rss-bridge/wiki/Classes#static-declaration-must-come-after-the-visibility-declaration)
* [Casting](https://github.com/RSS-Bridge/rss-bridge/wiki/Casting)
* [Do not add spaces when casting](https://github.com/RSS-Bridge/rss-bridge/wiki/Casting#do-not-add-spaces-when-casting)

172
README.md
View File

@@ -110,96 +110,88 @@ Use this script to generate the list automatically (using the GitHub API):
https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
-->
* [16mhz](https://github.com/16mhz)
* [Ahiles3005](https://github.com/Ahiles3005)
* [Albirew](https://github.com/Albirew)
* [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)
* [captn3m0](https://github.com/captn3m0)
* [chemel](https://github.com/chemel)
* [ckiw](https://github.com/ckiw)
* [cnlpete](https://github.com/cnlpete)
* [corenting](https://github.com/corenting)
* [couraudt](https://github.com/couraudt)
* [da2x](https://github.com/da2x)
* [disk0x](https://github.com/disk0x)
* [eMerzh](https://github.com/eMerzh)
* [em92](https://github.com/em92)
* [fluffy-critter](https://github.com/fluffy-critter)
* [griffaurel](https://github.com/griffaurel)
* [hunhejj](https://github.com/hunhejj)
* [j0k3r](https://github.com/j0k3r)
* [jdigilio](https://github.com/jdigilio)
* [kranack](https://github.com/kranack)
* [kraoc](https://github.com/kraoc)
* [laBecasse](https://github.com/laBecasse)
* [lagaisse](https://github.com/lagaisse)
* [lalannev](https://github.com/lalannev)
* [ldidry](https://github.com/ldidry)
* [m0zes](https://github.com/m0zes)
* [matthewseal](https://github.com/matthewseal)
* [mcbyte-it](https://github.com/mcbyte-it)
* [mdemoss](https://github.com/mdemoss)
* [melangue](https://github.com/melangue)
* [metaMMA](https://github.com/metaMMA)
* [mickael-bertrand](https://github.com/mickael-bertrand)
* [mitsukarenai](https://github.com/mitsukarenai)
* [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)
* [pellaeon](https://github.com/pellaeon)
* [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)
* [rogerdc](https://github.com/rogerdc)
* [sebsauvage](https://github.com/sebsauvage)
* [sublimz](https://github.com/sublimz)
* [sysadminstory](https://github.com/sysadminstory)
* [tameroski](https://github.com/tameroski)
* [teromene](https://github.com/teromene)
* [triatic](https://github.com/triatic)
* [wtuuju](https://github.com/wtuuju)
* [yardenac](https://github.com/yardenac)
* [16mhz](https://api.github.com/users/16mhz)
* [Ahiles3005](https://api.github.com/users/Ahiles3005)
* [Albirew](https://api.github.com/users/Albirew)
* [AmauryCarrade](https://api.github.com/users/AmauryCarrade)
* [ArthurHoaro](https://api.github.com/users/ArthurHoaro)
* [Astalaseven](https://api.github.com/users/Astalaseven)
* [Astyan-42](https://api.github.com/users/Astyan-42)
* [Daiyousei](https://api.github.com/users/Daiyousei)
* [Djuuu](https://api.github.com/users/Djuuu)
* [Draeli](https://api.github.com/users/Draeli)
* [EtienneM](https://api.github.com/users/EtienneM)
* [Frenzie](https://api.github.com/users/Frenzie)
* [Ginko-Aloe](https://api.github.com/users/Ginko-Aloe)
* [Glandos](https://api.github.com/users/Glandos)
* [GregThib](https://api.github.com/users/GregThib)
* [Grummfy](https://api.github.com/users/Grummfy)
* [JackNUMBER](https://api.github.com/users/JackNUMBER)
* [JeremyRand](https://api.github.com/users/JeremyRand)
* [Jocker666z](https://api.github.com/users/Jocker666z)
* [LogMANOriginal](https://api.github.com/users/LogMANOriginal)
* [MonsieurPoutounours](https://api.github.com/users/MonsieurPoutounours)
* [ORelio](https://api.github.com/users/ORelio)
* [PaulVayssiere](https://api.github.com/users/PaulVayssiere)
* [Piranhaplant](https://api.github.com/users/Piranhaplant)
* [Riduidel](https://api.github.com/users/Riduidel)
* [Strubbl](https://api.github.com/users/Strubbl)
* [TheRadialActive](https://api.github.com/users/TheRadialActive)
* [TwizzyDizzy](https://api.github.com/users/TwizzyDizzy)
* [WalterBarrett](https://api.github.com/users/WalterBarrett)
* [ZeNairolf](https://api.github.com/users/ZeNairolf)
* [adamchainz](https://api.github.com/users/adamchainz)
* [aledeg](https://api.github.com/users/aledeg)
* [alexAubin](https://api.github.com/users/alexAubin)
* [az5he6ch](https://api.github.com/users/az5he6ch)
* [b1nj](https://api.github.com/users/b1nj)
* [benasse](https://api.github.com/users/benasse)
* [captn3m0](https://api.github.com/users/captn3m0)
* [chemel](https://api.github.com/users/chemel)
* [ckiw](https://api.github.com/users/ckiw)
* [cnlpete](https://api.github.com/users/cnlpete)
* [corenting](https://api.github.com/users/corenting)
* [da2x](https://api.github.com/users/da2x)
* [eMerzh](https://api.github.com/users/eMerzh)
* [em92](https://api.github.com/users/em92)
* [griffaurel](https://api.github.com/users/griffaurel)
* [hunhejj](https://api.github.com/users/hunhejj)
* [j0k3r](https://api.github.com/users/j0k3r)
* [jdigilio](https://api.github.com/users/jdigilio)
* [kranack](https://api.github.com/users/kranack)
* [kraoc](https://api.github.com/users/kraoc)
* [laBecasse](https://api.github.com/users/laBecasse)
* [lagaisse](https://api.github.com/users/lagaisse)
* [lalannev](https://api.github.com/users/lalannev)
* [ldidry](https://api.github.com/users/ldidry)
* [m0zes](https://api.github.com/users/m0zes)
* [matthewseal](https://api.github.com/users/matthewseal)
* [mcbyte-it](https://api.github.com/users/mcbyte-it)
* [mdemoss](https://api.github.com/users/mdemoss)
* [melangue](https://api.github.com/users/melangue)
* [metaMMA](https://api.github.com/users/metaMMA)
* [mickael-bertrand](https://api.github.com/users/mickael-bertrand)
* [mitsukarenai](https://api.github.com/users/mitsukarenai)
* [mro](https://api.github.com/users/mro)
* [mxmehl](https://api.github.com/users/mxmehl)
* [nel50n](https://api.github.com/users/nel50n)
* [niawag](https://api.github.com/users/niawag)
* [pellaeon](https://api.github.com/users/pellaeon)
* [pit-fgfjiudghdf](https://api.github.com/users/pit-fgfjiudghdf)
* [pitchoule](https://api.github.com/users/pitchoule)
* [pmaziere](https://api.github.com/users/pmaziere)
* [prysme01](https://api.github.com/users/prysme01)
* [quentinus95](https://api.github.com/users/quentinus95)
* [qwertygc](https://api.github.com/users/qwertygc)
* [regisenguehard](https://api.github.com/users/regisenguehard)
* [rogerdc](https://api.github.com/users/rogerdc)
* [sebsauvage](https://api.github.com/users/sebsauvage)
* [sublimz](https://api.github.com/users/sublimz)
* [sysadminstory](https://api.github.com/users/sysadminstory)
* [tameroski](https://api.github.com/users/tameroski)
* [teromene](https://api.github.com/users/teromene)
* [triatic](https://api.github.com/users/triatic)
* [wtuuju](https://api.github.com/users/wtuuju)
Licenses
===

View File

@@ -3,7 +3,7 @@ class CommonDreamsBridge extends FeedExpander {
const MAINTAINER = 'nyutag';
const NAME = 'CommonDreams Bridge';
const URI = 'https://www.commondreams.org/';
const URI = 'http://www.commondreams.org/';
const DESCRIPTION = 'Returns the newest articles.';
public function collectData(){

View File

@@ -1,227 +0,0 @@
<?php
class CrewbayBridge extends BridgeAbstract {
const MAINTAINER = 'couraudt';
const NAME = 'Crewbay Bridge';
const URI = 'https://www.crewbay.com';
const DESCRIPTION = 'Returns the newest sailing offers.';
const PARAMETERS = array(
array(
'keyword' => array(
'name' => 'Filter by keyword',
'title' => 'Enter the keyword to filter here'
),
'type' => array(
'name' => 'Type of search',
'title' => 'Choose between finding a boat or a crew',
'type' => 'list',
'values' => array(
'Find a boat' => 'boats',
'Find a crew' => 'crew'
)
),
'status' => array(
'name' => 'Status on the boat',
'title' => 'Choose between recreational or professional classified ads',
'type' => 'list',
'values' => array(
'Recreational' => 'recreational',
'Professional' => 'professional'
)
),
'recreational_position' => array(
'name' => 'Recreational position wanted',
'title' => 'Filter by recreational position you wanted aboard',
'required' => false,
'type' => 'list',
'values' => array(
'' => '',
'Amateur Crew' => 'Amateur Crew',
'Friendship' => 'Friendship',
'Competent Crew' => 'Competent Crew',
'Racing' => 'Racing',
'Voluntary work' => 'Voluntary work',
'Mile building' => 'Mile building'
)
),
'professional_position' => array(
'name' => 'Professional position wanted',
'title' => 'Filter by professional position you wanted aboard',
'required' => false,
'type' => 'list',
'values' => array(
'' => '',
'1st Engineer' => '1st Engineer',
'1st Mate' => '1st Mate',
'Beautician' => 'Beautician',
'Bosun' => 'Bosun',
'Captain' => 'Captain',
'Chef' => 'Chef',
'Steward(ess)' => 'Steward(ess)',
'Deckhand' => 'Deckhand',
'Delivery Crew' => 'Delivery Crew',
'Dive Instructor' => 'Dive Instructor',
'Masseur' => 'Masseur',
'Medical Staff' => 'Medical Staff',
'Nanny' => 'Nanny',
'Navigator' => 'Navigator',
'Racing Crew' => 'Racing Crew',
'Teacher' => 'Teacher',
'Electrical Engineer' => 'Electrical Engineer',
'Fitter' => 'Fitter',
'2nd Engineer' => '2nd Engineer',
'3rd Engineer' => '3rd Engineer',
'Lead Deckhand' => 'Lead Deckhand',
'Security Officer' => 'Security Officer',
'O.O.W' => 'O.O.W',
'1st Officer' => '1st Officer',
'2nd Officer' => '2nd Officer',
'3rd Officer' => '3rd Officer',
'Captain/Engineer' => 'Captain/Engineer',
'Hairdresser' => 'Hairdresser',
'Fitness Trainer' => 'Fitness Trainer',
'Laundry' => 'Laundry',
'Solo Steward/ess' => 'Solo Steward/ess',
'Stew/Deck' => 'Stew/Deck',
'2nd Steward/ess' => '2nd Steward/ess',
'3rd Steward/ess' => '3rd Steward/ess',
'Chief Steward/ess' => 'Chief Steward/ess',
'Head Housekeeper' => 'Head Housekeeper',
'Purser' => 'Purser',
'Cook' => 'Cook',
'Cook/Stew' => 'Cook/Stew',
'2nd Chef' => '2nd Chef',
'Head Chef' => 'Head Chef',
'Administrator' => 'Administrator',
'P.A' => 'P.A',
'Villa staff' => 'Villa staff',
'Housekeeping/Stew' => 'Housekeeping/Stew',
'Stew/Beautician' => 'Stew/Beautician',
'Stew/Masseuse' => 'Stew/Masseuse',
'Manager' => 'Manager',
'Sailing instructor' => 'Sailing instructor'
)
)
)
);
public function collectData() {
$url = $this->getURI();
$html = getSimpleHTMLDOM($url) or returnClientError('No results for this query.');
$annonces = $html->find('#SearchResults div.result');
$limit = 0;
foreach ($annonces as $annonce) {
$detail = $annonce->find('.btn--profile', 0);
$htmlDetail = getSimpleHTMLDOMCached($detail->href);
if (!empty($this->getInput('recreational_position')) || !empty($this->getInput('professional_position'))) {
if ($this->getInput('type') == 'boats') {
if ($this->getInput('status') == 'professional') {
$positions = array($annonce->find('.title .position', 0)->plaintext);
} else {
$positions = array(str_replace('Wanted:', '', $annonce->find('.content li', 0)->plaintext));
}
} else {
$list = $htmlDetail->find('.viewer-details .viewer-list');
$positions = explode("\r\n", end($list)->find('span.value', 0)->plaintext);
}
$found = false;
$keyword = $this->getInput('status') == 'professional' ? 'professional_position' : 'recreational_position';
foreach ($positions as $position) {
if (strpos(trim($position), $this->getInput($keyword)) !== false) {
$found = true;
break;
}
}
if (!$found) {
continue;
}
}
$item = array();
if ($this->getInput('type') == 'boats') {
$titleSelector = '.title h2';
} else {
$titleSelector = '.layout__item h2';
}
$userName = $annonce->find('.result--description a', 0)->plaintext;
$annonceTitle = trim($annonce->find($titleSelector, 0)->plaintext);
if (empty($annonceTitle)) {
$item['title'] = $userName;
} else {
$item['title'] = $userName . ' - ' . $annonceTitle;
}
$item['uri'] = $detail->href;
$images = $annonce->find('.avatar img');
$item['enclosures'] = array(end($images)->getAttribute('src'));
$content = $htmlDetail->find('.viewer-intro--info', 0)->innertext;
$sections = $htmlDetail->find('.viewer-container .viewer-section');
foreach ($sections as $section) {
if ($section->find('.viewer-section-title', 0)) {
$class = str_replace('viewer-', '', explode(' ', $section->getAttribute('class'))[0]);
if (!in_array($class, array('apply', 'photos', 'reviews', 'contact', 'experience', 'qa'))) {
// Basic sections
$content .= $section->find('.viewer-section-title h3', 0)->outertext;
$content .= $section->find('.viewer-section-content', 0)->innertext;
}
} else {
// Info section
$content .= $section->find('.viewer-section-content h3', 0)->outertext;
$content .= $section->find('.viewer-section-content p', 0)->outertext;
}
}
if (!empty($this->getInput('keyword'))) {
$keyword = strtolower($this->getInput('keyword'));
if (strpos(strtolower($item['title']), $keyword) === false) {
if (strpos(strtolower($content), $keyword) === false) {
continue;
}
}
}
$item['content'] = $content;
$tags = $htmlDetail->find('li.viewer-tags--tag');
foreach ($tags as $tag) {
if (!isset($item['categories'])) {
$item['categories'] = array();
}
$text = trim($tag->plaintext);
if (!in_array($text, $item['categories'])) {
$item['categories'][] = $text;
}
}
$this->items[] = $item;
$limit += 1;
if ($limit == 10) break;
}
}
public function getURI() {
$uri = parent::getURI();
if ($this->getInput('type') == 'boats') {
$uri .= '/boats';
} else {
$uri .= '/crew';
}
if ($this->getInput('status') == 'professional') {
$uri .= '/professional';
} else {
$uri .= '/recreational';
}
return $uri;
}
}

View File

@@ -121,7 +121,7 @@ class ElloBridge extends BridgeAbstract {
private function getAPIKey() {
$cache = Cache::create('FileCache');
$cache->setPath(PATH_CACHE);
$cache->setPath(CACHE_DIR);
$cache->setParameters(['key']);
$key = $cache->loadData();

View File

@@ -103,7 +103,7 @@ EOD;
$timestamp = 0;
$item['uri'] = html_entity_decode('http://touch.facebook.com'
. $content->find("div[class='_52jc _5qc4 _78cz _24u0 _36xo']", 0)->find('a', 0)->getAttribute('href'), ENT_QUOTES);
. $content->find("div[class='_52jc _5qc4 _24u0 _36xo']", 0)->find('a', 0)->getAttribute('href'), ENT_QUOTES);
//Decode images
$imagecleaned = preg_replace_callback('/<i [^>]* style="[^"]*url\(\'(.*?)\'\).*?><\/i>/m', function ($matches) {

View File

@@ -364,26 +364,6 @@ class FacebookBridge extends BridgeAbstract {
}, $content);
}
/**
* Remove Facebook's tracking code
*/
private function remove_tracking_codes($content){
return preg_replace_callback('/ href=\"([^"]+)\"/i', function($matches){
if(is_array($matches) && count($matches) > 1) {
$link = $matches[1];
if(strpos($link, 'facebook.com') !== false) {
if(strpos($link, '?') !== false) {
$link = substr($link, 0, strpos($link, '?'));
}
}
return ' href="' . $link . '"';
}
}, $content);
}
/**
* Convert textual representation of emoticons back to ASCII emoticons.
* i.e. "<i><u>smile emoticon</u></i>" => ":)"
@@ -446,7 +426,8 @@ class FacebookBridge extends BridgeAbstract {
// Show captcha filling form to the viewer, proxying the captcha image
$img = base64_encode(getContents($captcha->find('img', 0)->src));
header('Content-Type: text/html', true, 500);
http_response_code(500);
header('Content-Type: text/html');
$message = <<<EOD
<form method="post" action="?{$_SERVER['QUERY_STRING']}">
@@ -538,7 +519,7 @@ EOD;
if(isset($element)) {
$author = str_replace(' - Posts | Facebook', '', $html->find('title#pageTitle', 0)->innertext);
$author = str_replace(' | Facebook', '', $html->find('title#pageTitle', 0)->innertext);
$profilePic = $html->find('meta[property="og:image"]', 0)->content;
@@ -577,28 +558,10 @@ EOD;
$content = $post->find('.userContentWrapper', 0);
// This array specifies filters applied to all posts in order of appearance
$content_filters = array(
'._5mly', // Remove embedded videos (the preview image remains)
'._2ezg', // Remove "Views ..."
'.hidden_elem', // Remove hidden elements (they are hidden anyway)
);
foreach($content_filters as $filter) {
foreach($content->find($filter) as $subject) {
$subject->outertext = '';
}
}
// Change origin tag for embedded media from div to paragraph
foreach($content->find('._59tj') as $subject) {
$subject->outertext = '<p>' . $subject->innertext . '</p>';
}
// Change title tag for embedded media from anchor to paragraph
foreach($content->find('._3n1k a') as $anchor) {
$anchor->outertext = '<p>' . $anchor->innertext . '</p>';
}
$content = preg_replace(
'/(?i)><div class=\"_59tj([^>]+)>(.+?)<\/div><\/div><a/i',
'',
$content);
$content = preg_replace(
'/(?i)><div class=\"_3dp([^>]+)>(.+?)div\ class=\"[^u]+userContent\"/i',
@@ -648,8 +611,6 @@ EOD;
// Restore links in the content before adding to the item
$content = defaultLinkTo($content, self::URI);
$content = $this->remove_tracking_codes($content);
// Retrieve date of the post
$date = $post->find('abbr')[0];
@@ -659,8 +620,14 @@ EOD;
$date = 0;
}
// Build title from content
$title = strip_tags($post->find('.userContent', 0)->innertext);
// Build title from username and content
$title = $author;
if(strlen($title) > 24)
$title = substr($title, 0, strpos(wordwrap($title, 24), "\n")) . '...';
$title = $title . ' | ' . strip_tags($content);
if(strlen($title) > 64)
$title = substr($title, 0, strpos(wordwrap($title, 64), "\n")) . '...';
@@ -671,10 +638,10 @@ EOD;
}
//Build and add final item
$item['uri'] = htmlspecialchars_decode($uri, ENT_QUOTES);
$item['content'] = htmlspecialchars_decode($content, ENT_QUOTES);
$item['title'] = htmlspecialchars_decode($title, ENT_QUOTES);
$item['author'] = htmlspecialchars_decode($author, ENT_QUOTES);
$item['uri'] = htmlspecialchars_decode($uri);
$item['content'] = htmlspecialchars_decode($content);
$item['title'] = $title;
$item['author'] = $author;
$item['timestamp'] = $date;
if(strpos($item['content'], '<img') === false) {

View File

@@ -94,7 +94,7 @@ class FilterBridge extends FeedExpander {
}
try{
$this->collectExpandableDatas($this->getURI());
} catch (Exception $e) {
} catch (HttpException $e) {
$this->collectExpandableDatas($this->getURI());
}
}

View File

@@ -1,82 +0,0 @@
<?php
class FindACrewBridge extends BridgeAbstract {
const MAINTAINER = 'couraudt';
const NAME = 'Find A Crew Bridge';
const URI = 'https://www.findacrew.net';
const DESCRIPTION = 'Returns the newest sailing offers.';
const PARAMETERS = array(
array(
'type' => array(
'name' => 'Type of search',
'title' => 'Choose between finding a boat or a crew',
'type' => 'list',
'values' => array(
'Find a boat' => 'boat',
'Find a crew' => 'crew'
)
),
'long' => array(
'name' => 'Longitude of the searched location',
'title' => 'Center the search at that longitude (e.g: -42.02)'
),
'lat' => array(
'name' => 'Latitude of the searched location',
'title' => 'Center the search at that latitude (e.g: 12.42)'
),
'distance' => array(
'name' => 'Limit boundary of search in KM',
'title' => 'Boundary of the search in kilometers when using longitude and latitude'
)
)
);
public function collectData() {
$url = $this->getURI();
if ($this->getInput('type') == 'boat') {
$data = array('SrhLstBtAction' => 'Create');
} else {
$data = array('SrhLstCwAction' => 'Create');
}
if ($this->getInput('long') && $this->getInput('lat')) {
$data['real_LocSrh_Lng'] = $this->getInput('long');
$data['real_LocSrh_Lat'] = $this->getInput('lat');
if ($this->getInput('distance')) {
$data['LocDis'] = (int)$this->getInput('distance') * 1000;
}
}
$header = array(
'Content-Type: application/x-www-form-urlencoded'
);
$opts = array(
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => http_build_query($data) . "\n"
);
$html = getSimpleHTMLDOM($url, $header, $opts) or returnClientError('No results for this query.');
$annonces = $html->find('.css_SrhRst');
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;
$item['content'] = "<img src='$img' /><br>$content";
$item['enclosures'] = array($img);
$item['categories'] = array($annonce->find('.css_AccLocCur', 0)->plaintext);
$this->items[] = $item;
}
}
public function getURI() {
$uri = parent::getURI();
// Those params must be in the URL
$uri .= '/en/' . $this->getInput('type') . '/search?srhtyp=srhrst&mdl=2';
return $uri;
}
}

View File

@@ -37,9 +37,10 @@ class GithubIssueBridge extends BridgeAbstract {
$name = $this->getInput('u') . '/' . $this->getInput('p');
switch($this->queriedContext) {
case 'Project Issues':
$prefix = static::NAME . 's for ';
if($this->getInput('c')) {
$prefix = static::NAME . 's comments for ';
} else {
$prefix = static::NAME . 's for ';
}
$name = $prefix . $name;
break;
@@ -52,9 +53,8 @@ class GithubIssueBridge extends BridgeAbstract {
}
public function getURI(){
if(null !== $this->getInput('u') && null !== $this->getInput('p')) {
$uri = static::URI . $this->getInput('u') . '/'
. $this->getInput('p') . '/issues';
if(!is_null($this->getInput('u')) && !is_null($this->getInput('p'))) {
$uri = static::URI . $this->getInput('u') . '/' . $this->getInput('p') . '/issues';
if($this->queriedContext === 'Issue comments') {
$uri .= '/' . $this->getInput('i');
} elseif($this->getInput('c')) {
@@ -66,54 +66,54 @@ class GithubIssueBridge extends BridgeAbstract {
return parent::getURI();
}
protected function extractIssueEvent($issueNbr, $title, $comment){
protected function extractIssueComment($issueNbr, $title, $comment){
$class = $comment->getAttribute('class');
$classes = explode(' ', $class);
$event = false;
if(in_array('discussion-item', $classes)) {
$event = true;
}
$author = 'unknown';
if($comment->find('.author', 0)) {
$author = $comment->find('.author', 0)->plaintext;
}
$uri = static::URI . $this->getInput('u') . '/' . $this->getInput('p') . '/issues/' . $issueNbr;
$comment = $comment->firstChild();
$uri = static::URI . $this->getInput('u') . '/' . $this->getInput('p')
. '/issues/' . $issueNbr . '#' . $comment->getAttribute('id');
if(!$event) {
$comment = $comment->nextSibling();
}
$author = $comment->find('.author', 0)->plaintext;
$title .= ' / ' . trim($comment->plaintext);
$content = $title;
if (null !== $comment->nextSibling()) {
$content = $comment->nextSibling()->innertext;
if ($comment->nextSibling()->nodeName() === 'span') {
$content = $comment->nextSibling()->nextSibling()->innertext;
if($event) {
$title .= ' / ' . substr($class, strpos($class, 'discussion-item-') + strlen('discussion-item-'));
if(!$comment->hasAttribute('id')) {
$items = array();
$timestamp = strtotime($comment->find('relative-time', 0)->getAttribute('datetime'));
$content = $comment->innertext;
while($comment = $comment->nextSibling()) {
$item = array();
$item['author'] = $author;
$item['title'] = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
$item['timestamp'] = $timestamp;
$item['content'] = $content . '<p>' . $comment->children(1)->innertext . '</p>';
$item['uri'] = $uri . '#' . $comment->children(1)->getAttribute('id');
$items[] = $item;
}
return $items;
}
$content = $comment->parent()->innertext;
} else {
$title .= ' / ' . trim($comment->firstChild()->plaintext);
$content = '<pre>' . $comment->find('.comment-body', 0)->innertext . '</pre>';
}
$item = array();
$item['author'] = $author;
$item['uri'] = $uri;
$item['uri'] = $uri . '#' . $comment->getAttribute('id');
$item['title'] = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
$item['timestamp'] = strtotime(
$comment->find('relative-time', 0)->getAttribute('datetime')
);
$item['content'] = $content;
return $item;
}
protected function extractIssueComment($issueNbr, $title, $comment){
$uri = static::URI . $this->getInput('u') . '/'
. $this->getInput('p') . '/issues/' . $issueNbr;
$author = $comment->find('.author', 0)->plaintext;
$title .= ' / ' . trim(
$comment->find('.comment .timeline-comment-header-text', 0)->plaintext
);
$content = $comment->find('.comment-body', 0)->innertext;
$item = array();
$item['author'] = $author;
$item['uri'] = $uri
. '#' . $comment->firstChild()->nextSibling()->getAttribute('id');
$item['title'] = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
$item['timestamp'] = strtotime(
$comment->find('relative-time', 0)->getAttribute('datetime')
);
$item['timestamp'] = strtotime($comment->find('relative-time', 0)->getAttribute('datetime'));
$item['content'] = $content;
return $item;
}
@@ -121,29 +121,17 @@ class GithubIssueBridge extends BridgeAbstract {
protected function extractIssueComments($issue){
$items = array();
$title = $issue->find('.gh-header-title', 0)->plaintext;
$issueNbr = trim(
substr($issue->find('.gh-header-number', 0)->plaintext, 1)
);
$issueNbr = trim(substr($issue->find('.gh-header-number', 0)->plaintext, 1));
$comments = $issue->find('.js-discussion', 0);
foreach($comments->children() as $comment) {
if (!$comment->hasChildNodes()) {
continue;
}
$comment = $comment->firstChild();
$classes = explode(' ', $comment->getAttribute('class'));
if (in_array('timeline-comment-wrapper', $classes)) {
if(in_array('discussion-item', $classes)
|| in_array('timeline-comment-wrapper', $classes)) {
$item = $this->extractIssueComment($issueNbr, $title, $comment);
$items[] = $item;
continue;
}
while (in_array('discussion-item', $classes)) {
$item = $this->extractIssueEvent($issueNbr, $title, $comment);
$items[] = $item;
$comment = $comment->nextSibling();
if (null == $comment) {
break;
if(array_keys($item) !== range(0, count($item) - 1)) {
$item = array($item);
}
$classes = explode(' ', $comment->getAttribute('class'));
$items = array_merge($items, $item);
}
}
return $items;
@@ -151,9 +139,7 @@ class GithubIssueBridge extends BridgeAbstract {
public function collectData(){
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError(
'No results for Github Issue ' . $this->getURI()
);
or returnServerError('No results for Github Issue ' . $this->getURI());
switch($this->queriedContext) {
case 'Issue comments':
@@ -162,40 +148,31 @@ class GithubIssueBridge extends BridgeAbstract {
case 'Project Issues':
foreach($html->find('.js-active-navigation-container .js-navigation-item') as $issue) {
$info = $issue->find('.opened-by', 0);
$issueNbr = substr(
trim($info->plaintext), 1, strpos(trim($info->plaintext), ' ')
);
$issueNbr = substr(trim($info->plaintext), 1, strpos(trim($info->plaintext), ' '));
$item = array();
$item['content'] = '';
if($this->getInput('c')) {
$uri = static::URI . $this->getInput('u')
. '/' . $this->getInput('p') . '/issues/' . $issueNbr;
$uri = static::URI . $this->getInput('u') . '/' . $this->getInput('p') . '/issues/' . $issueNbr;
$issue = getSimpleHTMLDOMCached($uri, static::CACHE_TIMEOUT);
if($issue) {
$this->items = array_merge(
$this->items,
$this->extractIssueComments($issue)
);
$this->items = array_merge($this->items, $this->extractIssueComments($issue));
continue;
}
$item['content'] = 'Can not extract comments from ' . $uri;
}
$item['author'] = $info->find('a', 0)->plaintext;
$item['timestamp'] = strtotime(
$info->find('relative-time', 0)->getAttribute('datetime')
);
$item['timestamp'] = strtotime($info->find('relative-time', 0)->getAttribute('datetime'));
$item['title'] = html_entity_decode(
$issue->find('.js-navigation-open', 0)->plaintext,
ENT_QUOTES,
'UTF-8'
);
$comments = trim($issue->find('.col-5', 0)->plaintext);
$comments = $issue->find('.col-5', 0)->plaintext;
$item['content'] .= "\n" . 'Comments: ' . ($comments ? $comments : '0');
$item['uri'] = self::URI
. $issue->find('.js-navigation-open', 0)->getAttribute('href');
$item['uri'] = self::URI . $issue->find('.js-navigation-open', 0)->getAttribute('href');
$this->items[] = $item;
}
break;
@@ -203,11 +180,7 @@ class GithubIssueBridge extends BridgeAbstract {
array_walk($this->items, function(&$item){
$item['content'] = preg_replace('/\s+/', ' ', $item['content']);
$item['content'] = str_replace(
'href="/',
'href="' . static::URI,
$item['content']
);
$item['content'] = str_replace('href="/', 'href="' . static::URI, $item['content']);
$item['content'] = str_replace(
'href="#',
'href="' . substr($item['uri'], 0, strpos($item['uri'], '#') + 1),

0
bridges/GlassdoorBridge.php Normal file → Executable file
View File

View File

@@ -19,12 +19,6 @@ class InstagramBridge extends BridgeAbstract {
'required' => true
)
),
array(
'l' => array(
'name' => 'location',
'required' => true
)
),
'global' => array(
'media_type' => array(
'name' => 'Media type',
@@ -44,18 +38,16 @@ class InstagramBridge extends BridgeAbstract {
public function collectData(){
if(is_null($this->getInput('u')) && $this->getInput('media_type') == 'story') {
returnClientError('Stories are not supported for hashtags nor locations!');
if(!is_null($this->getInput('h')) && $this->getInput('media_type') == 'story') {
returnClientError('Stories are not supported for hashtags!');
}
$data = $this->getInstagramJSON($this->getURI());
if(!is_null($this->getInput('u'))) {
$userMedia = $data->entry_data->ProfilePage[0]->graphql->user->edge_owner_to_timeline_media->edges;
} elseif(!is_null($this->getInput('h'))) {
} else {
$userMedia = $data->entry_data->TagPage[0]->graphql->hashtag->edge_hashtag_to_media->edges;
} elseif(!is_null($this->getInput('l'))) {
$userMedia = $data->entry_data->LocationsPage[0]->graphql->location->edge_location_to_media->edges;
}
foreach($userMedia as $media) {
@@ -155,9 +147,8 @@ class InstagramBridge extends BridgeAbstract {
return self::URI . urlencode($this->getInput('u')) . '/';
} elseif(!is_null($this->getInput('h'))) {
return self::URI . 'explore/tags/' . urlencode($this->getInput('h'));
} elseif(!is_null($this->getInput('l'))) {
return self::URI . 'explore/locations/' . urlencode($this->getInput('l'));
}
return parent::getURI();
}
}

View File

@@ -132,7 +132,7 @@ class JustETFBridge extends BridgeAbstract {
date_time_set($df, 0, 0);
// Debug::log(date_format($df, 'U'));
// debugMessage(date_format($df, 'U'));
return date_format($df, 'U');
}
@@ -210,7 +210,7 @@ class JustETFBridge extends BridgeAbstract {
$element = $article->find('div.subheadline', 0)
or returnServerError('Date not found!');
// Debug::log($element->plaintext);
// debugMessage($element->plaintext);
$date = trim(explode('|', $element->plaintext)[0]);
@@ -223,7 +223,7 @@ class JustETFBridge extends BridgeAbstract {
$element->find('a', 0)->onclick = '';
// Debug::log($element->innertext);
// debugMessage($element->innertext);
return $element->innertext;
}
@@ -288,7 +288,7 @@ class JustETFBridge extends BridgeAbstract {
$element = $html->find('div.infobox div.vallabel', 0)
or returnServerError('Date not found!');
// Debug::log($element->plaintext);
// debugMessage($element->plaintext);
$date = trim(explode("\r\n", $element->plaintext)[1]);

View File

@@ -1,28 +0,0 @@
<?php
class MozillaSecurityBridge extends BridgeAbstract {
const MAINTAINER = 'm0le.net';
const NAME = 'Mozilla Security Advisories';
const URI = 'https://www.mozilla.org/en-US/security/advisories/';
const CACHE_TIMEOUT = 7200; // 2h
const DESCRIPTION = 'Mozilla Security Advisories';
const WEBROOT = 'https://www.mozilla.org';
public function collectData(){
$html = getSimpleHTMLDOM(self::URI)
or returnServerError('Could not request MSA.');
$html = defaultLinkTo($html, self::WEBROOT);
$item = array();
$articles = $html->find('div[itemprop="articleBody"] h2');
foreach ($articles as $element) {
$item['title'] = $element->innertext;
$item['timestamp'] = strtotime($element->innertext);
$item['content'] = $element->next_sibling()->innertext;
$item['uri'] = self::URI;
$this->items[] = $item;
}
}
}

View File

@@ -1,130 +0,0 @@
<?php
class OnVaSortirBridge extends FeedExpander {
const MAINTAINER = 'AntoineTurmel';
const NAME = 'OnVaSortir';
const URI = 'https://www.onvasortir.com';
const DESCRIPTION = 'Returns the newest events from OnVaSortir (full text)';
const PARAMETERS = array(
array(
'city' => array(
'name' => 'City',
'type' => 'list',
'required' => true,
'values' => array(
'Agen' => 'Agen',
'Ajaccio' => 'Ajaccio',
'Albi' => 'Albi',
'Amiens' => 'Amiens',
'Angers' => 'Angers',
'Angoulême' => 'Angouleme',
'Annecy' => 'annecy',
'Aurillac' => 'aurillac',
'Auxerre' => 'auxerre',
'Avignon' => 'avignon',
'Béziers' => 'Beziers',
'Bastia' => 'Bastia',
'Beauvais' => 'Beauvais',
'Belfort' => 'Belfort',
'Bergerac' => 'bergerac',
'Besançon' => 'Besancon',
'Biarritz' => 'Biarritz',
'Blois' => 'Blois',
'Bordeaux' => 'bordeaux',
'Bourg-en-Bresse' => 'bourg-en-bresse',
'Bourges' => 'Bourges',
'Brest' => 'Brest',
'Brive' => 'brive-la-gaillarde',
'Bruxelles' => 'bruxelles',
'Caen' => 'Caen',
'Calais' => 'Calais',
'Carcassonne' => 'Carcassonne',
'Châteauroux' => 'Chateauroux',
'Chalon-sur-saone' => 'chalon-sur-saone',
'Chambéry' => 'chambery',
'Chantilly' => 'chantilly',
'Charleroi' => 'charleroi',
'Charleville-Mézières' => 'Charleville-Mezieres',
'Chartres' => 'Chartres',
'Cherbourg' => 'Cherbourg',
'Cholet' => 'cholet',
'Clermont-Ferrand' => 'Clermont-Ferrand',
'Compiègne' => 'compiegne',
'Dieppe' => 'dieppe',
'Dijon' => 'Dijon',
'Dunkerque' => 'Dunkerque',
'Evreux' => 'evreux',
'Fréjus' => 'frejus',
'Gap' => 'gap',
'Genève' => 'geneve',
'Grenoble' => 'Grenoble',
'La Roche sur Yon' => 'La-Roche-sur-Yon',
'La Rochelle' => 'La-Rochelle',
'Lausanne' => 'lausanne',
'Laval' => 'Laval',
'Le Havre' => 'le-havre',
'Le Mans' => 'le-mans',
'Liège' => 'liege',
'Lille' => 'lille',
'Limoges' => 'Limoges',
'Lorient' => 'Lorient',
'Luxembourg' => 'Luxembourg',
'Lyon' => 'lyon',
'Marseille' => 'marseille',
'Metz' => 'Metz',
'Mons' => 'Mons',
'Mont de Marsan' => 'mont-de-marsan',
'Montauban' => 'Montauban',
'Montluçon' => 'montlucon',
'Montpellier' => 'montpellier',
'Mulhouse' => 'Mulhouse',
'Nîmes' => 'nimes',
'Namur' => 'Namur',
'Nancy' => 'Nancy',
'Nantes' => 'nantes',
'Nevers' => 'nevers',
'Nice' => 'nice',
'Niort' => 'niort',
'Orléans' => 'orleans',
'Périgueux' => 'perigueux',
'Paris' => 'paris',
'Pau' => 'Pau',
'Perpignan' => 'Perpignan',
'Poitiers' => 'Poitiers',
'Quimper' => 'Quimper',
'Reims' => 'Reims',
'Rennes' => 'Rennes',
'Roanne' => 'roanne',
'Rodez' => 'rodez',
'Rouen' => 'Rouen',
'Saint-Brieuc' => 'Saint-Brieuc',
'Saint-Etienne' => 'saint-etienne',
'Saint-Malo' => 'saint-malo',
'Saint-Nazaire' => 'saint-nazaire',
'Saint-Quentin' => 'saint-quentin',
'Saintes' => 'saintes',
'Strasbourg' => 'Strasbourg',
'Tarbes' => 'Tarbes',
'Toulon' => 'Toulon',
'Toulouse' => 'Toulouse',
'Tours' => 'Tours',
'Troyes' => 'troyes',
'Valence' => 'valence',
'Vannes' => 'vannes',
'Zurich' => 'zurich',
),
'defaultValue' => ''
)
)
);
protected function parseItem($item){
$item = parent::parseItem($item);
$html = getSimpleHTMLDOMCached($item['uri']);
$text = $html->find('div.corpsMax', 0)->innertext;
$item['content'] = utf8_encode($text);
return $item;
}
public function collectData(){
$this->collectExpandableDatas('https://' .
$this->getInput('city') . '.onvasortir.com/rss.php');
}
}

View File

@@ -53,7 +53,7 @@ class PixivBridge extends BridgeAbstract {
$url = str_replace('_master1200', '', $url);
$url = str_replace('c/240x240/img-master/', 'img-original/', $url);
$path = PATH_CACHE . 'pixiv_img/';
$path = CACHE_DIR . '/pixiv_img';
if(!is_dir($path))
mkdir($path, 0755, true);

View File

@@ -13,8 +13,8 @@ class RainbowSixSiegeBridge extends BridgeAbstract {
public function collectData(){
$dlUrl = 'https://prod-tridionservice.ubisoft.com/live/v1/News/Latest?templateId=tcm%3A152-7677';
$dlUrl .= '8-32&pageIndex=0&pageSize=10&language=en-US&detailPageId=tcm%3A150-194572-64';
$dlUrl .= '&keywordList=233416%2C316144%2C233418%2C233417&siteId=undefined&useSeoFriendlyUrl=true';
$dlUrl .= '8-32&pageIndex=0&pageSize=10&language=en-US&detailPageId=tcm%3A152-194572-64';
$dlUrl .= '&keywordList=175426&siteId=undefined&useSeoFriendlyUrl=true';
$jsonString = getContents($dlUrl) or returnServerError('Error while downloading the website content');
$json = json_decode($jsonString, true);

View File

@@ -1,50 +1,25 @@
<?php
class Rue89Bridge extends BridgeAbstract {
class Rue89Bridge extends FeedExpander {
const MAINTAINER = 'teromene';
const MAINTAINER = 'pit-fgfjiudghdf';
const NAME = 'Rue89';
const URI = 'https://www.nouvelobs.com/rue89/';
const DESCRIPTION = 'Returns the newest posts from Rue89';
const URI = 'http://rue89.nouvelobs.com/';
const DESCRIPTION = 'Returns the 5 newest posts from Rue89 (full text)';
protected function parseItem($item){
$item = parent::parseItem($item);
public function collectData() {
$url = 'http://api.rue89.nouvelobs.com/export/mobile2/node/'
. str_replace(' ', '', substr($item['uri'], -8))
. '/full';
$jsonArticles = getContents('https://appdata.nouvelobs.com/rue89/feed.json')
or die('Unable to query Rue89 !');
$articles = json_decode($jsonArticles)->items;
foreach($articles as $article) {
$this->items[] = $this->getArticle($article);
}
}
private function getArticle($articleInfo) {
$articleJson = getContents($articleInfo->json_url) or die('Unable to get article !');
$article = json_decode($articleJson);
$item = array();
$item['title'] = $article->title;
$item['uri'] = $article->url;
if($article->content_premium !== null) {
$item['content'] = $article->content_premium;
} else {
$item['content'] = $article->content;
}
$item['timestamp'] = $article->date_publi;
$item['author'] = $article->author->show_name;
$item['enclosures'] = array();
foreach($article->images as $image) {
$item['enclosures'][] = $image->url;
}
$item['categories'] = array();
foreach($article->categories as $category) {
$item['categories'][] = $category->title;
}
$datas = json_decode(getContents($url), true);
$item['content'] = $datas['node']['body'];
return $item;
}
public function collectData(){
$this->collectExpandableDatas('http://api.rue89.nouvelobs.com/feed');
}
}

View File

@@ -34,13 +34,13 @@ class SoundCloudBridge extends BridgeAbstract {
for($i = 0; $i < 10; $i++) {
$item = array();
$item['author'] = $tracks[$i]->user->username;
$item['author'] = $tracks[$i]->user->username . ' - ' . $tracks[$i]->title;
$item['title'] = $tracks[$i]->user->username . ' - ' . $tracks[$i]->title;
$item['timestamp'] = strtotime($tracks[$i]->created_at);
$item['content'] = $tracks[$i]->description;
$item['enclosures'] = array($tracks[$i]->uri
$item['content'] = '<audio src="'
. $tracks[$i]->uri
. '/stream?client_id='
. self::CLIENT_ID);
. self::CLIENT_ID
. '">';
$item['id'] = self::URI
. urlencode($this->getInput('u'))

View File

@@ -3,7 +3,7 @@ class ThePirateBayBridge extends BridgeAbstract {
const MAINTAINER = 'mitsukarenai';
const NAME = 'The Pirate Bay';
const URI = 'https://thepiratebay.wf/';
const URI = 'https://thepiratebay.org/';
const DESCRIPTION = 'Returns results for the keywords. You can put several
list of keywords by separating them with a semicolon (e.g. "one show;another
show"). Category based search needs the category number as input. User based

View File

@@ -66,41 +66,6 @@ class TwitterBridge extends BridgeAbstract {
)
);
public function detectParameters($url){
$params = array();
// By keyword or hashtag (search)
$regex = '/^(https?:\/\/)?(www\.)?twitter\.com\/search.*(\?|&)q=([^\/&?\n]+)/';
if(preg_match($regex, $url, $matches) > 0) {
$params['q'] = urldecode($matches[4]);
return $params;
}
// By hashtag
$regex = '/^(https?:\/\/)?(www\.)?twitter\.com\/hashtag\/([^\/?\n]+)/';
if(preg_match($regex, $url, $matches) > 0) {
$params['q'] = urldecode($matches[3]);
return $params;
}
// By list
$regex = '/^(https?:\/\/)?(www\.)?twitter\.com\/([^\/?\n]+)\/lists\/([^\/?\n]+)/';
if(preg_match($regex, $url, $matches) > 0) {
$params['user'] = urldecode($matches[3]);
$params['list'] = urldecode($matches[4]);
return $params;
}
// By username
$regex = '/^(https?:\/\/)?(www\.)?twitter\.com\/([^\/?\n]+)/';
if(preg_match($regex, $url, $matches) > 0) {
$params['u'] = urldecode($matches[3]);
return $params;
}
return null;
}
public function getName(){
switch($this->queriedContext) {
case 'By keyword or hashtag':
@@ -179,9 +144,9 @@ class TwitterBridge extends BridgeAbstract {
$item = array();
// extract username and sanitize
$item['username'] = htmlspecialchars_decode($tweet->getAttribute('data-screen-name'), ENT_QUOTES);
$item['username'] = $tweet->getAttribute('data-screen-name');
// extract fullname (pseudonym)
$item['fullname'] = htmlspecialchars_decode($tweet->getAttribute('data-name'), ENT_QUOTES);
$item['fullname'] = $tweet->getAttribute('data-name');
// get author
$item['author'] = $item['fullname'] . ' (@' . $item['username'] . ')';
// get avatar link
@@ -193,8 +158,7 @@ class TwitterBridge extends BridgeAbstract {
// extract tweet timestamp
$item['timestamp'] = $tweet->find('span.js-short-timestamp', 0)->getAttribute('data-time');
// generate the title
$item['title'] = strip_tags($this->fixAnchorSpacing(htmlspecialchars_decode(
$tweet->find('p.js-tweet-text', 0), ENT_QUOTES), '<a>'));
$item['title'] = strip_tags($this->fixAnchorSpacing($tweet->find('p.js-tweet-text', 0), '<a>'));
switch($this->queriedContext) {
case 'By list':
@@ -294,17 +258,16 @@ EOD;
}
$item['content'] = <<<EOD
{$item['content']}
<hr>
<div style="display: inline-block; vertical-align: top;">
<blockquote>{$cleanedQuotedTweet}</blockquote>
</div>
<div style="display: block; vertical-align: top;">
<blockquote>{$quotedImage_html}</blockquote>
</div>
<hr>
{$item['content']}
EOD;
}
$item['content'] = htmlspecialchars_decode($item['content'], ENT_QUOTES);
// put out
$this->items[] = $item;

View File

@@ -93,7 +93,7 @@ class WordPressBridge extends FeedExpander {
}
try{
$this->collectExpandableDatas($this->getURI() . '/feed/atom/');
} catch (Exception $e) {
} catch (HttpException $e) {
$this->collectExpandableDatas($this->getURI() . '/?feed=atom');
}

View File

@@ -74,10 +74,10 @@ class WordPressPluginUpdateBridge extends BridgeAbstract {
}
private function getCachedDate($url){
Debug::log('getting pubdate from url ' . $url . '');
debugMessage('getting pubdate from url ' . $url . '');
// Initialize cache
$cache = Cache::create('FileCache');
$cache->setPath(PATH_CACHE . 'pages/');
$cache->setPath(CACHE_DIR . '/pages');
$params = [$url];
$cache->setParameters($params);
// Get cachefile timestamp

View File

@@ -453,7 +453,7 @@ class XenForoBridge extends BridgeAbstract {
}
// Debug::log(date_format($df, 'U'));
// debugMessage(date_format($df, 'U'));
return date_format($df, 'U');

View File

@@ -115,7 +115,6 @@ class YGGTorrentBridge extends BridgeAbstract {
$item = array();
$item['timestamp'] = $row->find('.hidden', 1)->plaintext;
$item['title'] = $row->find('a', 1)->plaintext;
$item['uri'] = $row->find('a', 1)->href;
$torrentData = $this->collectTorrentData($row->find('a', 1)->href);
$item['author'] = $torrentData['author'];
$item['content'] = $torrentData['content'];

View File

@@ -1,15 +1,8 @@
<?php
class ZoneTelechargementBridge extends BridgeAbstract {
/* This bridge was initally done for the Website Zone Telechargement,
* but the website changed it's name and URL.
* Therefore, the class name and filename does not correspond to the
* name of the bridge. This permits to keep the same RSS Feed URL.
*/
const NAME = 'Annuaire Telechargement';
const URI = 'https://www.annuaire-telechargement.com/';
const DESCRIPTION = 'Suivi de série sur Annuaire Telechargement';
const NAME = 'Zone Telechargement';
const URI = 'https://www.zone-telechargement1.org/';
const DESCRIPTION = 'Suivi de série sur Zone Telechargement';
const MAINTAINER = 'sysadminstory';
const PARAMETERS = array(
'Suivre la publication des épisodes d\'une série en cours de diffusion' => array(
@@ -17,14 +10,14 @@ class ZoneTelechargementBridge extends BridgeAbstract {
'name' => 'URL de la série',
'type' => 'text',
'required' => true,
'title' => 'URL d\'une série sans le https://www.annuaire-telechargement.com/',
'title' => 'URL d\'une série sans le https://ww4.zone-telechargement1.org/',
'exampleValue' => 'telecharger-series/31079-halt-and-catch-fire-saison-4-french-hd720p.html'
)
)
);
public function getIcon() {
return 'https://www.annuaire-telechargement.com/templates/Default/images/favicon.ico';
return 'https://ww7.zone-telechargement1.org/templates/Default/images/favicon.ico';
}
public function collectData(){

View File

@@ -15,7 +15,7 @@ class AtomFormat extends FormatAbstract{
$extraInfos = $this->getExtraInfos();
$title = $this->xml_encode($extraInfos['name']);
$uri = !empty($extraInfos['uri']) ? $extraInfos['uri'] : REPOSITORY;
$uri = !empty($extraInfos['uri']) ? $extraInfos['uri'] : 'https://github.com/RSS-Bridge/rss-bridge';
$uriparts = parse_url($uri);
if(!empty($extraInfos['icon'])) {

View File

@@ -18,7 +18,7 @@ class MrssFormat extends FormatAbstract {
if(!empty($extraInfos['uri'])) {
$uri = $this->xml_encode($extraInfos['uri']);
} else {
$uri = REPOSITORY;
$uri = 'https://github.com/RSS-Bridge/rss-bridge';
}
$uriparts = parse_url($uri);

154
index.php
View File

@@ -1,5 +1,40 @@
<?php
require_once __DIR__ . '/lib/rssbridge.php';
/*
Create a file named 'DEBUG' for enabling debug mode.
For further security, you may put whitelisted IP addresses in the file,
one IP per line. Empty file allows anyone(!).
Debugging allows displaying PHP error messages and bypasses the cache: this
can allow a malicious client to retrieve data about your server and hammer
a provider throught your rss-bridge instance.
*/
if(file_exists('DEBUG')) {
$debug_whitelist = trim(file_get_contents('DEBUG'));
$debug_enabled = empty($debug_whitelist)
|| in_array($_SERVER['REMOTE_ADDR'],
explode("\n", str_replace("\r", '', $debug_whitelist)
)
);
if($debug_enabled) {
ini_set('display_errors', '1');
error_reporting(E_ALL);
define('DEBUG', true);
if (empty($debug_whitelist)) {
define('DEBUG_INSECURE', true);
}
}
}
require_once __DIR__ . '/lib/RssBridge.php';
define('PHP_VERSION_REQUIRED', '5.6.0');
// Specify directory for cached files (using FileCache)
define('CACHE_DIR', __DIR__ . '/cache');
// Specify path for whitelist file
define('WHITELIST_FILE', __DIR__ . '/whitelist.txt');
Configuration::verifyInstallation();
Configuration::loadConfiguration();
@@ -19,15 +54,13 @@ if (isset($argv)) {
$params = $_GET;
}
define('USER_AGENT',
'Mozilla/5.0 (X11; Linux x86_64; rv:30.0) Gecko/20121202 Firefox/30.0(rss-bridge/'
. Configuration::$VERSION
. ';+'
. REPOSITORY
. ')'
);
// FIXME : beta test UA spoofing, please report any blacklisting by PHP-fopen-unfriendly websites
ini_set('user_agent', USER_AGENT);
$userAgent = 'Mozilla/5.0(X11; Linux x86_64; rv:30.0)';
$userAgent .= ' Gecko/20121202 Firefox/30.0(rss-bridge/0.1;';
$userAgent .= '+https://github.com/RSS-Bridge/rss-bridge)';
ini_set('user_agent', $userAgent);
// default whitelist
$whitelist_default = array(
@@ -36,7 +69,7 @@ $whitelist_default = array(
'DansTonChatBridge',
'DuckDuckGoBridge',
'FacebookBridge',
'FlickrBridge',
'FlickrExploreBridge',
'GooglePlusPostBridge',
'GoogleSearchBridge',
'IdenticaBridge',
@@ -50,7 +83,26 @@ $whitelist_default = array(
try {
Bridge::setWhitelist($whitelist_default);
Bridge::setDir(__DIR__ . '/bridges/');
Format::setDir(__DIR__ . '/formats/');
Cache::setDir(__DIR__ . '/caches/');
if(!file_exists(WHITELIST_FILE)) {
$whitelist_selection = $whitelist_default;
$whitelist_write = implode("\n", $whitelist_default);
file_put_contents(WHITELIST_FILE, $whitelist_write);
} else {
$whitelist_file_content = file_get_contents(WHITELIST_FILE);
if($whitelist_file_content != "*\n") {
$whitelist_selection = explode("\n", $whitelist_file_content);
} else {
$whitelist_selection = Bridge::listBridges();
}
// Prepare for case-insensitive match
$whitelist_selection = array_map('strtolower', $whitelist_selection);
}
$showInactive = filter_input(INPUT_GET, 'show_inactive', FILTER_VALIDATE_BOOLEAN);
$action = array_key_exists('action', $params) ? $params['action'] : null;
@@ -63,7 +115,7 @@ try {
$list->bridges = array();
$list->total = 0;
foreach(Bridge::getBridgeNames() as $bridgeName) {
foreach(Bridge::listBridges() as $bridgeName) {
$bridge = Bridge::create($bridgeName);
@@ -77,7 +129,7 @@ try {
}
$status = Bridge::isWhitelisted($bridgeName) ? 'active' : 'inactive';
$status = Bridge::isWhitelisted($whitelist_selection, strtolower($bridgeName)) ? 'active' : 'inactive';
$list->bridges[$bridgeName] = array(
'status' => $status,
@@ -96,43 +148,12 @@ try {
header('Content-Type: application/json');
echo json_encode($list, JSON_PRETTY_PRINT);
} elseif($action === 'detect') {
$targetURL = $params['url']
or returnClientError('You must specify a url!');
$format = $params['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);
} elseif($action === 'display' && !empty($bridge)) {
// DEPRECATED: 'nameBridge' scheme is replaced by 'name' in bridge parameter values
// this is to keep compatibility until futher complete removal
if(($pos = strpos($bridge, 'Bridge')) === (strlen($bridge) - strlen('Bridge'))) {
$bridge = substr($bridge, 0, $pos);
}
$format = $params['format']
or returnClientError('You must specify a format!');
@@ -144,8 +165,8 @@ try {
}
// whitelist control
if(!Bridge::isWhitelisted($bridge)) {
throw new \Exception('This bridge is not whitelisted', 401);
if(!Bridge::isWhitelisted($whitelist_selection, strtolower($bridge))) {
throw new \HttpException('This bridge is not whitelisted', 401);
die;
}
@@ -162,10 +183,7 @@ try {
if(array_key_exists('_cache_timeout', $params)) {
if(!CUSTOM_CACHE_TIMEOUT) {
unset($params['_cache_timeout']);
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) . '?' . http_build_query($params);
header('Location: ' . $uri, true, 301);
die();
throw new \HttpException('This server doesn\'t support "_cache_timeout"!');
}
$cache_timeout = filter_var($params['_cache_timeout'], FILTER_VALIDATE_INT);
@@ -203,7 +221,7 @@ try {
// Initialize cache
$cache = Cache::create('FileCache');
$cache->setPath(PATH_CACHE);
$cache->setPath(CACHE_DIR);
$cache->purgeCache(86400); // 24 hours
$cache->setParameters($cache_params);
@@ -213,7 +231,7 @@ try {
if($mtime !== false
&& (time() - $cache_timeout < $mtime)
&& !Debug::isEnabled()) { // Load cached data
&& (!defined('DEBUG') || DEBUG !== true)) { // Load cached data
// Send "Not Modified" response if client supports it
// Implementation based on https://stackoverflow.com/a/10847262
@@ -221,7 +239,7 @@ try {
$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);
header('HTTP/1.1 304 Not Modified');
die();
}
}
@@ -260,8 +278,7 @@ try {
$item['title'] = 'Bridge returned error ' . $e->getCode() . '! (' . $params['_error_time'] . ')';
}
$item['uri'] = (isset($_SERVER['REQUEST_URI']) ? parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) : '')
. '?' . http_build_query($params);
$item['uri'] = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) . '?' . http_build_query($params);
$item['timestamp'] = time();
$item['content'] = buildBridgeException($e, $bridge);
@@ -274,8 +291,7 @@ try {
// Create "new" error message every 24 hours
$params['_error_time'] = urlencode((int)(time() / 86400));
$item['uri'] = (isset($_SERVER['REQUEST_URI']) ? parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) : '')
. '?' . http_build_query($params);
$item['uri'] = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) . '?' . http_build_query($params);
$item['title'] = 'Bridge returned error ' . $e->getCode() . '! (' . $params['_error_time'] . ')';
$item['timestamp'] = time();
$item['content'] = buildBridgeException($e, $bridge);
@@ -300,18 +316,24 @@ try {
$format->display();
} catch(Error $e) {
error_log($e);
header('Content-Type: text/html', true, $e->getCode());
http_response_code($e->getCode());
header('Content-Type: text/html');
die(buildTransformException($e, $bridge));
} catch(Exception $e) {
error_log($e);
header('Content-Type: text/html', true, $e->getCode());
http_response_code($e->getCode());
header('Content-Type: text/html');
die(buildTransformException($e, $bridge));
}
} else {
echo BridgeList::create($showInactive);
echo BridgeList::create($whitelist_selection, $showInactive);
}
} catch(HttpException $e) {
error_log($e);
http_response_code($e->getCode());
header('Content-Type: text/plain');
die($e->getMessage());
} catch(\Exception $e) {
error_log($e);
header('Content-Type: text/plain', true, $e->getCode());
die($e->getMessage());
}

View File

@@ -1,61 +1,12 @@
<?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.
*
* 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
*/
/**
* Authentication module for RSS-Bridge.
*
* This class implements an authentication module for RSS-Bridge, utilizing the
* HTTP authentication capabilities of PHP.
*
* _Notice_: Authentication via HTTP does not prevent users from accessing files
* on your server. If your server supports `.htaccess`, you should globally restrict
* access to files instead.
*
* @link https://php.net/manual/en/features.http-auth.php HTTP authentication with PHP
* @link https://httpd.apache.org/docs/2.4/howto/htaccess.html Apache HTTP Server
* Tutorial: .htaccess files
*
* @todo Configuration parameters should be stored internally instead of accessing
* the configuration class directly.
* @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()!');
}
/**
* 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);
header('WWW-Authenticate: Basic realm="RSS-Bridge"');
header('HTTP/1.0 401 Unauthorized');
die('Please authenticate in order to access this instance !');
}
@@ -63,13 +14,6 @@ class Authentication {
}
/**
* 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'])) {

View File

@@ -1,296 +1,88 @@
<?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.
*
* 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
*/
/**
* Factory class responsible for creating bridge objects from a given working
* directory, limited by a whitelist.
*
* This class is capable of:
* - Locating bridge classes in the specified working directory (see {@see Bridge::$workingDir})
* - Filtering bridges based on a whitelist (see {@see Bridge::$whitelist})
* - Creating new bridge instances based on the bridge's name (see {@see Bridge::create()})
*
* The following example illustrates the intended use for this class.
*
* ```PHP
* require_once __DIR__ . '/rssbridge.php';
*
* // Step 1: Set the working directory
* Bridge::setWorkingDir(__DIR__ . '/../bridges/');
*
* // Step 2: Add bridges to the whitelist
* Bridge::setWhitelist(array('GitHubIssue', 'GoogleSearch', 'Facebook', 'Twitter'));
*
* // Step 3: Create a new instance of a bridge (based on the name)
* $bridge = Bridge::create('GitHubIssue');
* ```
*/
require_once(__DIR__ . '/BridgeInterface.php');
class Bridge {
/**
* Holds a path to the working directory.
*
* Do not access this property directly!
* Use {@see Bridge::setWorkingDir()} and {@see Bridge::getWorkingDir()} instead.
*
* @var string|null
*/
protected static $workingDir = null;
static protected $dirBridge;
/**
* Holds a list of whitelisted bridges.
*
* Do not access this property directly!
* Use {@see Bridge::getWhitelist()} instead.
*
* @var array
*/
protected static $whitelist = array();
/**
* Throws an exception when trying to create a new instance of this class.
* Use {@see Bridge::create()} to instanciate a new bridge from the working
* directory.
*
* @throws \LogicException if called.
*/
public function __construct(){
throw new \LogicException('Use ' . __CLASS__ . '::create($name) to create bridge objects!');
throw new \LogicException('Please use ' . __CLASS__ . '::create for new object.');
}
/**
* Creates a new bridge object from the working directory.
*
* @throws \InvalidArgumentException if the requested bridge name is invalid.
* @throws \Exception if the requested bridge doesn't exist in the working
* directory.
* @param string $name Name of the bridge object.
* @return object|bool The bridge object or false if the class is not instantiable.
*/
public static function create($name){
if(!self::isBridgeName($name)) {
throw new \InvalidArgumentException('Bridge name invalid!');
* Create a new bridge object
* @param string $nameBridge Defined bridge name you want use
* @return Bridge object dedicated
*/
static public function create($nameBridge){
if(!preg_match('@^[A-Z][a-zA-Z0-9-]*$@', $nameBridge)) {
$message = <<<EOD
'nameBridge' must start with one uppercase character followed or not by
alphanumeric or dash characters!
EOD;
throw new \InvalidArgumentException($message);
}
$name = self::sanitizeBridgeName($name) . 'Bridge';
$filePath = self::getWorkingDir() . $name . '.php';
$nameBridge = $nameBridge . 'Bridge';
$pathBridge = self::getDir() . $nameBridge . '.php';
if(!file_exists($filePath)) {
throw new \Exception('Bridge file ' . $filePath . ' does not exist!');
if(!file_exists($pathBridge)) {
throw new \Exception('The bridge you looking for does not exist. It should be at path '
. $pathBridge);
}
require_once $filePath;
require_once $pathBridge;
if((new \ReflectionClass($name))->isInstantiable()) {
return new $name();
if((new ReflectionClass($nameBridge))->isInstantiable()) {
return new $nameBridge();
}
return false;
}
/**
* Sets the working directory.
*
* @param string $dir Path to the directory containing bridges.
* @throws \LogicException if the provided path is not a valid string.
* @throws \Exception if the provided path does not exist.
* @throws \InvalidArgumentException if $dir is not a directory.
* @return void
*/
public static function setWorkingDir($dir){
self::$workingDir = null;
if(!is_string($dir)) {
throw new \InvalidArgumentException('Working directory is not a valid string!');
static public function setDir($dirBridge){
if(!is_string($dirBridge)) {
throw new \InvalidArgumentException('Dir bridge must be a string.');
}
if(!file_exists($dir)) {
throw new \Exception('Working directory does not exist!');
if(!file_exists($dirBridge)) {
throw new \Exception('Dir bridge does not exist.');
}
if(!is_dir($dir)) {
throw new \InvalidArgumentException('Working directory is not a directory!');
self::$dirBridge = $dirBridge;
}
static public function getDir(){
if(is_null(self::$dirBridge)) {
throw new \LogicException(__CLASS__ . ' class need to know bridge path !');
}
self::$workingDir = realpath($dir) . '/';
return self::$dirBridge;
}
/**
* Returns the working directory.
* The working directory must be specified with {@see Bridge::setWorkingDir()}!
*
* @throws \LogicException if the working directory is not set.
* @return string The current working directory.
*/
public static function getWorkingDir(){
if(is_null(self::$workingDir)) {
throw new \LogicException('Working directory is not set!');
}
* Lists the available bridges.
* @return array List of the bridges
*/
static public function listBridges(){
$listBridge = array();
$dirFiles = scandir(self::getDir());
return self::$workingDir;
}
/**
* Returns true if the provided name is a valid bridge name.
*
* A valid bridge name starts with a capital letter ([A-Z]), followed by
* zero or more alphanumeric characters or hyphen ([A-Za-z0-9-]).
*
* @param string $name The bridge name.
* @return bool true if the name is a valid bridge name, false otherwise.
*/
public static function isBridgeName($name){
return is_string($name) && preg_match('/^[A-Z][a-zA-Z0-9-]*$/', $name) === 1;
}
/**
* Returns the list of bridge names from the working directory.
*
* The list is cached internally to allow for successive calls.
*
* @return array List of bridge names
*/
public static function getBridgeNames(){
static $bridgeNames = array(); // Initialized on first call
if(empty($bridgeNames)) {
$files = scandir(self::getWorkingDir());
if($files !== false) {
foreach($files as $file) {
if(preg_match('/^([^.]+)Bridge\.php$/U', $file, $out)) {
$bridgeNames[] = $out[1];
}
if($dirFiles !== false) {
foreach($dirFiles as $fileName) {
if(preg_match('@^([^.]+)Bridge\.php$@U', $fileName, $out)) {
$listBridge[] = $out[1];
}
}
}
return $bridgeNames;
return $listBridge;
}
/**
* Checks if a bridge is whitelisted.
*
* @param string $name Name of the bridge.
* @return bool True if the bridge is whitelisted.
*/
public static function isWhitelisted($name){
return in_array(self::sanitizeBridgeName($name), self::getWhitelist());
}
/**
* Returns the whitelist.
*
* On first call this function reads the whitelist from {@see WHITELIST}.
* * Each line in the file specifies one bridge on the whitelist.
* * An empty file disables all bridges.
* * If the file only only contains `*`, all bridges are whitelisted.
*
* Use {@see Bridge::setWhitelist()} to specify a default whitelist **before**
* calling this function! The list is cached internally to allow for
* successive calls. If {@see Bridge::setWhitelist()} gets called after this
* function, the whitelist is **not** updated again!
*
* @return array Array of whitelisted bridges
*/
public static function getWhitelist() {
static $firstCall = true; // Initialized on first call
if($firstCall) {
// Create initial whitelist or load from disk
if (!file_exists(WHITELIST) && !empty(self::$whitelist)) {
file_put_contents(WHITELIST, implode("\n", self::$whitelist));
} else {
$contents = trim(file_get_contents(WHITELIST));
if($contents === '*') { // Whitelist all bridges
self::$whitelist = self::getBridgeNames();
} else {
self::$whitelist = array_map('self::sanitizeBridgeName', explode("\n", $contents));
}
}
}
return self::$whitelist;
}
/**
* Sets the (default) whitelist.
*
* If this function is called **before** {@see Bridge::getWhitelist()}, the
* provided whitelist will be replaced by a custom whitelist specified in
* {@see WHITELIST} (if it exists).
*
* If this function is called **after** {@see Bridge::getWhitelist()}, the
* provided whitelist is taken as is (not updated by the custom whitelist
* again).
*
* @param array $default The whitelist as array of bridge names.
* @return void
*/
public static function setWhitelist($default = array()) {
self::$whitelist = array_map('self::sanitizeBridgeName', $default);
}
/**
* Returns the sanitized bridge name.
*
* The bridge name can be specified in various ways:
* * The PHP file name (i.e. `GitHubIssueBridge.php`)
* * The PHP file name without file extension (i.e. `GitHubIssueBridge`)
* * The bridge name (i.e. `GitHubIssue`)
*
* Casing is ignored (i.e. `GITHUBISSUE` and `githubissue` are the same).
*
* A bridge file matching the given bridge name must exist in the working
* directory!
*
* @param string $name The bridge name
* @return string|null The sanitized bridge name if the provided name is
* valid, null otherwise.
*/
protected static function sanitizeBridgeName($name) {
if(is_string($name)) {
// Trim trailing '.php' if exists
if(preg_match('/(.+)(?:\.php)/', $name, $matches)) {
$name = $matches[1];
}
// 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', self::getBridgeNames()))) {
$index = array_search(strtolower($name), array_map('strtolower', self::getBridgeNames()));
return self::getBridgeNames()[$index];
}
Debug::log('Invalid bridge name specified: "' . $name . '"!');
}
return null; // Bad parameter
static public function isWhitelisted($whitelist, $name){
return in_array($name, $whitelist)
|| in_array($name . '.php', $whitelist)
|| in_array($name . 'bridge', $whitelist) // DEPRECATED
|| in_array($name . 'bridge.php', $whitelist) // DEPRECATED
|| (count($whitelist) === 1 && trim($whitelist[0]) === '*');
}
}

View File

@@ -1,112 +1,32 @@
<?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.
*
* 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
*/
/**
* An abstract class for bridges
*
* This class implements {@see BridgeInterface} with most common functions in
* order to reduce code duplication. Bridges should inherit from this class
* instead of implementing the interface manually.
*
* @todo Move constants to the interface (this is supported by PHP)
* @todo Change visibility of constants to protected
* @todo Return `self` on more functions to allow chaining
* @todo Add specification for PARAMETERS ()
* @todo Add specification for $items
*/
require_once(__DIR__ . '/BridgeInterface.php');
abstract class BridgeAbstract implements BridgeInterface {
/**
* Name of the bridge
*
* Use {@see BridgeAbstract::getName()} to read this parameter
*/
const NAME = 'Unnamed bridge';
/**
* URI to the site the bridge is intended to be used for.
*
* Use {@see BridgeAbstract::getURI()} to read this parameter
*/
const URI = '';
/**
* A brief description of what the bridge can do
*
* Use {@see BridgeAbstract::getDescription()} to read this parameter
*/
const DESCRIPTION = 'No description provided';
/**
* The name of the maintainer. Multiple maintainers can be separated by comma
*
* Use {@see BridgeAbstract::getMaintainer()} to read this parameter
*/
const MAINTAINER = 'No maintainer';
/**
* The default cache timeout for the bridge
*
* Use {@see BridgeAbstract::getCacheTimeout()} to read this parameter
*/
const CACHE_TIMEOUT = 3600;
/**
* Parameters for the bridge
*
* Use {@see BridgeAbstract::getParameters()} to read this parameter
*/
const PARAMETERS = array();
/**
* Holds the list of items collected by the bridge
*
* Items must be collected by {@see BridgeInterface::collectData()}
*
* Use {@see BridgeAbstract::getItems()} to access items.
*
* @var array
*/
protected $items = array();
/**
* Holds the list of input parameters used by the bridge
*
* Do not access this parameter directly!
* Use {@see BridgeAbstract::setInputs()} and {@see BridgeAbstract::getInput()} instead!
*
* @var array
*/
protected $inputs = array();
/**
* Holds the name of the queried context
*
* @var string
*/
protected $queriedContext = '';
/** {@inheritdoc} */
/**
* Return items stored in the bridge
* @return mixed
*/
public function getItems(){
return $this->items;
}
/**
* Sets the input values for a given context.
* Sets the input values for a given context. Existing values are
* overwritten.
*
* @param array $inputs Associative array of inputs
* @param string $queriedContext The context name
* @return void
* @param string $context The context name
*/
protected function setInputs(array $inputs, $queriedContext){
// Import and assign all inputs to their context
@@ -183,15 +103,9 @@ abstract class BridgeAbstract implements BridgeInterface {
}
/**
* 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
*/
* Defined datas with parameters depending choose bridge
* @param array array with expected bridge paramters
*/
public function setDatas(array $inputs){
if(empty(static::PARAMETERS)) {
@@ -234,7 +148,7 @@ abstract class BridgeAbstract implements BridgeInterface {
* 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
* @return mixed Returns the input value or null if the input is not defined
*/
protected function getInput($input){
if(!isset($this->inputs[$this->queriedContext][$input]['value'])) {
@@ -243,52 +157,32 @@ abstract class BridgeAbstract implements BridgeInterface {
return $this->inputs[$this->queriedContext][$input]['value'];
}
/** {@inheritdoc} */
public function getDescription(){
return static::DESCRIPTION;
}
/** {@inheritdoc} */
public function getMaintainer(){
return static::MAINTAINER;
}
/** {@inheritdoc} */
public function getName(){
return static::NAME;
}
/** {@inheritdoc} */
public function getIcon(){
return '';
}
/** {@inheritdoc} */
public function getParameters(){
return static::PARAMETERS;
}
/** {@inheritdoc} */
public function getURI(){
return static::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;
}
}
}

View File

@@ -1,32 +1,6 @@
<?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.
*
* 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
*/
/**
* A generator class for a single bridge card on the home page of RSS-Bridge.
*
* This class generates the HTML content for a single bridge card for the home
* page of RSS-Bridge.
*
* @todo Return error if a caller creates an object of this class.
*/
final class BridgeCard {
/**
* Build a HTML document string of buttons for each of the provided formats
*
* @param array $formats A list of format names
* @return string The document string
*/
private static function buildFormatButtons($formats) {
$buttons = '';
@@ -42,13 +16,6 @@ final class BridgeCard {
return $buttons;
}
/**
* 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) {
$form = <<<EOD
<form method="GET" action="?">
@@ -64,24 +31,13 @@ This bridge is not fetching its content through a secure connection</div>';
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);
$form = BridgeCard::getFormHeader($bridgeName, $isHttps);
if(count($parameters) > 0) {
@@ -109,13 +65,13 @@ This bridge is not fetching its content through a secure connection</div>';
. PHP_EOL;
if(!isset($inputEntry['type']) || $inputEntry['type'] === 'text') {
$form .= self::getTextInput($inputEntry, $idArg, $id);
$form .= BridgeCard::getTextInput($inputEntry, $idArg, $id);
} elseif($inputEntry['type'] === 'number') {
$form .= self::getNumberInput($inputEntry, $idArg, $id);
$form .= BridgeCard::getNumberInput($inputEntry, $idArg, $id);
} else if($inputEntry['type'] === 'list') {
$form .= self::getListInput($inputEntry, $idArg, $id);
$form .= BridgeCard::getListInput($inputEntry, $idArg, $id);
} elseif($inputEntry['type'] === 'checkbox') {
$form .= self::getCheckboxInput($inputEntry, $idArg, $id);
$form .= BridgeCard::getCheckboxInput($inputEntry, $idArg, $id);
}
}
@@ -124,7 +80,7 @@ This bridge is not fetching its content through a secure connection</div>';
}
if($isActive) {
$form .= self::buildFormatButtons($formats);
$form .= BridgeCard::buildFormatButtons($formats);
} else {
$form .= '<span style="font-weight: bold;">Inactive</span>';
}
@@ -132,12 +88,6 @@ This bridge is not fetching its content through a secure connection</div>';
return $form . '</form>' . PHP_EOL;
}
/**
* Get input field attributes
*
* @param array $entry The current entry
* @return string The input field attributes
*/
private static function getInputAttributes($entry) {
$retVal = '';
@@ -153,17 +103,9 @@ This bridge is not fetching its content through a secure connection</div>';
return $retVal;
}
/**
* 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)
. BridgeCard::getInputAttributes($entry)
. ' id="'
. $id
. '" type="text" value="'
@@ -176,17 +118,9 @@ This bridge is not fetching its content through a secure connection</div>';
. PHP_EOL;
}
/**
* 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)
. BridgeCard::getInputAttributes($entry)
. ' id="'
. $id
. '" type="number" value="'
@@ -199,17 +133,9 @@ This bridge is not fetching its content through a secure connection</div>';
. 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) {
$list = '<select '
. self::getInputAttributes($entry)
. BridgeCard::getInputAttributes($entry)
. ' id="'
. $id
. '" name="'
@@ -259,17 +185,9 @@ This bridge is not fetching its content through a secure connection</div>';
return $list;
}
/**
* 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) {
return '<input '
. self::getInputAttributes($entry)
. BridgeCard::getInputAttributes($entry)
. ' id="'
. $id
. '" type="checkbox" name="'
@@ -280,14 +198,6 @@ This bridge is not fetching its content through a secure connection</div>';
. PHP_EOL;
}
/**
* 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
*/
static function displayBridgeCard($bridgeName, $formats, $isActive = true){
$bridge = Bridge::create($bridgeName);
@@ -330,7 +240,7 @@ CARD;
if(count($parameters) === 0
|| count($parameters) === 1 && array_key_exists('global', $parameters)) {
$card .= self::getForm($bridgeName, $formats, $isActive, $isHttps);
$card .= BridgeCard::getForm($bridgeName, $formats, $isActive, $isHttps);
} else {
@@ -344,7 +254,7 @@ CARD;
if(!is_numeric($parameterName))
$card .= '<h5>' . $parameterName . '</h5>' . PHP_EOL;
$card .= self::getForm($bridgeName, $formats, $isActive, $isHttps, $parameterName, $parameter);
$card .= BridgeCard::getForm($bridgeName, $formats, $isActive, $isHttps, $parameterName, $parameter);
}
}

View File

@@ -1,57 +1,4 @@
<?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.
*
* 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
*/
/**
* The bridge interface
*
* A bridge is a class that is responsible for collecting and transforming data
* from one hosting provider into an internal representation of feed data, that
* can later be transformed into different feed formats (see {@see FormatInterface}).
*
* For this purpose, all bridges need to perform three common operations:
*
* 1. Collect data from a remote site.
* 2. Extract the required contents.
* 3. Add the contents to the internal data structure.
*
* Bridges can optionally specify parameters to customize bridge behavior based
* on user input. For example, a user could specify how many items to return in
* the feed and where to get them.
*
* In order to present a bridge on the home page, and for the purpose of bridge
* specific behaviour, additional information must be provided by the bridge:
*
* * **Name**
* The name of the bridge that can be displayed to users.
*
* * **Description**
* A brief description for the bridge that can be displayed to users.
*
* * **URI**
* A link to the hosting provider.
*
* * **Maintainer**
* The GitHub username of the bridge maintainer
*
* * **Parameters**
* A list of parameters for customization
*
* * **Icon**
* A link to the favicon of the hosting provider
*
* * **Cache timeout**
* The default cache timeout for the bridge.
*/
interface BridgeInterface {
/**
@@ -114,12 +61,4 @@ interface BridgeInterface {
* @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);
}

View File

@@ -1,31 +1,6 @@
<?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.
*
* 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
*/
/**
* A generator class for the home page of RSS-Bridge.
*
* This class generates the HTML content for displaying all bridges on the home
* page of RSS-Bridge.
*
* @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
<head>
@@ -47,29 +22,20 @@ final class BridgeList {
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) {
private static function getBridges($whitelist, $showInactive, &$totalBridges, &$totalActiveBridges) {
$body = '';
$totalActiveBridges = 0;
$inactiveBridges = '';
$bridgeList = Bridge::getBridgeNames();
$formats = Format::getFormatNames();
$bridgeList = Bridge::listBridges();
$formats = Format::searchInformation();
$totalBridges = count($bridgeList);
foreach($bridgeList as $bridgeName) {
if(Bridge::isWhitelisted($bridgeName)) {
if(Bridge::isWhitelisted($whitelist, strtolower($bridgeName))) {
$body .= BridgeCard::displayBridgeCard($bridgeName, $formats);
$totalActiveBridges++;
@@ -88,24 +54,19 @@ EOD;
return $body;
}
/**
* Get the document header
*
* @return string The document header
*/
private static function getHeader() {
$warning = '';
if(Debug::isEnabled()) {
if(!Debug::isSecure()) {
if(defined('DEBUG') && DEBUG === true) {
if(defined('DEBUG_INSECURE') && DEBUG_INSECURE === true) {
$warning .= <<<EOD
<section class="critical-warning">Warning : Debug mode is active from any location,
make sure only you can access RSS-Bridge.</section>
<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
<section class="warning">Warning : Debug mode is active from your IP address,
your requests will bypass the cache.</section>
<section class="warning">Warning : Debug mode is active from your IP address,
your requests will bypass the cache.</section>
EOD;
}
}
@@ -119,11 +80,6 @@ EOD;
EOD;
}
/**
* Get the searchbar
*
* @return string The searchbar
*/
private static function getSearchbar() {
$query = filter_input(INPUT_GET, 'q');
@@ -137,16 +93,6 @@ EOD;
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();
@@ -185,14 +131,7 @@ EOD;
EOD;
}
/**
* Create the entire home page
*
* @param bool $showInactive Inactive bridges are displayed on the home page,
* if enabled.
* @return string The home page
*/
static function create($showInactive = true) {
static function create($whitelist, $showInactive = true) {
$totalBridges = 0;
$totalActiveBridges = 0;
@@ -202,7 +141,7 @@ EOD;
. '<body onload="search()">'
. BridgeList::getHeader()
. BridgeList::getSearchbar()
. BridgeList::getBridges($showInactive, $totalBridges, $totalActiveBridges)
. BridgeList::getBridges($whitelist, $showInactive, $totalBridges, $totalActiveBridges)
. BridgeList::getFooter($totalBridges, $totalActiveBridges, $showInactive)
. '</body></html>';

View File

@@ -1,140 +1,53 @@
<?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.
*
* 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
*/
/**
* Factory class responsible for creating cache objects from a given working
* directory.
*
* This class is capable of:
* - Locating cache classes in the specified working directory (see {@see Cache::$workingDir})
* - Creating new cache instances based on the cache's name (see {@see Cache::create()})
*
* The following example illustrates the intended use for this class.
*
* ```PHP
* require_once __DIR__ . '/rssbridge.php';
*
* // Step 1: Set the working directory
* Cache::setWorkingDir(__DIR__ . '/../caches/');
*
* // Step 2: Create a new instance of a cache object (based on the name)
* $cache = Cache::create('FileCache');
* ```
*/
require_once(__DIR__ . '/CacheInterface.php');
class Cache {
/**
* Holds a path to the working directory.
*
* Do not access this property directly!
* Use {@see Cache::setWorkingDir()} and {@see Cache::getWorkingDir()} instead.
*
* @var string|null
*/
protected static $workingDir = null;
static protected $dirCache;
/**
* Throws an exception when trying to create a new instance of this class.
* Use {@see Cache::create()} to create a new cache object from the working
* directory.
*
* @throws \LogicException if called.
*/
public function __construct(){
throw new \LogicException('Use ' . __CLASS__ . '::create($name) to create cache objects!');
throw new \LogicException('Please use ' . __CLASS__ . '::create for new object.');
}
/**
* Creates a new cache object from the working directory.
*
* @throws \InvalidArgumentException if the requested cache name is invalid.
* @throws \Exception if the requested cache file doesn't exist in the
* working directory.
* @param string $name Name of the cache object.
* @return object|bool The cache object or false if the class is not instantiable.
*/
public static function create($name){
if(!self::isCacheName($name)) {
throw new \InvalidArgumentException('Cache name invalid!');
static public function create($nameCache){
if(!static::isValidNameCache($nameCache)) {
throw new \InvalidArgumentException('Name cache must be at least one
uppercase follow or not by alphanumeric or dash characters.');
}
$filePath = self::getWorkingDir() . $name . '.php';
$pathCache = self::getDir() . $nameCache . '.php';
if(!file_exists($filePath)) {
throw new \Exception('Cache file ' . $filePath . ' does not exist!');
if(!file_exists($pathCache)) {
throw new \Exception('The cache you looking for does not exist.');
}
require_once $filePath;
require_once $pathCache;
if((new \ReflectionClass($name))->isInstantiable()) {
return new $name();
}
return false;
return new $nameCache();
}
/**
* Sets the working directory.
*
* @param string $dir Path to a directory containing cache classes
* @throws \InvalidArgumentException if $dir is not a string.
* @throws \Exception if the working directory doesn't exist.
* @throws \InvalidArgumentException if $dir is not a directory.
* @return void
*/
public static function setWorkingDir($dir){
self::$workingDir = null;
if(!is_string($dir)) {
throw new \InvalidArgumentException('Working directory is not a valid string!');
static public function setDir($dirCache){
if(!is_string($dirCache)) {
throw new \InvalidArgumentException('Dir cache must be a string.');
}
if(!file_exists($dir)) {
throw new \Exception('Working directory does not exist!');
if(!file_exists($dirCache)) {
throw new \Exception('Dir cache does not exist.');
}
if(!is_dir($dir)) {
throw new \InvalidArgumentException('Working directory is not a directory!');
}
self::$workingDir = realpath($dir) . '/';
self::$dirCache = $dirCache;
}
/**
* Returns the working directory.
* The working directory must be set with {@see Cache::setWorkingDir()}!
*
* @throws \LogicException if the working directory is not set.
* @return string The current working directory.
*/
public static function getWorkingDir(){
if(is_null(self::$workingDir)) {
throw new \LogicException('Working directory is not set!');
static public function getDir(){
$dirCache = self::$dirCache;
if(is_null($dirCache)) {
throw new \LogicException(__CLASS__ . ' class need to know cache path !');
}
return self::$workingDir;
return $dirCache;
}
/**
* Returns true if the provided name is a valid cache name.
*
* A valid cache name starts with a capital letter ([A-Z]), followed by
* zero or more alphanumeric characters or hyphen ([A-Za-z0-9-]).
*
* @param string $name The cache name.
* @return bool true if the name is a valid cache name, false otherwise.
*/
public static function isCacheName($name){
return is_string($name) && preg_match('/^[A-Z][a-zA-Z0-9-]*$/', $name) === 1;
static public function isValidNameCache($nameCache){
return preg_match('@^[A-Z][a-zA-Z0-9-]*$@', $nameCache);
}
}

View File

@@ -1,51 +1,7 @@
<?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.
*
* 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
*/
/**
* The cache interface
*
* @todo Add missing function to the interface
* @todo Explain parameters and return values in more detail
* @todo Return self more often (to allow call chaining)
*/
interface CacheInterface {
/**
* Loads data from cache
*
* @return mixed The cache data
*/
public function loadData();
/**
* Stores data to the cache
*
* @param mixed $datas The data to store
* @return self The cache object
*/
public function saveData($datas);
/**
* Returns the timestamp for the curent cache file
*
* @return int Timestamp
*/
public function getTime();
/**
* Removes any data that is older than the specified duration from cache
*
* @param int $duration The cache duration in seconds
*/
public function purgeCache($duration);
}

View File

@@ -1,86 +1,15 @@
<?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.
*
* 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
*/
class Configuration {
/**
* Configuration module for RSS-Bridge.
*
* This class implements a configuration module for RSS-Bridge.
*/
final class Configuration {
public static $VERSION = 'dev.2018-10-15';
/**
* 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 = '2018-12-11';
public 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!');
}
/**
* 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 5.6.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, '5.6.0') === -1)
die('RSS-Bridge requires at least PHP version 5.6.0!');
if(version_compare(PHP_VERSION, PHP_VERSION_REQUIRED) === -1)
die('RSS-Bridge requires at least PHP version ' . PHP_VERSION_REQUIRED . '!');
// extensions check
if(!extension_loaded('openssl'))
@@ -102,53 +31,27 @@ final class Configuration {
die('"json" extension not loaded. Please check "php.ini"');
// Check cache folder permissions (write permissions required)
if(!is_writable(PATH_CACHE))
die('RSS-Bridge does not have write permissions for ' . PATH_CACHE . '!');
if(!is_writable(CACHE_DIR))
die('RSS-Bridge does not have write permissions for ' . CACHE_DIR . '!');
// Check whitelist file permissions
if(!file_exists(WHITELIST) && !is_writable(dirname(WHITELIST)))
die('RSS-Bridge does not have write permissions for ' . WHITELIST . '!');
// Check whitelist file permissions (only in DEBUG mode)
if(!file_exists(WHITELIST_FILE) && !is_writable(dirname(WHITELIST_FILE)))
die('RSS-Bridge does not have write permissions for ' . WHITELIST_FILE . '!');
}
/**
* 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:
* - `config.default.ini.php`: The default configuration file that ships with
* every release of RSS-Bridge (do not modify this file!).
* - `config.ini.php`: The local configuration file that can be modified by
* server administrators.
*
* The files must be located at {@see PATH_ROOT}
*
* RSS-Bridge will first load `config.default.ini.php` into memory and then
* replace parameters with the contents of `config.ini.php`. 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(PATH_ROOT . 'config.default.ini.php'))
if(!file_exists('config.default.ini.php'))
die('The default configuration file "config.default.ini.php" is missing!');
Configuration::$config = parse_ini_file(PATH_ROOT . 'config.default.ini.php', true, INI_SCANNER_TYPED);
Configuration::$config = parse_ini_file('config.default.ini.php', true, INI_SCANNER_TYPED);
if(!Configuration::$config)
die('Error parsing config.default.ini.php');
if(file_exists(PATH_ROOT . 'config.ini.php')) {
if(file_exists('config.ini.php')) {
// Replace default configuration with custom settings
foreach(parse_ini_file(PATH_ROOT . 'config.ini.php', true, INI_SCANNER_TYPED) as $header => $section) {
foreach(parse_ini_file('config.ini.php', true, INI_SCANNER_TYPED) as $header => $section) {
foreach($section as $key => $value) {
// Skip unknown sections and keys
if(array_key_exists($header, Configuration::$config) && array_key_exists($key, Configuration::$config[$header])) {
@@ -161,27 +64,22 @@ final class Configuration {
if(!is_string(self::getConfig('proxy', 'url')))
die('Parameter [proxy] => "url" is not a valid string! Please check "config.ini.php"!');
if(!empty(self::getConfig('proxy', 'url'))) {
/** URL of the proxy server */
if(!empty(self::getConfig('proxy', 'url')))
define('PROXY_URL', self::getConfig('proxy', 'url'));
}
if(!is_bool(self::getConfig('proxy', 'by_bridge')))
die('Parameter [proxy] => "by_bridge" is not a valid Boolean! Please check "config.ini.php"!');
/** 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', 'name')))
die('Parameter [proxy] => "name" is not a valid string! Please check "config.ini.php"!');
/** Name of the proxy server */
define('PROXY_NAME', self::getConfig('proxy', 'name'));
if(!is_bool(self::getConfig('cache', 'custom_timeout')))
die('Parameter [cache] => "custom_timeout" is not a valid Boolean! Please check "config.ini.php"!');
/** True if the cache timeout can be specified by the user */
define('CUSTOM_CACHE_TIMEOUT', self::getConfig('cache', 'custom_timeout'));
if(!is_bool(self::getConfig('authentication', 'enable')))
@@ -193,41 +91,21 @@ final class Configuration {
if(!is_string(self::getConfig('authentication', 'password')))
die('Parameter [authentication] => "password" is not a valid string! Please check "config.ini.php"!');
if(!empty(self::getConfig('admin', 'email'))
&& !filter_var(self::getConfig('admin', 'email'), FILTER_VALIDATE_EMAIL))
die('Parameter [admin] => "email" is not a valid email address! Please check "config.ini.php"!');
}
/**
* 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) {
public static function getConfig($category, $key) {
if(array_key_exists($section, self::$config) && array_key_exists($key, self::$config[$section])) {
return self::$config[$section][$key];
if(array_key_exists($category, self::$config) && array_key_exists($key, self::$config[$category])) {
return self::$config[$category][$key];
}
return null;
}
/**
* 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';
$headFile = '.git/HEAD';
// '@' is used to mute open_basedir warning
if(@is_readable($headFile)) {

View File

@@ -1,121 +0,0 @@
<?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.
*
* 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
*/
/**
* Implements functions for debugging purposes. Debugging can be enabled by
* placing a file named DEBUG in {@see PATH_ROOT}.
*
* The file specifies a whitelist of IP addresses on which debug mode will be
* enabled. An empty file enables debug mode for everyone (highly discouraged
* for public servers!). Each line in the file specifies one client in the
* whitelist. For example:
*
* * `192.168.1.72`
* * `127.0.0.1`
* * `::1`
*
* Notice: If you are running RSS-Bridge on your local machine, you need to add
* localhost (either `127.0.0.1` for IPv4 or `::1` for IPv6) to your whitelist!
*
* Warning: In debug mode your server may display sensitive information! For
* security reasons it is recommended to whitelist only specific IP addresses.
*/
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 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
if($firstCall && file_exists(PATH_ROOT . 'DEBUG')) {
$debug_whitelist = trim(file_get_contents(PATH_ROOT . 'DEBUG'));
self::$enabled = empty($debug_whitelist) || in_array($_SERVER['REMOTE_ADDR'],
explode("\n", str_replace("\r", '', $debug_whitelist)
)
);
if(self::$enabled) {
ini_set('display_errors', '1');
error_reporting(E_ALL);
self::$secure = !empty($debug_whitelist);
}
$firstCall = false; // Skip check on next call
}
return self::$enabled;
}
/**
* 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);
}
}

View File

@@ -1,28 +1,17 @@
<?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.
*
* 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
*/
class HttpException extends \Exception{}
/**
* Returns an URL that automatically populates a new issue on GitHub based
* on the information provided
*
* @param string $title string Sets the title of the issue
* @param string $body string Sets the body of the issue (GitHub markdown applies)
* @param string $labels mixed (optional) Specifies labels to add to the issue
* @param string $maintainer string (optional) Specifies the maintainer for the issue.
* @param $title string Sets the title of the issue
* @param $body string Sets the body of the issue (GitHub markdown applies)
* @param $labels mixed (optional) Specifies labels to add to the issue
* @param $maintainer string (optional) Specifies the maintainer for the issue.
* The maintainer only applies if part of the development team!
* @return string|null A qualified URL to a new issue with populated conent or null.
*
* @todo This function belongs inside a class
* @return string Returns a qualified URL to a new issue with populated conent.
* Returns null if title or body is null or empty
*/
function buildGitHubIssueQuery($title, $body, $labels = null, $maintainer = null){
if(!isset($title) || !isset($body) || empty($title) || empty($body)) {
@@ -30,8 +19,7 @@ function buildGitHubIssueQuery($title, $body, $labels = null, $maintainer = null
}
// Add title and body
$uri = REPOSITORY
. 'issues/new?title='
$uri = 'https://github.com/rss-bridge/rss-bridge/issues/new?title='
. urlencode($title)
. '&body='
. urlencode($body);
@@ -60,11 +48,10 @@ function buildGitHubIssueQuery($title, $body, $labels = null, $maintainer = null
/**
* Returns the exception message as HTML string
*
* @param object $e Exception The exception to show
* @param object $bridge object The bridge object
* @return string|null Returns the exception as HTML string or null.
*
* @todo This function belongs inside a class
* @param $e Exception The exception to show
* @param $bridge object The bridge object
* @return string Returns the exception as HTML string. Returns null if the
* provided parameter are invalid
*/
function buildBridgeException($e, $bridge){
if(( !($e instanceof \Exception) && !($e instanceof \Error)) || !($bridge instanceof \BridgeInterface)) {
@@ -77,7 +64,7 @@ function buildBridgeException($e, $bridge){
$body = 'Error message: `'
. $e->getMessage()
. "`\nQuery string: `"
. (isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '')
. $_SERVER['QUERY_STRING']
. "`\nVersion: `"
. Configuration::getVersion()
. '`';
@@ -99,11 +86,10 @@ EOD;
/**
* Returns the exception message as HTML string
*
* @param object $e Exception The exception to show
* @param object $bridge object The bridge object
* @return string|null Returns the exception as HTML string or null.
*
* @todo This function belongs inside a class
* @param $e Exception The exception to show
* @param $bridge object The bridge object
* @return string Returns the exception as HTML string. Returns null if the
* provided parameter are invalid
*/
function buildTransformException($e, $bridge){
if(( !($e instanceof \Exception) && !($e instanceof \Error)) || !($bridge instanceof \BridgeInterface)) {
@@ -116,8 +102,7 @@ function buildTransformException($e, $bridge){
$body = 'Error message: `'
. $e->getMessage()
. "`\nQuery string: `"
. (isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '')
. '`';
. $_SERVER['QUERY_STRING'] . '`';
$link = buildGitHubIssueQuery($title, $body, 'bug report', $bridge->getMaintainer());
$header = buildHeader($e, $bridge);
@@ -128,15 +113,6 @@ function buildTransformException($e, $bridge){
return buildPage($title, $header, $section);
}
/**
* Builds a new HTML header with data from a exception an a bridge
*
* @param object $e The exception object
* @param object $bridge The bridge object
* @return string The HTML header
*
* @todo This function belongs inside a class
*/
function buildHeader($e, $bridge){
return <<<EOD
<header>
@@ -147,17 +123,6 @@ function buildHeader($e, $bridge){
EOD;
}
/**
* Builds a new HTML section
*
* @param object $e The exception object
* @param object $bridge The bridge object
* @param string $message The message to display
* @param string $link The link to include in the anchor
* @return string The HTML section
*
* @todo This function belongs inside a class
*/
function buildSection($e, $bridge, $message, $link){
return <<<EOD
<section>
@@ -176,16 +141,6 @@ function buildSection($e, $bridge, $message, $link){
EOD;
}
/**
* Builds a new HTML page
*
* @param string $title The HTML title
* @param string $header The HTML header
* @param string $section The HTML section
* @return string The HTML page
*
* @todo This function belongs inside a class
*/
function buildPage($title, $header, $section){
return <<<EOD
<!DOCTYPE html>

View File

@@ -1,86 +1,17 @@
<?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.
*
* 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
*/
/**
* An abstract class for bridges that need to transform existing RSS or Atom
* feeds.
*
* This class extends {@see BridgeAbstract} with functions to extract contents
* from existing RSS or Atom feeds. Bridges that need to transform existing feeds
* should inherit from this class instead of {@see BridgeAbstract}.
*
* Bridges that extend this class don't need to concern themselves with getting
* contents from existing feeds, but can focus on adding additional contents
* (i.e. by downloading additional data), filtering or just transforming a feed
* into another format.
*
* @link http://www.rssboard.org/rss-0-9-1 RSS 0.91 Specification
* @link http://web.resource.org/rss/1.0/spec RDF Site Summary (RSS) 1.0
* @link http://www.rssboard.org/rss-specification RSS 2.0 Specification
* @link https://tools.ietf.org/html/rfc4287 The Atom Syndication Format
*
* @todo The parsing functions should all be private. This class is complicated
* enough without having to consider children overriding functions.
*/
require_once(__DIR__ . '/BridgeInterface.php');
abstract class FeedExpander extends BridgeAbstract {
/** 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 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 URI of the feed
*
* @var string
*/
private $name;
private $uri;
/**
* 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');
}
Debug::log('Loading from ' . $url);
debugMessage('Loading from ' . $url);
/* Notice we do not use cache here on purpose:
* we want a fresh view of the RSS stream each time
@@ -89,49 +20,34 @@ abstract class FeedExpander extends BridgeAbstract {
or returnServerError('Could not request ' . $url);
$rssContent = simplexml_load_string(trim($content));
Debug::log('Detecting feed format/version');
debugMessage('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;
debugMessage('Detected RSS 1.0 format');
$this->feedType = 'RSS_1_0';
break;
case isset($rssContent->channel[0]):
Debug::log('Detected RSS 0.9x or 2.0 format');
$this->feedType = self::FEED_TYPE_RSS_2_0;
debugMessage('Detected RSS 0.9x or 2.0 format');
$this->feedType = 'RSS_2_0';
break;
case isset($rssContent->entry[0]):
Debug::log('Detected ATOM format');
$this->feedType = self::FEED_TYPE_ATOM_1_0;
debugMessage('Detected ATOM format');
$this->feedType = 'ATOM_1_0';
break;
default:
Debug::log('Unknown feed format/version');
debugMessage('Unknown feed format/version');
returnServerError('The feed format is unknown!');
break;
}
Debug::log('Calling function "collect_' . $this->feedType . '_data"');
debugMessage('Calling function "collect_' . $this->feedType . '_data"');
$this->{'collect_' . $this->feedType . '_data'}($rssContent, $maxItems);
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 collect_RSS_1_0_data($rssContent, $maxItems){
$this->load_RSS_2_0_feed_data($rssContent->channel[0]);
foreach($rssContent->item as $item) {
Debug::log('parsing item ' . var_export($item, true));
debugMessage('parsing item ' . var_export($item, true));
$tmp_item = $this->parseItem($item);
if (!empty($tmp_item)) {
$this->items[] = $tmp_item;
@@ -140,28 +56,15 @@ abstract class FeedExpander extends BridgeAbstract {
}
}
/**
* 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 collect_RSS_2_0_data($rssContent, $maxItems){
$rssContent = $rssContent->channel[0];
Debug::log('RSS content is ===========\n'
debugMessage('RSS content is ===========\n'
. var_export($rssContent, true)
. '===========');
$this->load_RSS_2_0_feed_data($rssContent);
foreach($rssContent->item as $item) {
Debug::log('parsing item ' . var_export($item, true));
debugMessage('parsing item ' . var_export($item, true));
$tmp_item = $this->parseItem($item);
if (!empty($tmp_item)) {
$this->items[] = $tmp_item;
@@ -170,23 +73,10 @@ abstract class FeedExpander extends BridgeAbstract {
}
}
/**
* 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 collect_ATOM_1_0_data($content, $maxItems){
$this->load_ATOM_feed_data($content);
foreach($content->entry as $item) {
Debug::log('parsing item ' . var_export($item, true));
debugMessage('parsing item ' . var_export($item, true));
$tmp_item = $this->parseItem($item);
if (!empty($tmp_item)) {
$this->items[] = $tmp_item;
@@ -195,37 +85,18 @@ abstract class FeedExpander extends BridgeAbstract {
}
}
/**
* Convert RSS 2.0 time to timestamp
*
* @param object $item A feed item
* @return int The timestamp
*/
protected function RSS_2_0_time_to_timestamp($item){
return DateTime::createFromFormat('D, d M Y H:i:s e', $item->pubDate)->getTimestamp();
}
/**
* 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
*/
// TODO set title, link, description, language, and so on
protected function load_RSS_2_0_feed_data($rssContent){
$this->title = trim((string)$rssContent->title);
$this->name = trim((string)$rssContent->title);
$this->uri = trim((string)$rssContent->link);
}
/**
* Load Atom feed data into RSS-Bridge
*
* @param object $content The Atom content
* @return void
*/
protected function load_ATOM_feed_data($content){
$this->title = (string)$content->title;
$this->name = (string)$content->title;
// Find best link (only one, or first of 'alternate')
if(!isset($content->link)) {
@@ -243,16 +114,6 @@ abstract class FeedExpander extends BridgeAbstract {
}
}
/**
* 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->parseRSS_2_0_Item($feedItem);
@@ -278,16 +139,6 @@ abstract class FeedExpander extends BridgeAbstract {
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 parseRSS_0_9_1_Item($feedItem){
$item = array();
if(isset($feedItem->link)) $item['uri'] = (string)$feedItem->link;
@@ -299,16 +150,6 @@ abstract class FeedExpander extends BridgeAbstract {
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 parseRSS_1_0_Item($feedItem){
// 1.0 adds optional elements around the 0.91 standard
$item = $this->parseRSS_0_9_1_Item($feedItem);
@@ -323,16 +164,6 @@ abstract class FeedExpander extends BridgeAbstract {
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 parseRSS_2_0_Item($feedItem){
// Primary data is compatible to 0.91 with some additional data
$item = $this->parseRSS_0_9_1_Item($feedItem);
@@ -380,38 +211,33 @@ abstract class FeedExpander extends BridgeAbstract {
}
/**
* 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
* Method should return, from a source RSS item given by lastRSS, one of our Items objects
* @param $item the input rss item
* @return a RSS-Bridge Item, with (hopefully) the whole content)
*/
protected function parseItem($item){
switch($this->feedType) {
case self::FEED_TYPE_RSS_1_0:
case 'RSS_1_0':
return $this->parseRSS_1_0_Item($item);
break;
case self::FEED_TYPE_RSS_2_0:
case 'RSS_2_0':
return $this->parseRSS_2_0_Item($item);
break;
case self::FEED_TYPE_ATOM_1_0:
case '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 getName(){
return !empty($this->title) ? $this->title : parent::getName();
return !empty($this->name) ? $this->name : parent::getName();
}
/** {@inheritdoc} */
public function getIcon(){
return !empty($this->icon) ? $this->icon : parent::getIcon();
}

View File

@@ -1,166 +1,73 @@
<?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.
*
* 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
*/
/**
* Factory class responsible for creating format objects from a given working
* directory.
*
* This class is capable of:
* - Locating format classes in the specified working directory (see {@see Format::$workingDir})
* - Creating new format instances based on the format's name (see {@see Format::create()})
*
* The following example illustrates the intended use for this class.
*
* ```PHP
* require_once __DIR__ . '/rssbridge.php';
*
* // Step 1: Set the working directory
* Format::setWorkingDir(__DIR__ . '/../formats/');
*
* // Step 2: Create a new instance of a format object (based on the name)
* $format = Format::create('Atom');
* ```
*/
require_once(__DIR__ . '/FormatInterface.php');
class Format {
/**
* Holds a path to the working directory.
*
* Do not access this property directly!
* Use {@see Format::setWorkingDir()} and {@see Format::getWorkingDir()} instead.
*
* @var string|null
*/
protected static $workingDir = null;
static protected $dirFormat;
/**
* Throws an exception when trying to create a new instance of this class.
* Use {@see Format::create()} to create a new format object from the working
* directory.
*
* @throws \LogicException if called.
*/
public function __construct(){
throw new \LogicException('Use ' . __CLASS__ . '::create($name) to create cache objects!');
throw new \LogicException('Please use ' . __CLASS__ . '::create for new object.');
}
/**
* Creates a new format object from the working directory.
*
* @throws \InvalidArgumentException if the requested format name is invalid.
* @throws \Exception if the requested format file doesn't exist in the
* working directory.
* @param string $name Name of the format object.
* @return object|bool The format object or false if the class is not instantiable.
*/
public static function create($name){
if(!self::isFormatName($name)) {
throw new \InvalidArgumentException('Format name invalid!');
static public function create($nameFormat){
if(!preg_match('@^[A-Z][a-zA-Z]*$@', $nameFormat)) {
throw new \InvalidArgumentException('Name format must be at least
one uppercase follow or not by alphabetic characters.');
}
$name = $name . 'Format';
$pathFormat = self::getWorkingDir() . $name . '.php';
$nameFormat = $nameFormat . 'Format';
$pathFormat = self::getDir() . $nameFormat . '.php';
if(!file_exists($pathFormat)) {
throw new \Exception('Format file ' . $filePath . ' does not exist!');
throw new \Exception('The format you looking for does not exist.');
}
require_once $pathFormat;
if((new \ReflectionClass($name))->isInstantiable()) {
return new $name();
}
return false;
return new $nameFormat();
}
/**
* Sets the working directory.
*
* @param string $dir Path to a directory containing cache classes
* @throws \InvalidArgumentException if $dir is not a string.
* @throws \Exception if the working directory doesn't exist.
* @throws \InvalidArgumentException if $dir is not a directory.
* @return void
*/
public static function setWorkingDir($dir){
self::$workingDir = null;
if(!is_string($dir)) {
static public function setDir($dirFormat){
if(!is_string($dirFormat)) {
throw new \InvalidArgumentException('Dir format must be a string.');
}
if(!file_exists($dir)) {
throw new \Exception('Working directory does not exist!');
if(!file_exists($dirFormat)) {
throw new \Exception('Dir format does not exist.');
}
if(!is_dir($dir)) {
throw new \InvalidArgumentException('Working directory is not a directory!');
self::$dirFormat = $dirFormat;
}
static public function getDir(){
$dirFormat = self::$dirFormat;
if(is_null($dirFormat)) {
throw new \LogicException(__CLASS__ . ' class need to know format path !');
}
self::$workingDir = realpath($dir) . '/';
return $dirFormat;
}
/**
* Returns the working directory.
* The working directory must be set with {@see Format::setWorkingDir()}!
*
* @throws \LogicException if the working directory is not set.
* @return string The current working directory.
*/
public static function getWorkingDir(){
if(is_null(self::$workingDir)) {
throw new \LogicException('Working directory is not set!');
}
* Read format dir and catch informations about each format depending annotation
* @return array Informations about each format
*/
static public function searchInformation(){
$pathDirFormat = self::getDir();
return self::$workingDir;
}
$listFormat = array();
/**
* Returns true if the provided name is a valid format name.
*
* A valid format name starts with a capital letter ([A-Z]), followed by
* zero or more alphanumeric characters or hyphen ([A-Za-z0-9-]).
*
* @param string $name The format name.
* @return bool true if the name is a valid format name, false otherwise.
*/
public static function isFormatName($name){
return is_string($name) && preg_match('/^[A-Z][a-zA-Z0-9-]*$/', $name) === 1;
}
$searchCommonPattern = array('name');
/**
* Returns the list of format names from the working directory.
*
* The list is cached internally to allow for successive calls.
*
* @return array List of format names
*/
public static function getFormatNames(){
static $formatNames = array(); // Initialized on first call
if(empty($formatNames)) {
$files = scandir(self::getWorkingDir());
if($files !== false) {
foreach($files as $file) {
if(preg_match('/^([^.]+)Format\.php$/U', $file, $out)) {
$formatNames[] = $out[1];
}
$dirFiles = scandir($pathDirFormat);
if($dirFiles !== false) {
foreach($dirFiles as $fileName) {
if(preg_match('@^([^.]+)Format\.php$@U', $fileName, $out)) { // Is PHP file ?
$listFormat[] = $out[1];
}
}
}
return $formatNames;
return $listFormat;
}
}

View File

@@ -1,103 +1,41 @@
<?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.
*
* 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
*/
/**
* An abstract class for format implementations
*
* This class implements {@see FormatInterface}
*/
require_once(__DIR__ . '/FormatInterface.php');
abstract class FormatAbstract implements FormatInterface {
/** The default charset (UTF-8) */
const DEFAULT_CHARSET = 'UTF-8';
/** @var string|null $contentType The content type */
protected $contentType = null;
protected
$contentType,
$charset,
$items,
$lastModified,
$extraInfos;
/** @var string $charset The charset */
protected $charset;
/** @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 array $extraInfos The extra infos */
protected $extraInfos;
/**
* {@inheritdoc}
*
* @param string $charset {@inheritdoc}
*/
public function setCharset($charset){
$this->charset = $charset;
return $this;
}
/** {@inheritdoc} */
public function getCharset(){
$charset = $this->charset;
return is_null($charset) ? static::DEFAULT_CHARSET : $charset;
}
/**
* Set the content type
*
* @param string $contentType The content type
* @return self The format object
*/
protected function setContentType($contentType){
$this->contentType = $contentType;
return $this;
}
/**
* Set the last modified time
*
* @param int $lastModified The last modified time
* @return void
*/
public function setLastModified($lastModified){
$this->lastModified = $lastModified;
}
/**
* Send header with the currently specified content type
*
* @throws \LogicException if the content type is not set
* @throws \LogicException if the content type is not a string
*
* @return void
*/
protected function callContentType(){
if(empty($this->contentType))
throw new \LogicException('Content-Type is not set!');
if(!is_string($this->contentType))
throw new \LogicException('Content-Type must be a string!');
header('Content-Type: ' . $this->contentType);
}
/** {@inheritdoc} */
public function display(){
if ($this->lastModified) {
header('Last-Modified: ' . gmdate('D, d M Y H:i:s ', $this->lastModified) . 'GMT');
@@ -107,18 +45,12 @@ abstract class FormatAbstract implements FormatInterface {
return $this;
}
/**
* {@inheritdoc}
*
* @param array $items {@inheritdoc}
*/
public function setItems(array $items){
$this->items = array_map(array($this, 'array_trim'), $items);
return $this;
}
/** {@inheritdoc} */
public function getItems(){
if(!is_array($this->items))
throw new \LogicException('Feed the ' . get_class($this) . ' with "setItems" method before !');
@@ -127,10 +59,10 @@ abstract class FormatAbstract implements FormatInterface {
}
/**
* {@inheritdoc}
*
* @param array $extraInfos {@inheritdoc}
*/
* Define common informations can be required by formats and set default value for unknown values
* @param array $extraInfos array with know informations (there isn't merge !!!)
* @return this
*/
public function setExtraInfos(array $extraInfos = array()){
foreach(array('name', 'uri', 'icon') as $infoName) {
if(!isset($extraInfos[$infoName])) {
@@ -143,7 +75,10 @@ abstract class FormatAbstract implements FormatInterface {
return $this;
}
/** {@inheritdoc} */
/**
* Return extra infos
* @return array See "setExtraInfos" detail method to know what extra are disponibles
*/
public function getExtraInfos(){
if(is_null($this->extraInfos)) { // No extra info ?
$this->setExtraInfos(); // Define with default value
@@ -153,17 +88,12 @@ abstract class FormatAbstract implements FormatInterface {
}
/**
* 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
* Sanitized html while leaving it functionnal.
* The aim is to keep html as-is (with clickable hyperlinks)
* while reducing annoying and potentially dangerous things.
* Yes, I know sanitizing HTML 100% is an impossible task.
* Maybe we'll switch to http://htmlpurifier.org/
* or http://www.bioinformatics.org/phplabware/internal_utilities/htmLawed/index.php
*/
protected function sanitizeHtml($html)
{
@@ -174,17 +104,6 @@ abstract class FormatAbstract implements FormatInterface {
return $html;
}
/**
* Trim each element of an array
*
* This function applies `trim()` to all elements in the array, if the element
* is a valid string.
*
* @param array $elements The array to trim
* @return array The trimmed array
*
* @todo This is a utility function that doesn't belong here, find a new home.
*/
protected function array_trim($elements){
foreach($elements as $key => $value) {
if(is_string($value))

View File

@@ -1,84 +1,11 @@
<?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.
*
* 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
*/
/**
* The format interface
*
* @todo Add missing function to the interface
* @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();
/**
* Display the current data to the user
*
* @return self The format object
*/
public function display();
/**
* 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();
/**
* 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();
/**
* 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();
}

View File

@@ -1,35 +1,10 @@
<?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.
*
* 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
*/
/**
* Validator for bridge parameters
* Implements a validator for bridge parameters
*/
class ParameterValidator {
/**
* 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[] = array(
'name' => $name,
@@ -38,23 +13,13 @@ class ParameterValidator {
}
/**
* Return list of invalid parameters.
*
* Each element is an array of 'name' and 'reason'.
*
* @return array List of invalid parameters
* Returns an array of invalid parameters, where each element is an
* array of 'name' and 'reason'.
*/
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,
@@ -73,12 +38,6 @@ class ParameterValidator {
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);
@@ -88,23 +47,10 @@ class ParameterValidator {
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 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);
@@ -123,11 +69,9 @@ class ParameterValidator {
}
/**
* 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
* Checks if all required parameters are supplied by the user
* @param $data An array of parameters provided by the user
* @param $parameters An array of bridge parameters
*/
public function validateData(&$data, $parameters){
@@ -178,11 +122,11 @@ class ParameterValidator {
}
/**
* Get the name of the context matching the provided inputs
* Returns 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
* @return mixed Returns the context name or null if no match was found
*/
public function getQueriedContext($data, $parameters){
$queriedContexts = array();

69
lib/RssBridge.php Normal file
View File

@@ -0,0 +1,69 @@
<?php
/* rss-bridge library.
Foundation functions for rss-bridge project.
See https://github.com/sebsauvage/rss-bridge
Licence: Public domain.
*/
define('PATH_VENDOR', '/../vendor');
require __DIR__ . '/Exceptions.php';
require __DIR__ . '/Format.php';
require __DIR__ . '/FormatAbstract.php';
require __DIR__ . '/Bridge.php';
require __DIR__ . '/BridgeAbstract.php';
require __DIR__ . '/FeedExpander.php';
require __DIR__ . '/Cache.php';
require __DIR__ . '/Authentication.php';
require __DIR__ . '/Configuration.php';
require __DIR__ . '/BridgeCard.php';
require __DIR__ . '/BridgeList.php';
require __DIR__ . '/ParameterValidator.php';
require __DIR__ . '/html.php';
require __DIR__ . '/error.php';
require __DIR__ . '/contents.php';
$vendorLibSimpleHtmlDom = __DIR__ . PATH_VENDOR . '/simplehtmldom/simple_html_dom.php';
if(!file_exists($vendorLibSimpleHtmlDom)) {
throw new \HttpException('"PHP Simple HTML DOM Parser" library is missing.
Get it from http://simplehtmldom.sourceforge.net and place the script "simple_html_dom.php" in '
. substr(PATH_VENDOR, 4)
. '/simplehtmldom/',
500);
}
require_once $vendorLibSimpleHtmlDom;
$vendorLibPhpUrlJoin = __DIR__ . PATH_VENDOR . '/php-urljoin/src/urljoin.php';
if(!file_exists($vendorLibPhpUrlJoin)) {
throw new \HttpException('"php-urljoin" library is missing.
Get it from https://github.com/fluffy-critter/php-urljoin and place the script "urljoin.php" in '
. substr(PATH_VENDOR, 4)
. '/php-urljoin/src/',
500);
}
require_once $vendorLibPhpUrlJoin;
/* Example use
require_once __DIR__ . '/lib/RssBridge.php';
// Data retrieval
Bridge::setDir(__DIR__ . '/bridges/');
$bridge = Bridge::create('GoogleSearch');
$bridge->collectData($_REQUEST);
// Data transformation
Format::setDir(__DIR__ . '/formats/');
$format = Format::create('Atom');
$format
->setItems($bridge->getItems())
->setExtraInfos(array(
'name' => $bridge->getName(),
'uri' => $bridge->getURI(),
'icon' => $bridge->getIcon(),
))
->display();
*/

View File

@@ -1,56 +1,6 @@
<?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.
*
* 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
*/
/**
* Gets contents from the Internet.
*
* **Content caching** (disabled in debug mode)
*
* A copy of the received content is stored in a local cache folder `server/` at
* {@see PATH_CACHE}. The `If-Modified-Since` header is added to the request, if
* the provided URL has been cached before.
*
* When the server responds with `304 Not Modified`, the cached data is returned.
* This will improve response times and reduce bandwidth for servers that support
* the `If-Modified-Since` header.
*
* Cached files are forcefully removed after 24 hours.
*
* @link https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Modified-Since
* If-Modified-Since
*
* @param string $url The URL.
* @param array $header (optional) A list of cURL header.
* For more information follow the links below.
* * https://php.net/manual/en/function.curl-setopt.php
* * https://curl.haxx.se/libcurl/c/CURLOPT_HTTPHEADER.html
* @param array $opts (optional) A list of cURL options as associative array in
* the format `$opts[$option] = $value;`, where `$option` is any `CURLOPT_XXX`
* option and `$value` the corresponding value.
*
* For more information see http://php.net/manual/en/function.curl-setopt.php
* @return string The contents.
*/
function getContents($url, $header = array(), $opts = array()){
Debug::log('Reading contents from "' . $url . '"');
// Initialize cache
$cache = Cache::create('FileCache');
$cache->setPath(PATH_CACHE . 'server/');
$cache->purgeCache(86400); // 24 hours (forced)
$params = [$url];
$cache->setParameters($params);
debugMessage('Reading contents from "' . $url . '"');
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
@@ -58,7 +8,7 @@ function getContents($url, $header = array(), $opts = array()){
if(is_array($header) && count($header) !== 0) {
Debug::log('Setting headers: ' . json_encode($header));
debugMessage('Setting headers: ' . json_encode($header));
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
}
@@ -69,7 +19,7 @@ function getContents($url, $header = array(), $opts = array()){
if(is_array($opts) && count($opts) !== 0) {
Debug::log('Setting options: ' . json_encode($opts));
debugMessage('Setting options: ' . json_encode($opts));
foreach($opts as $key => $value) {
curl_setopt($ch, $key, $value);
@@ -79,7 +29,7 @@ function getContents($url, $header = array(), $opts = array()){
if(defined('PROXY_URL') && !defined('NOPROXY')) {
Debug::log('Setting proxy url: ' . PROXY_URL);
debugMessage('Setting proxy url: ' . PROXY_URL);
curl_setopt($ch, CURLOPT_PROXY, PROXY_URL);
}
@@ -87,104 +37,43 @@ function getContents($url, $header = array(), $opts = array()){
// We always want the response header as part of the data!
curl_setopt($ch, CURLOPT_HEADER, true);
// Build "If-Modified-Since" header
if(!Debug::isEnabled() && $time = $cache->getTime()) { // Skip if cache file doesn't exist
Debug::log('Adding If-Modified-Since');
curl_setopt($ch, CURLOPT_TIMEVALUE, $time);
curl_setopt($ch, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE);
}
// Enables logging for the outgoing header
curl_setopt($ch, CURLINFO_HEADER_OUT, true);
$data = curl_exec($ch);
$curlError = curl_error($ch);
$curlErrno = curl_errno($ch);
$curlInfo = curl_getinfo($ch);
Debug::log('Outgoing header: ' . json_encode($curlInfo));
if($data === false)
Debug::log('Cant\'t download ' . $url . ' cUrl error: ' . $curlError . ' (' . $curlErrno . ')');
debugMessage('Cant\'t download ' . $url . ' cUrl error: ' . $curlError . ' (' . $curlErrno . ')');
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$errorCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$header = substr($data, 0, $headerSize);
Debug::log('Response header: ' . $header);
debugMessage('Response header: ' . $header);
$headers = parseResponseHeader($header);
$finalHeader = end($headers);
curl_close($ch);
if($errorCode !== 200) {
switch($errorCode) {
case 200: // Contents received
Debug::log('New contents received');
$data = substr($data, $headerSize);
// Disable caching if the server responds with "Cache-Control: no-cache"
// or "Cache-Control: no-store"
$finalHeader = array_change_key_case($finalHeader, CASE_LOWER);
if(array_key_exists('cache-control', $finalHeader)) {
Debug::log('Server responded with "Cache-Control" header');
$directives = explode(',', $finalHeader['cache-control']);
$directives = array_map('trim', $directives);
if(in_array('no-cache', $directives)
|| in_array('no-store', $directives)) { // Skip caching
Debug::log('Skip server side caching');
return $data;
}
}
Debug::log('Store response to cache');
$cache->saveData($data);
return $data;
case 304: // Not modified, use cached data
Debug::log('Contents not modified on host, returning cached data');
return $cache->loadData();
default:
if(array_key_exists('Server', $finalHeader) && strpos($finalHeader['Server'], 'cloudflare') !== false) {
if(array_key_exists('Server', $finalHeader) && strpos($finalHeader['Server'], 'cloudflare') !== false) {
returnServerError(<<< EOD
The server responded with a Cloudflare challenge, which is not supported by RSS-Bridge!
If this error persists longer than a week, please consider opening an issue on GitHub!
EOD
);
}
);
}
returnError(<<<EOD
returnError(<<<EOD
The requested resource cannot be found!
Please make sure your input parameters are correct!
cUrl error: $curlError ($curlErrno)
EOD
, $errorCode);
, $errorCode);
}
curl_close($ch);
return substr($data, $headerSize);
}
/**
* Gets contents from the Internet as simplhtmldom object.
*
* @param string $url The URL.
* @param array $header (optional) A list of cURL header.
* For more information follow the links below.
* * https://php.net/manual/en/function.curl-setopt.php
* * https://curl.haxx.se/libcurl/c/CURLOPT_HTTPHEADER.html
* @param array $opts (optional) A list of cURL options as associative array in
* the format `$opts[$option] = $value;`, where `$option` is any `CURLOPT_XXX`
* option and `$value` the corresponding value.
*
* For more information see http://php.net/manual/en/function.curl-setopt.php
* @param bool $lowercase Force all selectors to lowercase.
* @param bool $forceTagsClosed Forcefully close tags in malformed HTML.
*
* _Remarks_: Forcefully closing tags is great for malformed HTML, but it can
* lead to parsing errors.
* @param string $target_charset Defines the target charset.
* @param bool $stripRN Replace all occurrences of `"\r"` and `"\n"` by `" "`.
* @param string $defaultBRText Specifies the replacement text for `<br>` tags
* when returning plaintext.
* @param string $defaultSpanText Specifies the replacement text for `<span />`
* tags when returning plaintext.
* @return string Contents as simplehtmldom object.
*/
function getSimpleHTMLDOM($url,
$header = array(),
$opts = array(),
@@ -205,34 +94,10 @@ $defaultSpanText = DEFAULT_SPAN_TEXT){
}
/**
* Gets contents from the Internet as simplhtmldom object. Contents are cached
* and re-used for subsequent calls until the cache duration elapsed.
*
* _Notice_: Cached contents are forcefully removed after 24 hours (86400 seconds).
*
* @param string $url The URL.
* @param int $duration Cache duration in seconds.
* @param array $header (optional) A list of cURL header.
* For more information follow the links below.
* * https://php.net/manual/en/function.curl-setopt.php
* * https://curl.haxx.se/libcurl/c/CURLOPT_HTTPHEADER.html
* @param array $opts (optional) A list of cURL options as associative array in
* the format `$opts[$option] = $value;`, where `$option` is any `CURLOPT_XXX`
* option and `$value` the corresponding value.
*
* For more information see http://php.net/manual/en/function.curl-setopt.php
* @param bool $lowercase Force all selectors to lowercase.
* @param bool $forceTagsClosed Forcefully close tags in malformed HTML.
*
* _Remarks_: Forcefully closing tags is great for malformed HTML, but it can
* lead to parsing errors.
* @param string $target_charset Defines the target charset.
* @param bool $stripRN Replace all occurrences of `"\r"` and `"\n"` by `" "`.
* @param string $defaultBRText Specifies the replacement text for `<br>` tags
* when returning plaintext.
* @param string $defaultSpanText Specifies the replacement text for `<span />`
* tags when returning plaintext.
* @return string Contents as simplehtmldom object.
* Maintain locally cached versions of pages to avoid multiple downloads.
* @param url url to cache
* @param duration duration of the cache file in seconds (default: 24h/86400s)
* @return content of the file as string
*/
function getSimpleHTMLDOMCached($url,
$duration = 86400,
@@ -244,11 +109,11 @@ $target_charset = DEFAULT_TARGET_CHARSET,
$stripRN = true,
$defaultBRText = DEFAULT_BR_TEXT,
$defaultSpanText = DEFAULT_SPAN_TEXT){
Debug::log('Caching url ' . $url . ', duration ' . $duration);
debugMessage('Caching url ' . $url . ', duration ' . $duration);
// Initialize cache
$cache = Cache::create('FileCache');
$cache->setPath(PATH_CACHE . 'pages/');
$cache->setPath(CACHE_DIR . '/pages');
$cache->purgeCache(86400); // 24 hours (forced)
$params = [$url];
@@ -258,7 +123,7 @@ $defaultSpanText = DEFAULT_SPAN_TEXT){
$time = $cache->getTime();
if($time !== false
&& (time() - $duration < $time)
&& Debug::isEnabled()) { // Contents within duration
&& (!defined('DEBUG') || DEBUG !== true)) { // Contents within duration
$content = $cache->loadData();
} else { // Content not within duration
$content = getContents($url, $header, $opts);
@@ -277,12 +142,9 @@ $defaultSpanText = DEFAULT_SPAN_TEXT){
}
/**
* Parses the cURL response header into an associative array
* Parses the provided response header into an associative array
*
* Based on https://stackoverflow.com/a/18682872
*
* @param string $header The cURL response header.
* @return array An associative array of response headers.
*/
function parseResponseHeader($header) {
@@ -315,18 +177,10 @@ function parseResponseHeader($header) {
}
/**
* Determines the MIME type from a URL/Path file extension.
*
* _Remarks_:
*
* * The built-in functions `mime_content_type` and `fileinfo` require fetching
* remote contents.
* * A caller can hint for a MIME type by appending `#.ext` to the URL (i.e. `#.image`).
*
* Determine MIME type from URL/Path file extension
* Remark: Built-in functions mime_content_type or fileinfo requires fetching remote content
* Remark: A bridge can hint for a MIME type by appending #.ext to a URL, e.g. #.image
* Based on https://stackoverflow.com/a/1147952
*
* @param string $url The URL or path to the file.
* @return string The MIME type of the file.
*/
function getMimeType($url) {
static $mime = null;

View File

@@ -1,43 +1,28 @@
<?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.
*
* 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
*/
/**
* Throws an exception when called.
*
* @throws \Exception when called
* @param string $message The error message
* @param int $code The HTTP error code
* @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);
throw new \HttpException($message, $code);
}
/**
* Returns HTTP Error 400 (Bad Request) when called.
*
* @param string $message The error message
*/
function returnClientError($message){
returnError($message, 400);
}
/**
* Returns HTTP Error 500 (Internal Server Error) when called.
*
* @param string $message The error message
*/
function returnServerError($message){
returnError($message, 500);
}
function debugMessage($text){
if(!file_exists('DEBUG')) {
return;
}
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3);
$calling = $backtrace[2];
$message = $calling['file'] . ':'
. $calling['line'] . ' class '
. (isset($calling['class']) ? $calling['class'] : '<no-class>') . '->'
. $calling['function'] . ' - '
. $text;
error_log($message);
}

View File

@@ -1,55 +1,18 @@
<?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.
*
* 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
*/
function sanitize($textToSanitize,
$removedTags = array('script', 'iframe', 'input', 'form'),
$keptAttributes = array('title', 'href', 'src'),
$keptText = array()){
$htmlContent = str_get_html($textToSanitize);
/**
* Removes unwanted tags from a given HTML text.
*
* @param string $html The HTML text to sanitize.
* @param array $tags_to_remove A list of tags to remove from the DOM.
* @param array $attributes_to_keep A list of attributes to keep on tags (other
* attributes are removed).
* @param array $text_to_keep A list of tags where the innertext replaces the tag
* (i.e. `<p>Hello World!</p>` becomes `Hello World!`).
* @return object A simplehtmldom object of the remaining contents.
*
* @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()){
$htmlContent = str_get_html($html);
/*
* Notice: simple_html_dom currently doesn't support "->find(*)", which is a
* known issue: https://sourceforge.net/p/simplehtmldom/bugs/157/
*
* A solution to this is to find all nodes WITHOUT a specific attribute. If
* the attribute is very unlikely to appear in the DOM, this is essentially
* returning all nodes.
*
* "*[!b38fd2b1fe7f4747d6b1c1254ccd055e]" is doing exactly that. The attrib
* "b38fd2b1fe7f4747d6b1c1254ccd055e" is very unlikely to appear in any DOM.
*/
foreach($htmlContent->find('*[!b38fd2b1fe7f4747d6b1c1254ccd055e]') as $element) {
if(in_array($element->tag, $text_to_keep)) {
if(in_array($element->tag, $keptText)) {
$element->outertext = $element->plaintext;
} elseif(in_array($element->tag, $tags_to_remove)) {
} elseif(in_array($element->tag, $removedTags)) {
$element->outertext = '';
} else {
foreach($element->getAllAttributes() as $attributeName => $attribute) {
if(!in_array($attributeName, $attributes_to_keep))
if(!in_array($attributeName, $keptAttributes))
$element->removeAttribute($attributeName);
}
}
@@ -58,48 +21,11 @@ $text_to_keep = array()){
return $htmlContent;
}
/**
* Replace background by image
*
* Replaces tags with styles of `backgroud-image` by `<img />` tags.
*
* For example:
*
* ```HTML
* <html>
* <body style="background-image: url('bgimage.jpg');">
* <h1>Hello world!</h1>
* </body>
* </html>
* ```
*
* results in this output:
*
* ```HTML
* <html>
* <img style="display:block;" src="bgimage.jpg" />
* </html>
* ```
*
* @param string $htmlContent The HTML content
* @return string The HTML content with all ocurrences replaced
*/
function backgroundToImg($htmlContent) {
$regex = '/background-image[ ]{0,}:[ ]{0,}url\([\'"]{0,}(.*?)[\'"]{0,}\)/';
$htmlContent = str_get_html($htmlContent);
/*
* Notice: simple_html_dom currently doesn't support "->find(*)", which is a
* known issue: https://sourceforge.net/p/simplehtmldom/bugs/157/
*
* A solution to this is to find all nodes WITHOUT a specific attribute. If
* the attribute is very unlikely to appear in the DOM, this is essentially
* returning all nodes.
*
* "*[!b38fd2b1fe7f4747d6b1c1254ccd055e]" is doing exactly that. The attrib
* "b38fd2b1fe7f4747d6b1c1254ccd055e" is very unlikely to appear in any DOM.
*/
foreach($htmlContent->find('*[!b38fd2b1fe7f4747d6b1c1254ccd055e]') as $element) {
if(preg_match($regex, $element->style, $matches) > 0) {
@@ -116,14 +42,9 @@ function backgroundToImg($htmlContent) {
/**
* Convert relative links in HTML into absolute links
*
* This function is based on `php-urljoin`.
*
* @link https://github.com/plaidfluff/php-urljoin php-urljoin
*
* @param string|object $content The HTML content. Supports HTML objects or string objects
* @param string $server Fully qualified URL to the page containing relative links
* @return object Content with fixed URLs.
* @param $content HTML content to fix. Supports HTML objects or string objects
* @param $server full URL to the page containing relative links
* @return content with fixed URLs, as HTML object or string depending on input type
*/
function defaultLinkTo($content, $server){
$string_convert = false;
@@ -149,12 +70,10 @@ function defaultLinkTo($content, $server){
/**
* Extract the first part of a string matching the specified start and end delimiters
*
* @param string $string Input string, e.g. `<div>Post author: John Doe</div>`
* @param string $start Start delimiter, e.g. `author: `
* @param string $end End delimiter, e.g. `<`
* @return string|bool Extracted string, e.g. `John Doe`, or false if the
* delimiters were not found.
* @param $string input string, e.g. '<div>Post author: John Doe</div>'
* @param $start start delimiter, e.g. 'author: '
* @param $end end delimiter, e.g. '<'
* @return extracted string, e.g. 'John Doe', or false if the delimiters were not found.
*/
function extractFromDelimiters($string, $start, $end) {
if (strpos($string, $start) !== false) {
@@ -166,11 +85,10 @@ function extractFromDelimiters($string, $start, $end) {
/**
* Remove one or more part(s) of a string using a start and end delmiters
*
* @param string $string Input string, e.g. `foo<script>superscript()</script>bar`
* @param string $start Start delimiter, e.g. `<script`
* @param string $end End delimiter, e.g. `</script>`
* @return string Cleaned string, e.g. `foobar`
* @param $string input string, e.g. 'foo<script>superscript()</script>bar'
* @param $start start delimiter, e.g. '<script'
* @param $end end delimiter, e.g. '</script>'
* @return cleaned string, e.g. 'foobar'
*/
function stripWithDelimiters($string, $start, $end) {
while(strpos($string, $start) !== false) {
@@ -183,13 +101,10 @@ function stripWithDelimiters($string, $start, $end) {
/**
* Remove HTML sections containing one or more sections using the same HTML tag
*
* @param string $string Input string, e.g. `foo<div class="ads"><div>ads</div>ads</div>bar`
* @param string $tag_name Name of the HTML tag, e.g. `div`
* @param string $tag_start Start of the HTML tag to remove, e.g. `<div class="ads">`
* @return string Cleaned String, e.g. `foobar`
*
* @todo This function needs more documentation to make it maintainable.
* @param $string input string, e.g. 'foo<div class="ads"><div>ads</div>ads</div>bar'
* @param $tag_name name of the HTML tag, e.g. 'div'
* @param $tag_start start of the HTML tag to remove, e.g. '<div class="ads">'
* @return cleaned string, e.g. 'foobar'
*/
function stripRecursiveHTMLSection($string, $tag_name, $tag_start){
$open_tag = '<' . $tag_name;
@@ -216,13 +131,9 @@ function stripRecursiveHTMLSection($string, $tag_name, $tag_start){
}
/**
* Convert Markdown into HTML. Only a subset of the Markdown syntax is implemented.
*
* @link https://daringfireball.net/projects/markdown/ Markdown
* @link https://github.github.com/gfm/ GitHub Flavored Markdown Spec
*
* @param string $string Input string in Markdown format
* @return string output string in HTML format
* Convert Markdown tags into HTML tags. Only a subset of the Markdown syntax is implemented.
* @param $string input string in Markdown format
* @return output string in HTML format
*/
function markdownToHtml($string) {

View File

@@ -1,79 +0,0 @@
<?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.
*
* 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
*/
/** Path to the root folder of RSS-Bridge (where index.php is located) */
define('PATH_ROOT', __DIR__ . '/../');
/** Path to the core library */
define('PATH_LIB', __DIR__ . '/../lib/'); // Path to core library
/** Path to the vendor library */
define('PATH_LIB_VENDOR', __DIR__ . '/../vendor/');
/** Path to the bridges library */
define('PATH_LIB_BRIDGES', __DIR__ . '/../bridges/');
/** Path to the formats library */
define('PATH_LIB_FORMATS', __DIR__ . '/../formats/');
/** Path to the caches library */
define('PATH_LIB_CACHES', __DIR__ . '/../caches/');
/** Path to the cache folder */
define('PATH_CACHE', __DIR__ . '/../cache/');
/** Path to the whitelist file */
define('WHITELIST', __DIR__ . '/../whitelist.txt');
/** URL to the RSS-Bridge repository */
define('REPOSITORY', 'https://github.com/RSS-Bridge/rss-bridge/');
// Interfaces
require_once PATH_LIB . 'BridgeInterface.php';
require_once PATH_LIB . 'CacheInterface.php';
require_once PATH_LIB . 'FormatInterface.php';
// Classes
require_once PATH_LIB . 'Debug.php';
require_once PATH_LIB . 'Exceptions.php';
require_once PATH_LIB . 'Format.php';
require_once PATH_LIB . 'FormatAbstract.php';
require_once PATH_LIB . 'Bridge.php';
require_once PATH_LIB . 'BridgeAbstract.php';
require_once PATH_LIB . 'FeedExpander.php';
require_once PATH_LIB . 'Cache.php';
require_once PATH_LIB . 'Authentication.php';
require_once PATH_LIB . 'Configuration.php';
require_once PATH_LIB . 'BridgeCard.php';
require_once PATH_LIB . 'BridgeList.php';
require_once PATH_LIB . 'ParameterValidator.php';
// Functions
require_once PATH_LIB . 'html.php';
require_once PATH_LIB . 'error.php';
require_once PATH_LIB . 'contents.php';
// Vendor
require_once PATH_LIB_VENDOR . 'simplehtmldom/simple_html_dom.php';
require_once PATH_LIB_VENDOR . 'php-urljoin/src/urljoin.php';
// Initialize static members
try {
Bridge::setWorkingDir(PATH_LIB_BRIDGES);
Format::setWorkingDir(PATH_LIB_FORMATS);
Cache::setWorkingDir(PATH_LIB_CACHES);
} catch(Exception $e) {
error_log($e);
header('Content-type: text/plain', true, 500);
die($e->getMessage());
}

View File

@@ -1,47 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ruleset name="RSS-Bridge PHPCompatibility">
<description>Defines rules for PHPCompatibility</description>
<exclude-pattern>./static</exclude-pattern>
<exclude-pattern>./vendor</exclude-pattern>
<!-- Run against the PHPCompatibility ruleset -->
<!--
-->
<config name="testVersion" value="5.6"/>
<rule ref="PHPCompatibility">
<!--
"PHP 7 changes how most errors are reported by PHP. Instead of reporting
errors through the traditional error reporting mechanism used by PHP 5,
most errors are now reported by throwing Error exceptions."
from: http://php.net/manual/en/language.errors.php7.php
Skip this check for PHP 5.6 in order to support PHP 7.x
Catch Exception and Error separately to support both versions.
Example:
<code>
try {
// Run your code here
} catch(Error $e) {
// Handle errors (PHP 7.0+)
} catch(Exception $e) {
// Handle exceptions (PHP 5.6+)
}
</code>
-->
<exclude name="PHPCompatibility.Classes.NewClasses.errorFound"/>
<!--
RSS-Bridge uses parse_ini_file with INI_SCANNER_TYPED to load configuration
settings. INI_SCANNER_TYPED was added in PHP 5.6.1. Skip checking for that
specific constant.
References: http://php.net/manual/de/function.parse-ini-file.php
-->
<exclude name="PHPCompatibility.Constants.NewConstants.ini_scanner_typedFound"/>
</rule>
</ruleset>

View File

@@ -47,9 +47,6 @@
<rule ref="PEAR.Functions.ValidDefaultValue"/>
<!-- Use PascalCase for class names -->
<rule ref="PEAR.NamingConventions.ValidClassName"/>
<!-- abstract and final declarations MUST precede the visibility declaration -->
<!-- static declaration MUST come after the visibility declaration -->
<rule ref="PSR2.Methods.MethodDeclaration" />
<!-- Use 'elseif' instead of 'else if' -->
<rule ref="PSR2.ControlStructures.ElseIfDeclaration"/>
<!-- Do not add spaces after opening or before closing bracket -->

View File

@@ -4,7 +4,9 @@ use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\TestResult;
use PHPUnit\Framework\AssertionFailedError;
require_once __DIR__ . '/../lib/rssbridge.php';
require_once(__DIR__ . '/../lib/RssBridge.php');
Bridge::setDir(__DIR__ . '/../bridges/');
/**
* This class checks bridges for implementation details:
@@ -141,7 +143,7 @@ final class BridgeImplementationTest extends TestCase {
}
public function count() {
return count(Bridge::getBridgeNames());
return count(Bridge::listBridges());
}
public function run(TestResult $result = null) {
@@ -150,7 +152,7 @@ final class BridgeImplementationTest extends TestCase {
$result = new TestResult;
}
foreach (Bridge::getBridgeNames() as $bridge) {
foreach (Bridge::listBridges() as $bridge) {
$bridge .= 'Bridge';