mirror of
https://github.com/RSS-Bridge/rss-bridge.git
synced 2025-08-18 06:11:33 +02:00
Compare commits
159 Commits
revert-909
...
2019-01-13
Author | SHA1 | Date | |
---|---|---|---|
|
3c5b23daa6 | ||
|
ef6709c402 | ||
|
fc96e97d51 | ||
|
600f2290b6 | ||
|
245af35a60 | ||
|
ef4923ae5c | ||
|
18229b5c70 | ||
|
3160e62293 | ||
|
f81d1b0846 | ||
|
8801ac9e64 | ||
|
288d4de218 | ||
|
f3f33cabed | ||
|
3e45643418 | ||
|
719320e1a4 | ||
|
81ee15a161 | ||
|
988635dcf3 | ||
|
4095cad9b4 | ||
|
e7d3a006c8 | ||
|
ce65f51d91 | ||
|
4b22862295 | ||
|
185a773e74 | ||
|
10659dd453 | ||
|
6b2a45c1e8 | ||
|
6e4b6fa1cc | ||
|
0cad5f24e6 | ||
|
cb6ad7c077 | ||
|
4438807b26 | ||
|
6c1d861529 | ||
|
dc83962483 | ||
|
bb2329fa3a | ||
|
758f37b452 | ||
|
fb8a064e3a | ||
|
b00971b2c3 | ||
|
a07ead42a7 | ||
|
a11ade3442 | ||
|
3932e7b8ef | ||
|
5305c405f6 | ||
|
1c58c04271 | ||
|
89218f1da6 | ||
|
30e2b79c38 | ||
|
2184f523cd | ||
|
242b6953ed | ||
|
bdcb7a9829 | ||
|
f4b46e497e | ||
|
d5085a4116 | ||
|
d7cabfca54 | ||
|
de575982a1 | ||
|
3d301fc4ee | ||
|
263e8872ea | ||
|
6e9c188a72 | ||
|
49da67cb33 | ||
|
b4dbd191d0 | ||
|
e09f452426 | ||
|
7b261d1cc2 | ||
|
96a518c9e7 | ||
|
0d2ea9a677 | ||
|
66e82e46db | ||
|
54800fcc8d | ||
|
67004556e6 | ||
|
c6a7b9ac64 | ||
|
dbffbd4d4e | ||
|
1c17ffb5c4 | ||
|
326cfb21cf | ||
|
8ab1fb86a9 | ||
|
a9ec3d0d1f | ||
|
ac5bcb62ec | ||
|
f24ab8b51b | ||
|
4348119adf | ||
|
fd4124cda2 | ||
|
91f7405297 | ||
|
85685b7758 | ||
|
41d02554f3 | ||
|
c4550be812 | ||
|
b29ba5b973 | ||
|
254fe9212a | ||
|
3806895059 | ||
|
599d438a0d | ||
|
e5a6baab96 | ||
|
b47a30ecc1 | ||
|
860b36c1e3 | ||
|
3d475572c6 | ||
|
59f2d755fe | ||
|
d7c374bd8c | ||
|
6b6ab6486a | ||
|
6c4e239f64 | ||
|
88b0656954 | ||
|
66b11b8c41 | ||
|
1b34d9860e | ||
|
6e70d461e1 | ||
|
0a92b5d29b | ||
|
e3849f45ab | ||
|
3d9c4a3718 | ||
|
5f146a257e | ||
|
936688e08c | ||
|
4b5372638c | ||
|
6f4a8f4d03 | ||
|
39652bb050 | ||
|
fcac5b8b92 | ||
|
6f7b56cba8 | ||
|
86ac0a4866 | ||
|
4a99c6e630 | ||
|
e8442a3bf8 | ||
|
427688fd67 | ||
|
4a6b3654eb | ||
|
5f867c00b4 | ||
|
c15b25a07d | ||
|
c296e73c18 | ||
|
007ee4d858 | ||
|
dd95ec6200 | ||
|
d951000c23 | ||
|
51634a72e0 | ||
|
78c69b08f0 | ||
|
3bb3353897 | ||
|
e26d61ec0a | ||
|
a0490e3673 | ||
|
c63af2e7ad | ||
|
9379854f7a | ||
|
ecdac1b089 | ||
|
2104fc4d58 | ||
|
697d63bb96 | ||
|
2bb13169b4 | ||
|
4713fb6190 | ||
|
a08811f147 | ||
|
a935e310ff | ||
|
7e3787a185 | ||
|
039c032798 | ||
|
cb91d9cce8 | ||
|
bf91f106b4 | ||
|
0b2ede35cd | ||
|
5842bdfc83 | ||
|
68ee24d6bd | ||
|
104ae2298e | ||
|
7026684e34 | ||
|
0b792d77eb | ||
|
110b865a54 | ||
|
19a7f10160 | ||
|
42e25e7fc0 | ||
|
4b7fea5ebc | ||
|
95bd206e9d | ||
|
9910310652 | ||
|
12f0e5a360 | ||
|
81ba96ff94 | ||
|
984f0b24d0 | ||
|
2126db84ac | ||
|
4bf45df18e | ||
|
a88b148d20 | ||
|
f564925ba0 | ||
|
22e8f8b4aa | ||
|
bfae04d1fe | ||
|
723bd1150a | ||
|
53d2fbe3a5 | ||
|
3babd02658 | ||
|
3031fa406d | ||
|
85c34a0960 | ||
|
5deb86acff | ||
|
946e66e9df | ||
|
1a00dfa412 | ||
|
0f8443e1d3 | ||
|
7d474e5361 |
@@ -4,5 +4,4 @@ DEBUG
|
|||||||
Dockerfile
|
Dockerfile
|
||||||
whitelist.txt
|
whitelist.txt
|
||||||
phpcs.xml
|
phpcs.xml
|
||||||
CHANGELOG.md
|
|
||||||
CONTRIBUTING.md
|
CONTRIBUTING.md
|
14
.gitattributes
vendored
14
.gitattributes
vendored
@@ -20,3 +20,17 @@
|
|||||||
*.PDF diff=astextplain
|
*.PDF diff=astextplain
|
||||||
*.rtf diff=astextplain
|
*.rtf 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
|
2
CONTRIBUTING.md → .github/CONTRIBUTING.md
vendored
2
CONTRIBUTING.md → .github/CONTRIBUTING.md
vendored
@@ -43,5 +43,7 @@ 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)
|
* [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 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)
|
* [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)
|
* [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)
|
* [Do not add spaces when casting](https://github.com/RSS-Bridge/rss-bridge/wiki/Casting#do-not-add-spaces-when-casting)
|
61
.github/ISSUE_TEMPLATE/bridge-request-template.md
vendored
Normal file
61
.github/ISSUE_TEMPLATE/bridge-request-template.md
vendored
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
---
|
||||||
|
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.
|
||||||
|
-->
|
32
.travis.yml
32
.travis.yml
@@ -1,28 +1,36 @@
|
|||||||
dist: trusty
|
dist: trusty
|
||||||
sudo: false
|
|
||||||
language: php
|
language: php
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- if [[ $TRAVIS_PHP_VERSION == "hhvm" ]]; then
|
- composer global require dealerdirect/phpcodesniffer-composer-installer;
|
||||||
composer global require squizlabs/PHP_CodeSniffer;
|
- composer global require phpcompatibility/php-compatibility;
|
||||||
else
|
# Use PHPUnit 6 for unit tests (stable), requires PHP 7
|
||||||
pear channel-update pear.php.net;
|
|
||||||
pear install PHP_CodeSniffer;
|
|
||||||
fi
|
|
||||||
- if [[ $TRAVIS_PHP_VERSION == "7.0" ]]; then
|
- if [[ $TRAVIS_PHP_VERSION == "7.0" ]]; then
|
||||||
composer global require phpunit/phpunit ^6;
|
composer global require phpunit/phpunit ^6;
|
||||||
fi
|
fi
|
||||||
|
# Use latest PHPUnit on nightly to detect breaking changes
|
||||||
|
- if [[ $TRAVIS_PHP_VERSION == "nightly" ]]; then
|
||||||
|
composer global require phpunit/phpunit;
|
||||||
|
fi
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- phpenv rehash
|
- phpenv rehash
|
||||||
- if [[ $TRAVIS_PHP_VERSION == "hhvm" ]]; then
|
# Run PHP_CodeSniffer on all versions
|
||||||
/home/travis/.composer/vendor/bin/phpcs . --standard=phpcs.xml --warning-severity=0 --extensions=php -p;
|
- ~/.config/composer/vendor/bin/phpcs . --standard=phpcs.xml --warning-severity=0 --extensions=php -p;
|
||||||
else
|
# Check PHP compatibility for the lowest supported version
|
||||||
phpcs . --standard=phpcs.xml --warning-severity=0 --extensions=php -p;
|
- if [[ $TRAVIS_PHP_VERSION == "5.6" ]]; then
|
||||||
|
~/.config/composer/vendor/bin/phpcs . --standard=phpcompatibility.xml --extensions=php -p;
|
||||||
fi
|
fi
|
||||||
|
# Run unit tests (stable)
|
||||||
- if [[ $TRAVIS_PHP_VERSION == "7.0" ]]; then
|
- if [[ $TRAVIS_PHP_VERSION == "7.0" ]]; then
|
||||||
phpunit --configuration=phpunit.xml --include-path=lib/;
|
phpunit --configuration=phpunit.xml --include-path=lib/;
|
||||||
fi
|
fi
|
||||||
|
# Run unit tests (latest/nightly)
|
||||||
|
# Check PHP compatibility for all versions, starting at the lowest supported version in order to detect breaking changes
|
||||||
|
- if [[ $TRAVIS_PHP_VERSION == "nightly" ]]; then
|
||||||
|
phpunit --configuration=phpunit.xml --include-path=lib/;
|
||||||
|
~/.config/composer/vendor/bin/phpcs . --standard=PHPCompatibility --extensions=php -p --runtime-set testVersion 5.6-;
|
||||||
|
fi
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
fast_finish: true
|
fast_finish: true
|
||||||
@@ -30,9 +38,7 @@ matrix:
|
|||||||
include:
|
include:
|
||||||
- php: 5.6
|
- php: 5.6
|
||||||
- php: 7.0
|
- php: 7.0
|
||||||
- php: hhvm
|
|
||||||
- php: nightly
|
- php: nightly
|
||||||
|
|
||||||
allow_failures:
|
allow_failures:
|
||||||
- php: hhvm
|
|
||||||
- php: nightly
|
- php: nightly
|
||||||
|
263
CHANGELOG.md
263
CHANGELOG.md
@@ -1,263 +0,0 @@
|
|||||||
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 & 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.
|
|
174
README.md
174
README.md
@@ -110,88 +110,98 @@ Use this script to generate the list automatically (using the GitHub API):
|
|||||||
https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
|
https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
|
||||||
-->
|
-->
|
||||||
|
|
||||||
* [16mhz](https://api.github.com/users/16mhz)
|
* [16mhz](https://github.com/16mhz)
|
||||||
* [Ahiles3005](https://api.github.com/users/Ahiles3005)
|
* [Ahiles3005](https://github.com/Ahiles3005)
|
||||||
* [Albirew](https://api.github.com/users/Albirew)
|
* [Albirew](https://github.com/Albirew)
|
||||||
* [AmauryCarrade](https://api.github.com/users/AmauryCarrade)
|
* [AmauryCarrade](https://github.com/AmauryCarrade)
|
||||||
* [ArthurHoaro](https://api.github.com/users/ArthurHoaro)
|
* [AntoineTurmel](https://github.com/AntoineTurmel)
|
||||||
* [Astalaseven](https://api.github.com/users/Astalaseven)
|
* [ArthurHoaro](https://github.com/ArthurHoaro)
|
||||||
* [Astyan-42](https://api.github.com/users/Astyan-42)
|
* [Astalaseven](https://github.com/Astalaseven)
|
||||||
* [Daiyousei](https://api.github.com/users/Daiyousei)
|
* [Astyan-42](https://github.com/Astyan-42)
|
||||||
* [Djuuu](https://api.github.com/users/Djuuu)
|
* [Daiyousei](https://github.com/Daiyousei)
|
||||||
* [Draeli](https://api.github.com/users/Draeli)
|
* [Djuuu](https://github.com/Djuuu)
|
||||||
* [EtienneM](https://api.github.com/users/EtienneM)
|
* [Draeli](https://github.com/Draeli)
|
||||||
* [Frenzie](https://api.github.com/users/Frenzie)
|
* [EtienneM](https://github.com/EtienneM)
|
||||||
* [Ginko-Aloe](https://api.github.com/users/Ginko-Aloe)
|
* [Frenzie](https://github.com/Frenzie)
|
||||||
* [Glandos](https://api.github.com/users/Glandos)
|
* [Ginko-Aloe](https://github.com/Ginko-Aloe)
|
||||||
* [GregThib](https://api.github.com/users/GregThib)
|
* [Glandos](https://github.com/Glandos)
|
||||||
* [Grummfy](https://api.github.com/users/Grummfy)
|
* [GregThib](https://github.com/GregThib)
|
||||||
* [JackNUMBER](https://api.github.com/users/JackNUMBER)
|
* [Grummfy](https://github.com/Grummfy)
|
||||||
* [JeremyRand](https://api.github.com/users/JeremyRand)
|
* [JackNUMBER](https://github.com/JackNUMBER)
|
||||||
* [Jocker666z](https://api.github.com/users/Jocker666z)
|
* [JeremyRand](https://github.com/JeremyRand)
|
||||||
* [LogMANOriginal](https://api.github.com/users/LogMANOriginal)
|
* [Jocker666z](https://github.com/Jocker666z)
|
||||||
* [MonsieurPoutounours](https://api.github.com/users/MonsieurPoutounours)
|
* [LogMANOriginal](https://github.com/LogMANOriginal)
|
||||||
* [ORelio](https://api.github.com/users/ORelio)
|
* [MonsieurPoutounours](https://github.com/MonsieurPoutounours)
|
||||||
* [PaulVayssiere](https://api.github.com/users/PaulVayssiere)
|
* [Nono-m0le](https://github.com/Nono-m0le)
|
||||||
* [Piranhaplant](https://api.github.com/users/Piranhaplant)
|
* [ORelio](https://github.com/ORelio)
|
||||||
* [Riduidel](https://api.github.com/users/Riduidel)
|
* [PaulVayssiere](https://github.com/PaulVayssiere)
|
||||||
* [Strubbl](https://api.github.com/users/Strubbl)
|
* [Piranhaplant](https://github.com/Piranhaplant)
|
||||||
* [TheRadialActive](https://api.github.com/users/TheRadialActive)
|
* [Riduidel](https://github.com/Riduidel)
|
||||||
* [TwizzyDizzy](https://api.github.com/users/TwizzyDizzy)
|
* [Roliga](https://github.com/Roliga)
|
||||||
* [WalterBarrett](https://api.github.com/users/WalterBarrett)
|
* [Strubbl](https://github.com/Strubbl)
|
||||||
* [ZeNairolf](https://api.github.com/users/ZeNairolf)
|
* [TheRadialActive](https://github.com/TheRadialActive)
|
||||||
* [adamchainz](https://api.github.com/users/adamchainz)
|
* [TwizzyDizzy](https://github.com/TwizzyDizzy)
|
||||||
* [aledeg](https://api.github.com/users/aledeg)
|
* [WalterBarrett](https://github.com/WalterBarrett)
|
||||||
* [alexAubin](https://api.github.com/users/alexAubin)
|
* [ZeNairolf](https://github.com/ZeNairolf)
|
||||||
* [az5he6ch](https://api.github.com/users/az5he6ch)
|
* [adamchainz](https://github.com/adamchainz)
|
||||||
* [b1nj](https://api.github.com/users/b1nj)
|
* [aledeg](https://github.com/aledeg)
|
||||||
* [benasse](https://api.github.com/users/benasse)
|
* [alexAubin](https://github.com/alexAubin)
|
||||||
* [captn3m0](https://api.github.com/users/captn3m0)
|
* [az5he6ch](https://github.com/az5he6ch)
|
||||||
* [chemel](https://api.github.com/users/chemel)
|
* [b1nj](https://github.com/b1nj)
|
||||||
* [ckiw](https://api.github.com/users/ckiw)
|
* [benasse](https://github.com/benasse)
|
||||||
* [cnlpete](https://api.github.com/users/cnlpete)
|
* [captn3m0](https://github.com/captn3m0)
|
||||||
* [corenting](https://api.github.com/users/corenting)
|
* [chemel](https://github.com/chemel)
|
||||||
* [da2x](https://api.github.com/users/da2x)
|
* [ckiw](https://github.com/ckiw)
|
||||||
* [eMerzh](https://api.github.com/users/eMerzh)
|
* [cnlpete](https://github.com/cnlpete)
|
||||||
* [em92](https://api.github.com/users/em92)
|
* [corenting](https://github.com/corenting)
|
||||||
* [griffaurel](https://api.github.com/users/griffaurel)
|
* [couraudt](https://github.com/couraudt)
|
||||||
* [hunhejj](https://api.github.com/users/hunhejj)
|
* [da2x](https://github.com/da2x)
|
||||||
* [j0k3r](https://api.github.com/users/j0k3r)
|
* [disk0x](https://github.com/disk0x)
|
||||||
* [jdigilio](https://api.github.com/users/jdigilio)
|
* [eMerzh](https://github.com/eMerzh)
|
||||||
* [kranack](https://api.github.com/users/kranack)
|
* [em92](https://github.com/em92)
|
||||||
* [kraoc](https://api.github.com/users/kraoc)
|
* [fluffy-critter](https://github.com/fluffy-critter)
|
||||||
* [laBecasse](https://api.github.com/users/laBecasse)
|
* [fulmeek](https://github.com/fulmeek)
|
||||||
* [lagaisse](https://api.github.com/users/lagaisse)
|
* [griffaurel](https://github.com/griffaurel)
|
||||||
* [lalannev](https://api.github.com/users/lalannev)
|
* [hunhejj](https://github.com/hunhejj)
|
||||||
* [ldidry](https://api.github.com/users/ldidry)
|
* [j0k3r](https://github.com/j0k3r)
|
||||||
* [m0zes](https://api.github.com/users/m0zes)
|
* [jdigilio](https://github.com/jdigilio)
|
||||||
* [matthewseal](https://api.github.com/users/matthewseal)
|
* [kranack](https://github.com/kranack)
|
||||||
* [mcbyte-it](https://api.github.com/users/mcbyte-it)
|
* [kraoc](https://github.com/kraoc)
|
||||||
* [mdemoss](https://api.github.com/users/mdemoss)
|
* [laBecasse](https://github.com/laBecasse)
|
||||||
* [melangue](https://api.github.com/users/melangue)
|
* [lagaisse](https://github.com/lagaisse)
|
||||||
* [metaMMA](https://api.github.com/users/metaMMA)
|
* [lalannev](https://github.com/lalannev)
|
||||||
* [mickael-bertrand](https://api.github.com/users/mickael-bertrand)
|
* [ldidry](https://github.com/ldidry)
|
||||||
* [mitsukarenai](https://api.github.com/users/mitsukarenai)
|
* [lorenzos](https://github.com/lorenzos)
|
||||||
* [mro](https://api.github.com/users/mro)
|
* [m0zes](https://github.com/m0zes)
|
||||||
* [mxmehl](https://api.github.com/users/mxmehl)
|
* [matthewseal](https://github.com/matthewseal)
|
||||||
* [nel50n](https://api.github.com/users/nel50n)
|
* [mcbyte-it](https://github.com/mcbyte-it)
|
||||||
* [niawag](https://api.github.com/users/niawag)
|
* [mdemoss](https://github.com/mdemoss)
|
||||||
* [pellaeon](https://api.github.com/users/pellaeon)
|
* [melangue](https://github.com/melangue)
|
||||||
* [pit-fgfjiudghdf](https://api.github.com/users/pit-fgfjiudghdf)
|
* [metaMMA](https://github.com/metaMMA)
|
||||||
* [pitchoule](https://api.github.com/users/pitchoule)
|
* [mickael-bertrand](https://github.com/mickael-bertrand)
|
||||||
* [pmaziere](https://api.github.com/users/pmaziere)
|
* [mitsukarenai](https://github.com/mitsukarenai)
|
||||||
* [prysme01](https://api.github.com/users/prysme01)
|
* [mr-flibble](https://github.com/mr-flibble)
|
||||||
* [quentinus95](https://api.github.com/users/quentinus95)
|
* [mro](https://github.com/mro)
|
||||||
* [qwertygc](https://api.github.com/users/qwertygc)
|
* [mxmehl](https://github.com/mxmehl)
|
||||||
* [regisenguehard](https://api.github.com/users/regisenguehard)
|
* [nel50n](https://github.com/nel50n)
|
||||||
* [rogerdc](https://api.github.com/users/rogerdc)
|
* [niawag](https://github.com/niawag)
|
||||||
* [sebsauvage](https://api.github.com/users/sebsauvage)
|
* [pellaeon](https://github.com/pellaeon)
|
||||||
* [sublimz](https://api.github.com/users/sublimz)
|
* [pit-fgfjiudghdf](https://github.com/pit-fgfjiudghdf)
|
||||||
* [sysadminstory](https://api.github.com/users/sysadminstory)
|
* [pitchoule](https://github.com/pitchoule)
|
||||||
* [tameroski](https://api.github.com/users/tameroski)
|
* [pmaziere](https://github.com/pmaziere)
|
||||||
* [teromene](https://api.github.com/users/teromene)
|
* [prysme01](https://github.com/prysme01)
|
||||||
* [triatic](https://api.github.com/users/triatic)
|
* [quentinus95](https://github.com/quentinus95)
|
||||||
* [wtuuju](https://api.github.com/users/wtuuju)
|
* [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)
|
||||||
|
|
||||||
Licenses
|
Licenses
|
||||||
===
|
===
|
||||||
|
@@ -21,5 +21,4 @@ class AcrimedBridge extends FeedExpander {
|
|||||||
|
|
||||||
return $item;
|
return $item;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -83,5 +83,4 @@ class AllocineFRBridge extends BridgeAbstract {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -72,6 +72,9 @@ class AmazonBridge extends BridgeAbstract {
|
|||||||
|
|
||||||
// Title
|
// Title
|
||||||
$title = $element->find('h2', 0);
|
$title = $element->find('h2', 0);
|
||||||
|
if (is_null($title)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$item['title'] = html_entity_decode($title->innertext, ENT_QUOTES);
|
$item['title'] = html_entity_decode($title->innertext, ENT_QUOTES);
|
||||||
|
|
||||||
|
@@ -137,5 +137,4 @@ class AnimeUltimeBridge extends BridgeAbstract {
|
|||||||
|
|
||||||
return parent::getName();
|
return parent::getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -119,5 +119,4 @@ class Arte7Bridge extends BridgeAbstract {
|
|||||||
$this->items[] = $item;
|
$this->items[] = $item;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -43,5 +43,4 @@ class BlaguesDeMerdeBridge extends BridgeAbstract {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -83,5 +83,4 @@ class BundesbankBridge extends BridgeAbstract {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,7 @@ class CommonDreamsBridge extends FeedExpander {
|
|||||||
|
|
||||||
const MAINTAINER = 'nyutag';
|
const MAINTAINER = 'nyutag';
|
||||||
const NAME = 'CommonDreams Bridge';
|
const NAME = 'CommonDreams Bridge';
|
||||||
const URI = 'http://www.commondreams.org/';
|
const URI = 'https://www.commondreams.org/';
|
||||||
const DESCRIPTION = 'Returns the newest articles.';
|
const DESCRIPTION = 'Returns the newest articles.';
|
||||||
|
|
||||||
public function collectData(){
|
public function collectData(){
|
||||||
|
227
bridges/CrewbayBridge.php
Normal file
227
bridges/CrewbayBridge.php
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
<?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;
|
||||||
|
}
|
||||||
|
}
|
@@ -15,27 +15,25 @@ class DealabsBridge extends PepperBridgeAbstract {
|
|||||||
'hide_expired' => array(
|
'hide_expired' => array(
|
||||||
'name' => 'Masquer les éléments expirés',
|
'name' => 'Masquer les éléments expirés',
|
||||||
'type' => 'checkbox',
|
'type' => 'checkbox',
|
||||||
'required' => 'true'
|
'required' => true
|
||||||
),
|
),
|
||||||
'hide_local' => array(
|
'hide_local' => array(
|
||||||
'name' => 'Masquer les deals locaux',
|
'name' => 'Masquer les deals locaux',
|
||||||
'type' => 'checkbox',
|
'type' => 'checkbox',
|
||||||
'title' => 'Masquer les deals en magasins physiques',
|
'title' => 'Masquer les deals en magasins physiques',
|
||||||
'required' => 'true'
|
'required' => true
|
||||||
),
|
),
|
||||||
'priceFrom' => array(
|
'priceFrom' => array(
|
||||||
'name' => 'Prix minimum',
|
'name' => 'Prix minimum',
|
||||||
'type' => 'text',
|
'type' => 'text',
|
||||||
'title' => 'Prix mnimum en euros',
|
'title' => 'Prix mnimum en euros',
|
||||||
'required' => 'false',
|
'required' => false
|
||||||
'defaultValue' => ''
|
|
||||||
),
|
),
|
||||||
'priceTo' => array(
|
'priceTo' => array(
|
||||||
'name' => 'Prix maximum',
|
'name' => 'Prix maximum',
|
||||||
'type' => 'text',
|
'type' => 'text',
|
||||||
'title' => 'Prix maximum en euros',
|
'title' => 'Prix maximum en euros',
|
||||||
'required' => 'false',
|
'required' => false
|
||||||
'defaultValue' => ''
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
@@ -43,7 +41,7 @@ class DealabsBridge extends PepperBridgeAbstract {
|
|||||||
'group' => array(
|
'group' => array(
|
||||||
'name' => 'Groupe',
|
'name' => 'Groupe',
|
||||||
'type' => 'list',
|
'type' => 'list',
|
||||||
'required' => 'true',
|
'required' => true,
|
||||||
'title' => 'Groupe dont il faut afficher les deals',
|
'title' => 'Groupe dont il faut afficher les deals',
|
||||||
'values' => array(
|
'values' => array(
|
||||||
'Abonnements internet' => 'abonnements-internet',
|
'Abonnements internet' => 'abonnements-internet',
|
||||||
@@ -959,7 +957,7 @@ class DealabsBridge extends PepperBridgeAbstract {
|
|||||||
'order' => array(
|
'order' => array(
|
||||||
'name' => 'Trier par',
|
'name' => 'Trier par',
|
||||||
'type' => 'list',
|
'type' => 'list',
|
||||||
'required' => 'true',
|
'required' => true,
|
||||||
'title' => 'Ordre de tri des deals',
|
'title' => 'Ordre de tri des deals',
|
||||||
'values' => array(
|
'values' => array(
|
||||||
'Du deal le plus Hot au moins Hot' => '',
|
'Du deal le plus Hot au moins Hot' => '',
|
||||||
@@ -1224,7 +1222,6 @@ class PepperBridgeAbstract extends BridgeAbstract {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the Shipping costs from a Deal if it exists
|
* Get the Shipping costs from a Deal if it exists
|
||||||
* @return string String of the deal shipping Cost
|
* @return string String of the deal shipping Cost
|
||||||
@@ -1457,8 +1454,6 @@ class PepperBridgeAbstract extends BridgeAbstract {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is some "localisation" function that returns the needed content using
|
* This is some "localisation" function that returns the needed content using
|
||||||
* the "$lang" class variable in the local class
|
* the "$lang" class variable in the local class
|
||||||
@@ -1472,5 +1467,4 @@ class PepperBridgeAbstract extends BridgeAbstract {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -6,72 +6,75 @@ class DemonoidBridge extends BridgeAbstract {
|
|||||||
const URI = 'https://www.demonoid.pw/';
|
const URI = 'https://www.demonoid.pw/';
|
||||||
const DESCRIPTION = 'Returns results from search';
|
const DESCRIPTION = 'Returns results from search';
|
||||||
|
|
||||||
const PARAMETERS = array(array(
|
const PARAMETERS = array(
|
||||||
'q' => array(
|
'Keywords' => array(
|
||||||
'name' => 'keywords',
|
'q' => array(
|
||||||
'exampleValue' => 'keyword1 keyword2…',
|
'name' => 'keywords',
|
||||||
'required' => true,
|
'exampleValue' => 'keyword1 keyword2…',
|
||||||
|
'required' => true,
|
||||||
|
),
|
||||||
|
'category' => array(
|
||||||
|
'name' => 'Category',
|
||||||
|
'type' => 'list',
|
||||||
|
'values' => array(
|
||||||
|
'All' => 0,
|
||||||
|
'Movies' => 1,
|
||||||
|
'Music' => 2,
|
||||||
|
'TV' => 3,
|
||||||
|
'Games' => 4,
|
||||||
|
'Applications' => 5,
|
||||||
|
'Pictures' => 8,
|
||||||
|
'Anime' => 9,
|
||||||
|
'Comics' => 10,
|
||||||
|
'Books' => 11,
|
||||||
|
'Audiobooks' => 17
|
||||||
|
)
|
||||||
|
)
|
||||||
),
|
),
|
||||||
'category' => array(
|
'Category Only' => array(
|
||||||
'name' => 'Category',
|
'catOnly' => array(
|
||||||
'type' => 'list',
|
'name' => 'Category',
|
||||||
'values' => array(
|
'type' => 'list',
|
||||||
'All' => 0,
|
'values' => array(
|
||||||
'Movies' => 1,
|
'All' => 0,
|
||||||
'Music' => 2,
|
'Movies' => 1,
|
||||||
'TV' => 3,
|
'Music' => 2,
|
||||||
'Games' => 4,
|
'TV' => 3,
|
||||||
'Applications' => 5,
|
'Games' => 4,
|
||||||
'Pictures' => 8,
|
'Applications' => 5,
|
||||||
'Anime' => 9,
|
'Pictures' => 8,
|
||||||
'Comics' => 10,
|
'Anime' => 9,
|
||||||
'Books' => 11,
|
'Comics' => 10,
|
||||||
'Audiobooks' => 17
|
'Books' => 11,
|
||||||
|
'Audiobooks' => 17
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
), array(
|
|
||||||
'catOnly' => array(
|
|
||||||
'name' => 'Category',
|
|
||||||
'type' => 'list',
|
|
||||||
'values' => array(
|
|
||||||
'All' => 0,
|
|
||||||
'Movies' => 1,
|
|
||||||
'Music' => 2,
|
|
||||||
'TV' => 3,
|
|
||||||
'Games' => 4,
|
|
||||||
'Applications' => 5,
|
|
||||||
'Pictures' => 8,
|
|
||||||
'Anime' => 9,
|
|
||||||
'Comics' => 10,
|
|
||||||
'Books' => 11,
|
|
||||||
'Audiobooks' => 17
|
|
||||||
)
|
|
||||||
)
|
|
||||||
), array(
|
|
||||||
'userid' => array(
|
|
||||||
'name' => 'user id',
|
|
||||||
'exampleValue' => '00000',
|
|
||||||
'required' => true,
|
|
||||||
'type' => 'number'
|
|
||||||
),
|
),
|
||||||
'category' => array(
|
'User ID' => array(
|
||||||
'name' => 'Category',
|
'userid' => array(
|
||||||
'type' => 'list',
|
'name' => 'user id',
|
||||||
'values' => array(
|
'exampleValue' => '00000',
|
||||||
'All' => 0,
|
'required' => true,
|
||||||
'Movies' => 1,
|
'type' => 'number'
|
||||||
'Music' => 2,
|
),
|
||||||
'TV' => 3,
|
'category' => array(
|
||||||
'Games' => 4,
|
'name' => 'Category',
|
||||||
'Applications' => 5,
|
'type' => 'list',
|
||||||
'Pictures' => 8,
|
'values' => array(
|
||||||
'Anime' => 9,
|
'All' => 0,
|
||||||
'Comics' => 10,
|
'Movies' => 1,
|
||||||
'Books' => 11,
|
'Music' => 2,
|
||||||
'Audiobooks' => 17
|
'TV' => 3,
|
||||||
|
'Games' => 4,
|
||||||
|
'Applications' => 5,
|
||||||
|
'Pictures' => 8,
|
||||||
|
'Anime' => 9,
|
||||||
|
'Comics' => 10,
|
||||||
|
'Books' => 11,
|
||||||
|
'Audiobooks' => 17
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
public function collectData() {
|
public function collectData() {
|
||||||
|
113
bridges/DerpibooruBridge.php
Normal file
113
bridges/DerpibooruBridge.php
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
<?php
|
||||||
|
class DerpibooruBridge extends BridgeAbstract {
|
||||||
|
const NAME = 'Derpibooru Bridge';
|
||||||
|
const URI = 'https://derpibooru.org/';
|
||||||
|
const DESCRIPTION = 'Returns newest posts from a Derpibooru search';
|
||||||
|
const CACHE_TIMEOUT = 300; // 5min
|
||||||
|
const MAINTAINER = 'Roliga';
|
||||||
|
|
||||||
|
const PARAMETERS = array(
|
||||||
|
array(
|
||||||
|
'f' => array(
|
||||||
|
'name' => 'Filter',
|
||||||
|
'type' => 'list',
|
||||||
|
'values' => array(
|
||||||
|
'Everything' => 56027,
|
||||||
|
'18+ R34' => 37432,
|
||||||
|
'Legacy Default' => 37431,
|
||||||
|
'18+ Dark' => 37429,
|
||||||
|
'Maximum Spoilers' => 37430,
|
||||||
|
'Default' => 100073
|
||||||
|
),
|
||||||
|
'defaultValue' => 56027
|
||||||
|
|
||||||
|
),
|
||||||
|
'q' => array(
|
||||||
|
'name' => 'Query',
|
||||||
|
'required' => true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
public function detectParameters($url){
|
||||||
|
$params = array();
|
||||||
|
|
||||||
|
// Search page e.g. https://derpibooru.org/search?q=cute
|
||||||
|
$regex = '/^(https?:\/\/)?(www\.)?derpibooru.org\/search.+q=([^\/&?\n]+)/';
|
||||||
|
if(preg_match($regex, $url, $matches) > 0) {
|
||||||
|
$params['q'] = urldecode($matches[3]);
|
||||||
|
return $params;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tag page, e.g. https://derpibooru.org/tags/artist-colon-devinian
|
||||||
|
$regex = '/^(https?:\/\/)?(www\.)?derpibooru.org\/tags\/([^\/&?\n]+)/';
|
||||||
|
if(preg_match($regex, $url, $matches) > 0) {
|
||||||
|
$params['q'] = str_replace('-colon-', ':', urldecode($matches[3]));
|
||||||
|
return $params;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName(){
|
||||||
|
if(!is_null($this->getInput('q'))) {
|
||||||
|
return 'Derpibooru search for: '
|
||||||
|
. $this->getInput('q');
|
||||||
|
} else {
|
||||||
|
return parent::getName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getURI(){
|
||||||
|
if(!is_null($this->getInput('f')) && !is_null($this->getInput('q'))) {
|
||||||
|
return self::URI
|
||||||
|
. 'search?filter_id='
|
||||||
|
. urlencode($this->getInput('f'))
|
||||||
|
. '&q='
|
||||||
|
. urlencode($this->getInput('q'));
|
||||||
|
} else {
|
||||||
|
return parent::getURI();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function collectData(){
|
||||||
|
$queryJson = json_decode(getContents(
|
||||||
|
self::URI
|
||||||
|
. 'search.json?filter_id='
|
||||||
|
. urlencode($this->getInput('f'))
|
||||||
|
. '&q='
|
||||||
|
. urlencode($this->getInput('q'))
|
||||||
|
)) or returnServerError('Failed to query Derpibooru');
|
||||||
|
|
||||||
|
foreach($queryJson->search as $post) {
|
||||||
|
$item = array();
|
||||||
|
|
||||||
|
$postUri = self::URI . $post->id;
|
||||||
|
|
||||||
|
$item['uri'] = $postUri;
|
||||||
|
$item['title'] = $post->id;
|
||||||
|
$item['timestamp'] = strtotime($post->created_at);
|
||||||
|
$item['author'] = $post->uploader;
|
||||||
|
$item['enclosures'] = array('https:' . $post->image);
|
||||||
|
$item['categories'] = explode(', ', $post->tags);
|
||||||
|
|
||||||
|
$item['content'] = '<p><a href="' // image preview
|
||||||
|
. $postUri
|
||||||
|
. '"><img src="https:'
|
||||||
|
. $post->representations->medium
|
||||||
|
. '"></a></p><p>' // description
|
||||||
|
. $post->description
|
||||||
|
. '</p><p><b>Size:</b> ' // image size
|
||||||
|
. $post->width
|
||||||
|
. 'x'
|
||||||
|
. $post->height
|
||||||
|
. '<br><b>Source:</b> <a href="' // source link
|
||||||
|
. $post->source_url
|
||||||
|
. '">'
|
||||||
|
. $post->source_url
|
||||||
|
. '</a></p>';
|
||||||
|
|
||||||
|
$this->items[] = $item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -236,5 +236,4 @@ class DesoutterBridge extends BridgeAbstract {
|
|||||||
|
|
||||||
echo $list;
|
echo $list;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -22,8 +22,7 @@ class DevToBridge extends BridgeAbstract {
|
|||||||
'name' => 'Full article',
|
'name' => 'Full article',
|
||||||
'type' => 'checkbox',
|
'type' => 'checkbox',
|
||||||
'required' => false,
|
'required' => false,
|
||||||
'title' => 'Enable to receive the full article for each item',
|
'title' => 'Enable to receive the full article for each item'
|
||||||
'defaultValue' => false
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -101,5 +100,4 @@ EOD;
|
|||||||
|
|
||||||
return $html->find('[id="article-body"]', 0);
|
return $html->find('[id="article-body"]', 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,7 @@ class DilbertBridge extends BridgeAbstract {
|
|||||||
|
|
||||||
const MAINTAINER = 'kranack';
|
const MAINTAINER = 'kranack';
|
||||||
const NAME = 'Dilbert Daily Strip';
|
const NAME = 'Dilbert Daily Strip';
|
||||||
const URI = 'http://dilbert.com';
|
const URI = 'https://dilbert.com';
|
||||||
const CACHE_TIMEOUT = 21600; // 6h
|
const CACHE_TIMEOUT = 21600; // 6h
|
||||||
const DESCRIPTION = 'The Unofficial Dilbert Daily Comic Strip';
|
const DESCRIPTION = 'The Unofficial Dilbert Daily Comic Strip';
|
||||||
|
|
||||||
@@ -17,9 +17,9 @@ class DilbertBridge extends BridgeAbstract {
|
|||||||
$img = $element->find('img', 0);
|
$img = $element->find('img', 0);
|
||||||
$link = $element->find('a', 0);
|
$link = $element->find('a', 0);
|
||||||
$comic = $img->src;
|
$comic = $img->src;
|
||||||
$title = $link->alt;
|
$title = $img->alt;
|
||||||
$url = $link->href;
|
$url = $link->href;
|
||||||
$date = substr($url, 25);
|
$date = substr(strrchr($url, '/'), 1);
|
||||||
if (empty($title))
|
if (empty($title))
|
||||||
$title = 'Dilbert Comic Strip on ' . $date;
|
$title = 'Dilbert Comic Strip on ' . $date;
|
||||||
$date = strtotime($date);
|
$date = strtotime($date);
|
||||||
|
@@ -62,7 +62,11 @@ class DiscogsBridge extends BridgeAbstract {
|
|||||||
$item['id'] = $release['id'];
|
$item['id'] = $release['id'];
|
||||||
$resId = array_key_exists('main_release', $release) ? $release['main_release'] : $release['id'];
|
$resId = array_key_exists('main_release', $release) ? $release['main_release'] : $release['id'];
|
||||||
$item['uri'] = self::URI . $this->getInput('artistid') . '/release/' . $resId;
|
$item['uri'] = self::URI . $this->getInput('artistid') . '/release/' . $resId;
|
||||||
$item['timestamp'] = DateTime::createFromFormat('Y', $release['year'])->getTimestamp();
|
|
||||||
|
if(isset($release['year'])) {
|
||||||
|
$item['timestamp'] = DateTime::createFromFormat('Y', $release['year'])->getTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
$item['content'] = $item['author'] . ' - ' . $item['title'];
|
$item['content'] = $item['author'] . ' - ' . $item['title'];
|
||||||
$this->items[] = $item;
|
$this->items[] = $item;
|
||||||
}
|
}
|
||||||
|
@@ -6,25 +6,36 @@ class EliteDangerousGalnetBridge extends BridgeAbstract {
|
|||||||
const URI = 'https://community.elitedangerous.com/galnet/';
|
const URI = 'https://community.elitedangerous.com/galnet/';
|
||||||
const CACHE_TIMEOUT = 7200; // 2h
|
const CACHE_TIMEOUT = 7200; // 2h
|
||||||
const DESCRIPTION = 'Returns the latest page of news from Galnet';
|
const DESCRIPTION = 'Returns the latest page of news from Galnet';
|
||||||
|
const PARAMETERS = array(
|
||||||
public function getIcon() {
|
array(
|
||||||
return 'https://community.elitedangerous.com/sites/
|
'language' => array(
|
||||||
EDSITE_COMM/themes/bootstrap/bootstrap_community/favicon.ico';
|
'name' => 'Language',
|
||||||
}
|
'type' => 'list',
|
||||||
|
'values' => array(
|
||||||
|
'English' => 'en',
|
||||||
|
'French' => 'fr',
|
||||||
|
'German' => 'de'
|
||||||
|
),
|
||||||
|
'defaultValue' => 'en'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
public function collectData(){
|
public function collectData(){
|
||||||
$html = getSimpleHTMLDOM(self::URI)
|
$language = $this->getInput('language');
|
||||||
|
$url = 'https://community.elitedangerous.com/';
|
||||||
|
$url = $url . $language . '/galnet';
|
||||||
|
$html = getSimpleHTMLDOM($url)
|
||||||
or returnServerError('Error while downloading the website content');
|
or returnServerError('Error while downloading the website content');
|
||||||
|
|
||||||
foreach($html->find('div.article') as $element) {
|
foreach($html->find('div.article') as $element) {
|
||||||
$item = array();
|
$item = array();
|
||||||
|
|
||||||
$uri = $element->find('h3 a', 0)->href;
|
$uri = $element->find('h3 a', 0)->href;
|
||||||
$uri = self::URI . substr($uri, strlen('/galnet/'));
|
$uri = 'https://community.elitedangerous.com/' . $language . $uri;
|
||||||
$item['uri'] = $uri;
|
$item['uri'] = $uri;
|
||||||
|
|
||||||
$title = $element->find('h3 a', 0)->plaintext;
|
$item['title'] = $element->find('h3 a', 0)->plaintext;
|
||||||
$item['title'] = substr($title, 1); //remove the space between icon and title
|
|
||||||
|
|
||||||
$content = $element->find('p', -1)->innertext;
|
$content = $element->find('p', -1)->innertext;
|
||||||
$item['content'] = $content;
|
$item['content'] = $content;
|
||||||
|
@@ -121,7 +121,7 @@ class ElloBridge extends BridgeAbstract {
|
|||||||
|
|
||||||
private function getAPIKey() {
|
private function getAPIKey() {
|
||||||
$cache = Cache::create('FileCache');
|
$cache = Cache::create('FileCache');
|
||||||
$cache->setPath(CACHE_DIR);
|
$cache->setPath(PATH_CACHE);
|
||||||
$cache->setParameters(['key']);
|
$cache->setParameters(['key']);
|
||||||
$key = $cache->loadData();
|
$key = $cache->loadData();
|
||||||
|
|
||||||
@@ -143,5 +143,4 @@ class ElloBridge extends BridgeAbstract {
|
|||||||
|
|
||||||
return parent::getName();
|
return parent::getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -15,7 +15,7 @@ class ExtremeDownloadBridge extends BridgeAbstract {
|
|||||||
'filter' => array(
|
'filter' => array(
|
||||||
'name' => 'Type de contenu',
|
'name' => 'Type de contenu',
|
||||||
'type' => 'list',
|
'type' => 'list',
|
||||||
'required' => 'true',
|
'required' => true,
|
||||||
'title' => 'Type de contenu à suivre : Téléchargement, Streaming ou les deux',
|
'title' => 'Type de contenu à suivre : Téléchargement, Streaming ou les deux',
|
||||||
'values' => array(
|
'values' => array(
|
||||||
'Streaming et Téléchargement' => 'both',
|
'Streaming et Téléchargement' => 'both',
|
||||||
@@ -100,5 +100,4 @@ class ExtremeDownloadBridge extends BridgeAbstract {
|
|||||||
|
|
||||||
return $return;
|
return $return;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -103,7 +103,7 @@ EOD;
|
|||||||
$timestamp = 0;
|
$timestamp = 0;
|
||||||
|
|
||||||
$item['uri'] = html_entity_decode('http://touch.facebook.com'
|
$item['uri'] = html_entity_decode('http://touch.facebook.com'
|
||||||
. $content->find("div[class='_52jc _5qc4 _24u0 _36xo']", 0)->find('a', 0)->getAttribute('href'), ENT_QUOTES);
|
. $content->find("div[class='_52jc _5qc4 _78cz _24u0 _36xo']", 0)->find('a', 0)->getAttribute('href'), ENT_QUOTES);
|
||||||
|
|
||||||
//Decode images
|
//Decode images
|
||||||
$imagecleaned = preg_replace_callback('/<i [^>]* style="[^"]*url\(\'(.*?)\'\).*?><\/i>/m', function ($matches) {
|
$imagecleaned = preg_replace_callback('/<i [^>]* style="[^"]*url\(\'(.*?)\'\).*?><\/i>/m', function ($matches) {
|
||||||
@@ -198,7 +198,6 @@ EOD;
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//Builds the HTML from the encoded JS that Facebook provides.
|
//Builds the HTML from the encoded JS that Facebook provides.
|
||||||
private function buildContent($pageContent){
|
private function buildContent($pageContent){
|
||||||
// The html ends with:
|
// The html ends with:
|
||||||
@@ -213,7 +212,6 @@ EOD;
|
|||||||
return str_get_html($htmlContent);
|
return str_get_html($htmlContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//Builds the cookie from the page, as Facebook sometimes refuses to give
|
//Builds the cookie from the page, as Facebook sometimes refuses to give
|
||||||
//the page if no cookie is provided.
|
//the page if no cookie is provided.
|
||||||
private function getCookies($pageURL){
|
private function getCookies($pageURL){
|
||||||
@@ -289,5 +287,4 @@ EOD;
|
|||||||
public function getURI(){
|
public function getURI(){
|
||||||
return 'http://facebook.com';
|
return 'http://facebook.com';
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -179,8 +179,7 @@ class FacebookBridge extends BridgeAbstract {
|
|||||||
|
|
||||||
if(filter_var(
|
if(filter_var(
|
||||||
$group,
|
$group,
|
||||||
FILTER_VALIDATE_URL,
|
FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED)) {
|
||||||
FILTER_FLAG_HOST_REQUIRED | FILTER_FLAG_PATH_REQUIRED)) {
|
|
||||||
// User provided a URL
|
// User provided a URL
|
||||||
|
|
||||||
$urlparts = parse_url($group);
|
$urlparts = parse_url($group);
|
||||||
@@ -364,6 +363,26 @@ class FacebookBridge extends BridgeAbstract {
|
|||||||
}, $content);
|
}, $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.
|
* Convert textual representation of emoticons back to ASCII emoticons.
|
||||||
* i.e. "<i><u>smile emoticon</u></i>" => ":)"
|
* i.e. "<i><u>smile emoticon</u></i>" => ":)"
|
||||||
@@ -426,8 +445,7 @@ class FacebookBridge extends BridgeAbstract {
|
|||||||
// Show captcha filling form to the viewer, proxying the captcha image
|
// Show captcha filling form to the viewer, proxying the captcha image
|
||||||
$img = base64_encode(getContents($captcha->find('img', 0)->src));
|
$img = base64_encode(getContents($captcha->find('img', 0)->src));
|
||||||
|
|
||||||
http_response_code(500);
|
header('Content-Type: text/html', true, 500);
|
||||||
header('Content-Type: text/html');
|
|
||||||
|
|
||||||
$message = <<<EOD
|
$message = <<<EOD
|
||||||
<form method="post" action="?{$_SERVER['QUERY_STRING']}">
|
<form method="post" action="?{$_SERVER['QUERY_STRING']}">
|
||||||
@@ -519,7 +537,7 @@ EOD;
|
|||||||
|
|
||||||
if(isset($element)) {
|
if(isset($element)) {
|
||||||
|
|
||||||
$author = str_replace(' | Facebook', '', $html->find('title#pageTitle', 0)->innertext);
|
$author = str_replace(' - Posts | Facebook', '', $html->find('title#pageTitle', 0)->innertext);
|
||||||
|
|
||||||
$profilePic = $html->find('meta[property="og:image"]', 0)->content;
|
$profilePic = $html->find('meta[property="og:image"]', 0)->content;
|
||||||
|
|
||||||
@@ -558,10 +576,28 @@ EOD;
|
|||||||
|
|
||||||
$content = $post->find('.userContentWrapper', 0);
|
$content = $post->find('.userContentWrapper', 0);
|
||||||
|
|
||||||
$content = preg_replace(
|
// This array specifies filters applied to all posts in order of appearance
|
||||||
'/(?i)><div class=\"_59tj([^>]+)>(.+?)<\/div><\/div><a/i',
|
$content_filters = array(
|
||||||
'',
|
'._5mly', // Remove embedded videos (the preview image remains)
|
||||||
$content);
|
'._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(
|
$content = preg_replace(
|
||||||
'/(?i)><div class=\"_3dp([^>]+)>(.+?)div\ class=\"[^u]+userContent\"/i',
|
'/(?i)><div class=\"_3dp([^>]+)>(.+?)div\ class=\"[^u]+userContent\"/i',
|
||||||
@@ -611,6 +647,8 @@ EOD;
|
|||||||
// Restore links in the content before adding to the item
|
// Restore links in the content before adding to the item
|
||||||
$content = defaultLinkTo($content, self::URI);
|
$content = defaultLinkTo($content, self::URI);
|
||||||
|
|
||||||
|
$content = $this->remove_tracking_codes($content);
|
||||||
|
|
||||||
// Retrieve date of the post
|
// Retrieve date of the post
|
||||||
$date = $post->find('abbr')[0];
|
$date = $post->find('abbr')[0];
|
||||||
|
|
||||||
@@ -620,14 +658,8 @@ EOD;
|
|||||||
$date = 0;
|
$date = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build title from username and content
|
// Build title from content
|
||||||
$title = $author;
|
$title = strip_tags($post->find('.userContent', 0)->innertext);
|
||||||
|
|
||||||
if(strlen($title) > 24)
|
|
||||||
$title = substr($title, 0, strpos(wordwrap($title, 24), "\n")) . '...';
|
|
||||||
|
|
||||||
$title = $title . ' | ' . strip_tags($content);
|
|
||||||
|
|
||||||
if(strlen($title) > 64)
|
if(strlen($title) > 64)
|
||||||
$title = substr($title, 0, strpos(wordwrap($title, 64), "\n")) . '...';
|
$title = substr($title, 0, strpos(wordwrap($title, 64), "\n")) . '...';
|
||||||
|
|
||||||
@@ -638,10 +670,10 @@ EOD;
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Build and add final item
|
//Build and add final item
|
||||||
$item['uri'] = htmlspecialchars_decode($uri);
|
$item['uri'] = htmlspecialchars_decode($uri, ENT_QUOTES);
|
||||||
$item['content'] = htmlspecialchars_decode($content);
|
$item['content'] = htmlspecialchars_decode($content, ENT_QUOTES);
|
||||||
$item['title'] = $title;
|
$item['title'] = htmlspecialchars_decode($title, ENT_QUOTES);
|
||||||
$item['author'] = $author;
|
$item['author'] = htmlspecialchars_decode($author, ENT_QUOTES);
|
||||||
$item['timestamp'] = $date;
|
$item['timestamp'] = $date;
|
||||||
|
|
||||||
if(strpos($item['content'], '<img') === false) {
|
if(strpos($item['content'], '<img') === false) {
|
||||||
@@ -654,7 +686,6 @@ EOD;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion (User)
|
#endregion (User)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,7 @@ class FeedExpanderExampleBridge extends FeedExpander {
|
|||||||
|
|
||||||
const MAINTAINER = 'logmanoriginal';
|
const MAINTAINER = 'logmanoriginal';
|
||||||
const NAME = 'FeedExpander Example';
|
const NAME = 'FeedExpander Example';
|
||||||
const URI = '#';
|
const URI = 'http://github.com/RSS-Bridge/rss-bridge/';
|
||||||
const DESCRIPTION = 'Example bridge to test FeedExpander';
|
const DESCRIPTION = 'Example bridge to test FeedExpander';
|
||||||
|
|
||||||
const PARAMETERS = array(
|
const PARAMETERS = array(
|
||||||
|
@@ -94,7 +94,7 @@ class FilterBridge extends FeedExpander {
|
|||||||
}
|
}
|
||||||
try{
|
try{
|
||||||
$this->collectExpandableDatas($this->getURI());
|
$this->collectExpandableDatas($this->getURI());
|
||||||
} catch (HttpException $e) {
|
} catch (Exception $e) {
|
||||||
$this->collectExpandableDatas($this->getURI());
|
$this->collectExpandableDatas($this->getURI());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
82
bridges/FindACrewBridge.php
Normal file
82
bridges/FindACrewBridge.php
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
<?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;
|
||||||
|
}
|
||||||
|
}
|
@@ -182,5 +182,4 @@ class FlickrBridge extends BridgeAbstract {
|
|||||||
return $url;
|
return $url;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -37,5 +37,4 @@ class ForGifsBridge extends FeedExpander {
|
|||||||
return $item;
|
return $item;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -62,5 +62,4 @@ class GOGBridge extends BridgeAbstract {
|
|||||||
return $content;
|
return $content;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -9,7 +9,6 @@
|
|||||||
*/
|
*/
|
||||||
class GQMagazineBridge extends BridgeAbstract
|
class GQMagazineBridge extends BridgeAbstract
|
||||||
{
|
{
|
||||||
|
|
||||||
const MAINTAINER = 'Riduidel';
|
const MAINTAINER = 'Riduidel';
|
||||||
|
|
||||||
const NAME = 'GQMagazine';
|
const NAME = 'GQMagazine';
|
||||||
@@ -20,18 +19,18 @@ class GQMagazineBridge extends BridgeAbstract
|
|||||||
const CACHE_TIMEOUT = 7200; // 2h
|
const CACHE_TIMEOUT = 7200; // 2h
|
||||||
const DESCRIPTION = 'GQMagazine section extractor bridge. This bridge allows you get only a specific section.';
|
const DESCRIPTION = 'GQMagazine section extractor bridge. This bridge allows you get only a specific section.';
|
||||||
|
|
||||||
|
const DEFAULT_DOMAIN = 'www.gqmagazine.fr';
|
||||||
|
|
||||||
const PARAMETERS = array( array(
|
const PARAMETERS = array( array(
|
||||||
'domain' => array(
|
'domain' => array(
|
||||||
'name' => 'Domain to use',
|
'name' => 'Domain to use',
|
||||||
'required' => true,
|
'required' => true,
|
||||||
'values' => array(
|
'defaultValue' => self::DEFAULT_DOMAIN
|
||||||
'www.gqmagazine.fr' => 'www.gqmagazine.fr'
|
|
||||||
),
|
|
||||||
'defaultValue' => 'www.gqmagazine.fr'
|
|
||||||
),
|
),
|
||||||
'page' => array(
|
'page' => array(
|
||||||
'name' => 'Initial page to load',
|
'name' => 'Initial page to load',
|
||||||
'required' => true
|
'required' => true,
|
||||||
|
'exampleValue' => 'sexe/news'
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
|
|
||||||
@@ -42,7 +41,12 @@ class GQMagazineBridge extends BridgeAbstract
|
|||||||
);
|
);
|
||||||
|
|
||||||
private function getDomain() {
|
private function getDomain() {
|
||||||
return $this->getInput('domain');
|
$domain = $this->getInput('domain');
|
||||||
|
if (empty($domain))
|
||||||
|
$domain = self::DEFAULT_DOMAIN;
|
||||||
|
if (strpos($domain, '://') === false)
|
||||||
|
$domain = 'https://' . $domain;
|
||||||
|
return $domain;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getURI()
|
public function getURI()
|
||||||
|
@@ -160,5 +160,4 @@ EOD;
|
|||||||
return $content;
|
return $content;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -28,7 +28,7 @@ class GithubIssueBridge extends BridgeAbstract {
|
|||||||
'i' => array(
|
'i' => array(
|
||||||
'name' => 'Issue number',
|
'name' => 'Issue number',
|
||||||
'type' => 'number',
|
'type' => 'number',
|
||||||
'required' => 'true'
|
'required' => true
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -37,10 +37,9 @@ class GithubIssueBridge extends BridgeAbstract {
|
|||||||
$name = $this->getInput('u') . '/' . $this->getInput('p');
|
$name = $this->getInput('u') . '/' . $this->getInput('p');
|
||||||
switch($this->queriedContext) {
|
switch($this->queriedContext) {
|
||||||
case 'Project Issues':
|
case 'Project Issues':
|
||||||
|
$prefix = static::NAME . 's for ';
|
||||||
if($this->getInput('c')) {
|
if($this->getInput('c')) {
|
||||||
$prefix = static::NAME . 's comments for ';
|
$prefix = static::NAME . 's comments for ';
|
||||||
} else {
|
|
||||||
$prefix = static::NAME . 's for ';
|
|
||||||
}
|
}
|
||||||
$name = $prefix . $name;
|
$name = $prefix . $name;
|
||||||
break;
|
break;
|
||||||
@@ -53,8 +52,9 @@ class GithubIssueBridge extends BridgeAbstract {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function getURI(){
|
public function getURI(){
|
||||||
if(!is_null($this->getInput('u')) && !is_null($this->getInput('p'))) {
|
if(null !== $this->getInput('u') && null !== $this->getInput('p')) {
|
||||||
$uri = static::URI . $this->getInput('u') . '/' . $this->getInput('p') . '/issues';
|
$uri = static::URI . $this->getInput('u') . '/'
|
||||||
|
. $this->getInput('p') . '/issues';
|
||||||
if($this->queriedContext === 'Issue comments') {
|
if($this->queriedContext === 'Issue comments') {
|
||||||
$uri .= '/' . $this->getInput('i');
|
$uri .= '/' . $this->getInput('i');
|
||||||
} elseif($this->getInput('c')) {
|
} elseif($this->getInput('c')) {
|
||||||
@@ -66,54 +66,54 @@ class GithubIssueBridge extends BridgeAbstract {
|
|||||||
return parent::getURI();
|
return parent::getURI();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function extractIssueComment($issueNbr, $title, $comment){
|
protected function extractIssueEvent($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();
|
$comment = $comment->firstChild();
|
||||||
if(!$event) {
|
$uri = static::URI . $this->getInput('u') . '/' . $this->getInput('p')
|
||||||
$comment = $comment->nextSibling();
|
. '/issues/' . $issueNbr . '#' . $comment->getAttribute('id');
|
||||||
}
|
|
||||||
|
|
||||||
if($event) {
|
$author = $comment->find('.author', 0)->plaintext;
|
||||||
$title .= ' / ' . substr($class, strpos($class, 'discussion-item-') + strlen('discussion-item-'));
|
|
||||||
if(!$comment->hasAttribute('id')) {
|
$title .= ' / ' . trim($comment->plaintext);
|
||||||
$items = array();
|
|
||||||
$timestamp = strtotime($comment->find('relative-time', 0)->getAttribute('datetime'));
|
$content = $title;
|
||||||
$content = $comment->innertext;
|
if (null !== $comment->nextSibling()) {
|
||||||
while($comment = $comment->nextSibling()) {
|
$content = $comment->nextSibling()->innertext;
|
||||||
$item = array();
|
if ($comment->nextSibling()->nodeName() === 'span') {
|
||||||
$item['author'] = $author;
|
$content = $comment->nextSibling()->nextSibling()->innertext;
|
||||||
$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 = array();
|
||||||
$item['author'] = $author;
|
$item['author'] = $author;
|
||||||
$item['uri'] = $uri . '#' . $comment->getAttribute('id');
|
$item['uri'] = $uri;
|
||||||
$item['title'] = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
|
$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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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['content'] = $content;
|
$item['content'] = $content;
|
||||||
return $item;
|
return $item;
|
||||||
}
|
}
|
||||||
@@ -121,17 +121,29 @@ class GithubIssueBridge extends BridgeAbstract {
|
|||||||
protected function extractIssueComments($issue){
|
protected function extractIssueComments($issue){
|
||||||
$items = array();
|
$items = array();
|
||||||
$title = $issue->find('.gh-header-title', 0)->plaintext;
|
$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);
|
$comments = $issue->find('.js-discussion', 0);
|
||||||
foreach($comments->children() as $comment) {
|
foreach($comments->children() as $comment) {
|
||||||
|
if (!$comment->hasChildNodes()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$comment = $comment->firstChild();
|
||||||
$classes = explode(' ', $comment->getAttribute('class'));
|
$classes = explode(' ', $comment->getAttribute('class'));
|
||||||
if(in_array('discussion-item', $classes)
|
if (in_array('timeline-comment-wrapper', $classes)) {
|
||||||
|| in_array('timeline-comment-wrapper', $classes)) {
|
|
||||||
$item = $this->extractIssueComment($issueNbr, $title, $comment);
|
$item = $this->extractIssueComment($issueNbr, $title, $comment);
|
||||||
if(array_keys($item) !== range(0, count($item) - 1)) {
|
$items[] = $item;
|
||||||
$item = array($item);
|
continue;
|
||||||
|
}
|
||||||
|
while (in_array('discussion-item', $classes)) {
|
||||||
|
$item = $this->extractIssueEvent($issueNbr, $title, $comment);
|
||||||
|
$items[] = $item;
|
||||||
|
$comment = $comment->nextSibling();
|
||||||
|
if (null == $comment) {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
$items = array_merge($items, $item);
|
$classes = explode(' ', $comment->getAttribute('class'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $items;
|
return $items;
|
||||||
@@ -139,7 +151,9 @@ class GithubIssueBridge extends BridgeAbstract {
|
|||||||
|
|
||||||
public function collectData(){
|
public function collectData(){
|
||||||
$html = getSimpleHTMLDOM($this->getURI())
|
$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) {
|
switch($this->queriedContext) {
|
||||||
case 'Issue comments':
|
case 'Issue comments':
|
||||||
@@ -148,31 +162,40 @@ class GithubIssueBridge extends BridgeAbstract {
|
|||||||
case 'Project Issues':
|
case 'Project Issues':
|
||||||
foreach($html->find('.js-active-navigation-container .js-navigation-item') as $issue) {
|
foreach($html->find('.js-active-navigation-container .js-navigation-item') as $issue) {
|
||||||
$info = $issue->find('.opened-by', 0);
|
$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 = array();
|
||||||
$item['content'] = '';
|
$item['content'] = '';
|
||||||
|
|
||||||
if($this->getInput('c')) {
|
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);
|
$issue = getSimpleHTMLDOMCached($uri, static::CACHE_TIMEOUT);
|
||||||
if($issue) {
|
if($issue) {
|
||||||
$this->items = array_merge($this->items, $this->extractIssueComments($issue));
|
$this->items = array_merge(
|
||||||
|
$this->items,
|
||||||
|
$this->extractIssueComments($issue)
|
||||||
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$item['content'] = 'Can not extract comments from ' . $uri;
|
$item['content'] = 'Can not extract comments from ' . $uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
$item['author'] = $info->find('a', 0)->plaintext;
|
$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(
|
$item['title'] = html_entity_decode(
|
||||||
$issue->find('.js-navigation-open', 0)->plaintext,
|
$issue->find('.js-navigation-open', 0)->plaintext,
|
||||||
ENT_QUOTES,
|
ENT_QUOTES,
|
||||||
'UTF-8'
|
'UTF-8'
|
||||||
);
|
);
|
||||||
$comments = $issue->find('.col-5', 0)->plaintext;
|
$comments = trim($issue->find('.col-5', 0)->plaintext);
|
||||||
$item['content'] .= "\n" . 'Comments: ' . ($comments ? $comments : '0');
|
$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;
|
$this->items[] = $item;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -180,7 +203,11 @@ class GithubIssueBridge extends BridgeAbstract {
|
|||||||
|
|
||||||
array_walk($this->items, function(&$item){
|
array_walk($this->items, function(&$item){
|
||||||
$item['content'] = preg_replace('/\s+/', ' ', $item['content']);
|
$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(
|
$item['content'] = str_replace(
|
||||||
'href="#',
|
'href="#',
|
||||||
'href="' . substr($item['uri'], 0, strpos($item['uri'], '#') + 1),
|
'href="' . substr($item['uri'], 0, strpos($item['uri'], '#') + 1),
|
||||||
|
3
bridges/GlassdoorBridge.php
Executable file → Normal file
3
bridges/GlassdoorBridge.php
Executable file → Normal file
@@ -186,8 +186,7 @@ class GlassdoorBridge extends BridgeAbstract {
|
|||||||
* redirection and strange naming conventions.
|
* redirection and strange naming conventions.
|
||||||
*/
|
*/
|
||||||
if(!filter_var($uri,
|
if(!filter_var($uri,
|
||||||
FILTER_VALIDATE_URL,
|
FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED)) {
|
||||||
FILTER_FLAG_SCHEME_REQUIRED | FILTER_FLAG_HOST_REQUIRED | FILTER_FLAG_PATH_REQUIRED)) {
|
|
||||||
returnClientError('The specified URL is invalid!');
|
returnClientError('The specified URL is invalid!');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -205,5 +205,4 @@ class GooglePlusPostBridge extends BridgeAbstract{
|
|||||||
return implode('', [$scheme, $user, $pass, $host, $port, $path, $query, $fragment]);
|
return implode('', [$scheme, $user, $pass, $host, $port, $path, $query, $fragment]);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -17,27 +17,25 @@ class HotUKDealsBridge extends PepperBridgeAbstract {
|
|||||||
'hide_expired' => array(
|
'hide_expired' => array(
|
||||||
'name' => 'Hide expired deals',
|
'name' => 'Hide expired deals',
|
||||||
'type' => 'checkbox',
|
'type' => 'checkbox',
|
||||||
'required' => 'true'
|
'required' => true
|
||||||
),
|
),
|
||||||
'hide_local' => array(
|
'hide_local' => array(
|
||||||
'name' => 'Hide local deals',
|
'name' => 'Hide local deals',
|
||||||
'type' => 'checkbox',
|
'type' => 'checkbox',
|
||||||
'title' => 'Hide deals in physical store',
|
'title' => 'Hide deals in physical store',
|
||||||
'required' => 'true'
|
'required' => true
|
||||||
),
|
),
|
||||||
'priceFrom' => array(
|
'priceFrom' => array(
|
||||||
'name' => 'Minimal Price',
|
'name' => 'Minimal Price',
|
||||||
'type' => 'text',
|
'type' => 'text',
|
||||||
'title' => 'Minmal Price in Pounds',
|
'title' => 'Minmal Price in Pounds',
|
||||||
'required' => 'false',
|
'required' => false
|
||||||
'defaultValue' => ''
|
|
||||||
),
|
),
|
||||||
'priceTo' => array(
|
'priceTo' => array(
|
||||||
'name' => 'Maximum Price',
|
'name' => 'Maximum Price',
|
||||||
'type' => 'text',
|
'type' => 'text',
|
||||||
'title' => 'Maximum Price in Pounds',
|
'title' => 'Maximum Price in Pounds',
|
||||||
'required' => 'false',
|
'required' => false
|
||||||
'defaultValue' => ''
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
@@ -45,7 +43,7 @@ class HotUKDealsBridge extends PepperBridgeAbstract {
|
|||||||
'group' => array(
|
'group' => array(
|
||||||
'name' => 'Group',
|
'name' => 'Group',
|
||||||
'type' => 'list',
|
'type' => 'list',
|
||||||
'required' => 'true',
|
'required' => true,
|
||||||
'title' => 'Group whose deals must be displayed',
|
'title' => 'Group whose deals must be displayed',
|
||||||
'values' => array(
|
'values' => array(
|
||||||
'2DS' => '2ds',
|
'2DS' => '2ds',
|
||||||
@@ -1319,7 +1317,7 @@ class HotUKDealsBridge extends PepperBridgeAbstract {
|
|||||||
'order' => array(
|
'order' => array(
|
||||||
'name' => 'Order by',
|
'name' => 'Order by',
|
||||||
'type' => 'list',
|
'type' => 'list',
|
||||||
'required' => 'true',
|
'required' => true,
|
||||||
'title' => 'Sort order of deals',
|
'title' => 'Sort order of deals',
|
||||||
'values' => array(
|
'values' => array(
|
||||||
'From the most to the least hot deal' => '-hot',
|
'From the most to the least hot deal' => '-hot',
|
||||||
|
@@ -7,18 +7,24 @@ class InstagramBridge extends BridgeAbstract {
|
|||||||
const DESCRIPTION = 'Returns the newest images';
|
const DESCRIPTION = 'Returns the newest images';
|
||||||
|
|
||||||
const PARAMETERS = array(
|
const PARAMETERS = array(
|
||||||
array(
|
'Username' => array(
|
||||||
'u' => array(
|
'u' => array(
|
||||||
'name' => 'username',
|
'name' => 'username',
|
||||||
'required' => true
|
'required' => true
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
array(
|
'Hashtag' => array(
|
||||||
'h' => array(
|
'h' => array(
|
||||||
'name' => 'hashtag',
|
'name' => 'hashtag',
|
||||||
'required' => true
|
'required' => true
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
'Location' => array(
|
||||||
|
'l' => array(
|
||||||
|
'name' => 'location',
|
||||||
|
'required' => true
|
||||||
|
)
|
||||||
|
),
|
||||||
'global' => array(
|
'global' => array(
|
||||||
'media_type' => array(
|
'media_type' => array(
|
||||||
'name' => 'Media type',
|
'name' => 'Media type',
|
||||||
@@ -38,16 +44,18 @@ class InstagramBridge extends BridgeAbstract {
|
|||||||
|
|
||||||
public function collectData(){
|
public function collectData(){
|
||||||
|
|
||||||
if(!is_null($this->getInput('h')) && $this->getInput('media_type') == 'story') {
|
if(is_null($this->getInput('u')) && $this->getInput('media_type') == 'story') {
|
||||||
returnClientError('Stories are not supported for hashtags!');
|
returnClientError('Stories are not supported for hashtags nor locations!');
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = $this->getInstagramJSON($this->getURI());
|
$data = $this->getInstagramJSON($this->getURI());
|
||||||
|
|
||||||
if(!is_null($this->getInput('u'))) {
|
if(!is_null($this->getInput('u'))) {
|
||||||
$userMedia = $data->entry_data->ProfilePage[0]->graphql->user->edge_owner_to_timeline_media->edges;
|
$userMedia = $data->entry_data->ProfilePage[0]->graphql->user->edge_owner_to_timeline_media->edges;
|
||||||
} else {
|
} elseif(!is_null($this->getInput('h'))) {
|
||||||
$userMedia = $data->entry_data->TagPage[0]->graphql->hashtag->edge_hashtag_to_media->edges;
|
$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) {
|
foreach($userMedia as $media) {
|
||||||
@@ -74,10 +82,20 @@ class InstagramBridge extends BridgeAbstract {
|
|||||||
$item = array();
|
$item = array();
|
||||||
$item['uri'] = self::URI . 'p/' . $media->shortcode . '/';
|
$item['uri'] = self::URI . 'p/' . $media->shortcode . '/';
|
||||||
|
|
||||||
|
if (isset($media->owner->username)) {
|
||||||
|
$item['author'] = $media->owner->username;
|
||||||
|
}
|
||||||
|
|
||||||
if (isset($media->edge_media_to_caption->edges[0]->node->text)) {
|
if (isset($media->edge_media_to_caption->edges[0]->node->text)) {
|
||||||
$item['title'] = $media->edge_media_to_caption->edges[0]->node->text;
|
$textContent = $media->edge_media_to_caption->edges[0]->node->text;
|
||||||
} else {
|
} else {
|
||||||
$item['title'] = basename($media->display_url);
|
$textContent = basename($media->display_url);
|
||||||
|
}
|
||||||
|
|
||||||
|
$item['title'] = ($media->is_video ? '▶ ' : '') . trim($textContent);
|
||||||
|
$titleLinePos = strpos(wordwrap($item['title'], 120), "\n");
|
||||||
|
if ($titleLinePos != false) {
|
||||||
|
$item['title'] = substr($item['title'], 0, $titleLinePos) . '...';
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!is_null($this->getInput('u')) && $media->__typename == 'GraphSidecar') {
|
if(!is_null($this->getInput('u')) && $media->__typename == 'GraphSidecar') {
|
||||||
@@ -85,7 +103,9 @@ class InstagramBridge extends BridgeAbstract {
|
|||||||
$item['content'] = $data[0];
|
$item['content'] = $data[0];
|
||||||
$item['enclosures'] = $data[1];
|
$item['enclosures'] = $data[1];
|
||||||
} else {
|
} else {
|
||||||
$item['content'] = '<img src="' . htmlentities($media->display_url) . '" alt="' . $item['title'] . '" />';
|
$item['content'] = '<a href="' . htmlentities($item['uri']) . '" target="_blank">';
|
||||||
|
$item['content'] .= '<img src="' . htmlentities($media->display_url) . '" alt="' . $item['title'] . '" />';
|
||||||
|
$item['content'] .= '</a><br><br>' . nl2br(htmlentities($textContent));
|
||||||
$item['enclosures'] = array($media->display_url);
|
$item['enclosures'] = array($media->display_url);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,8 +167,9 @@ class InstagramBridge extends BridgeAbstract {
|
|||||||
return self::URI . urlencode($this->getInput('u')) . '/';
|
return self::URI . urlencode($this->getInput('u')) . '/';
|
||||||
} elseif(!is_null($this->getInput('h'))) {
|
} elseif(!is_null($this->getInput('h'))) {
|
||||||
return self::URI . 'explore/tags/' . urlencode($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();
|
return parent::getURI();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -132,7 +132,7 @@ class JustETFBridge extends BridgeAbstract {
|
|||||||
|
|
||||||
date_time_set($df, 0, 0);
|
date_time_set($df, 0, 0);
|
||||||
|
|
||||||
// debugMessage(date_format($df, 'U'));
|
// Debug::log(date_format($df, 'U'));
|
||||||
|
|
||||||
return date_format($df, 'U');
|
return date_format($df, 'U');
|
||||||
}
|
}
|
||||||
@@ -210,7 +210,7 @@ class JustETFBridge extends BridgeAbstract {
|
|||||||
$element = $article->find('div.subheadline', 0)
|
$element = $article->find('div.subheadline', 0)
|
||||||
or returnServerError('Date not found!');
|
or returnServerError('Date not found!');
|
||||||
|
|
||||||
// debugMessage($element->plaintext);
|
// Debug::log($element->plaintext);
|
||||||
|
|
||||||
$date = trim(explode('|', $element->plaintext)[0]);
|
$date = trim(explode('|', $element->plaintext)[0]);
|
||||||
|
|
||||||
@@ -223,7 +223,7 @@ class JustETFBridge extends BridgeAbstract {
|
|||||||
|
|
||||||
$element->find('a', 0)->onclick = '';
|
$element->find('a', 0)->onclick = '';
|
||||||
|
|
||||||
// debugMessage($element->innertext);
|
// Debug::log($element->innertext);
|
||||||
|
|
||||||
return $element->innertext;
|
return $element->innertext;
|
||||||
}
|
}
|
||||||
@@ -288,7 +288,7 @@ class JustETFBridge extends BridgeAbstract {
|
|||||||
$element = $html->find('div.infobox div.vallabel', 0)
|
$element = $html->find('div.infobox div.vallabel', 0)
|
||||||
or returnServerError('Date not found!');
|
or returnServerError('Date not found!');
|
||||||
|
|
||||||
// debugMessage($element->plaintext);
|
// Debug::log($element->plaintext);
|
||||||
|
|
||||||
$date = trim(explode("\r\n", $element->plaintext)[1]);
|
$date = trim(explode("\r\n", $element->plaintext)[1]);
|
||||||
|
|
||||||
@@ -348,6 +348,5 @@ class JustETFBridge extends BridgeAbstract {
|
|||||||
|
|
||||||
return $element->plaintext;
|
return $element->plaintext;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
@@ -53,6 +53,7 @@ class KATBridge extends BridgeAbstract {
|
|||||||
$guessedDate['tm_year'] + 1900);
|
$guessedDate['tm_year'] + 1900);
|
||||||
return $timestamp;
|
return $timestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
$catBool = $this->getInput('cat_check');
|
$catBool = $this->getInput('cat_check');
|
||||||
if($catBool) {
|
if($catBool) {
|
||||||
$catNum = $this->getInput('cat');
|
$catNum = $this->getInput('cat');
|
||||||
|
@@ -150,5 +150,4 @@ class KernelBugTrackerBridge extends BridgeAbstract {
|
|||||||
|
|
||||||
return $html;
|
return $html;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -419,7 +419,6 @@ class LeBonCoinBridge extends BridgeAbstract {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private function buildRequestJson() {
|
private function buildRequestJson() {
|
||||||
|
|
||||||
$requestJson = new StdClass();
|
$requestJson = new StdClass();
|
||||||
@@ -534,5 +533,4 @@ class LeBonCoinBridge extends BridgeAbstract {
|
|||||||
return json_encode($requestJson);
|
return json_encode($requestJson);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
102
bridges/ModelKarteiBridge.php
Normal file
102
bridges/ModelKarteiBridge.php
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
<?php
|
||||||
|
class ModelKarteiBridge extends BridgeAbstract {
|
||||||
|
const NAME = 'model-kartei.de';
|
||||||
|
const URI = 'https://www.model-kartei.de/';
|
||||||
|
const DESCRIPTION = 'Get the public comp card gallery';
|
||||||
|
const MAINTAINER = 'fulmeek';
|
||||||
|
const PARAMETERS = array(array(
|
||||||
|
'model_id' => array(
|
||||||
|
'name' => 'Model ID',
|
||||||
|
'exampleValue' => '123456'
|
||||||
|
)
|
||||||
|
));
|
||||||
|
|
||||||
|
const LIMIT_ITEMS = 10;
|
||||||
|
|
||||||
|
private $feedName = '';
|
||||||
|
|
||||||
|
public function collectData() {
|
||||||
|
$model_id = preg_replace('/[^0-9]/', '', $this->getInput('model_id'));
|
||||||
|
if (empty($model_id))
|
||||||
|
returnServerError('Invalid model ID');
|
||||||
|
|
||||||
|
$html = getSimpleHTMLDOM(self::URI . 'sedcards/model/' . $model_id . '/')
|
||||||
|
or returnServerError('Model not found');
|
||||||
|
|
||||||
|
$objTitle = $html->find('.sTitle', 0);
|
||||||
|
if ($objTitle)
|
||||||
|
$this->feedName = $objTitle->plaintext;
|
||||||
|
|
||||||
|
$itemlist = $html->find('#photoList .photoPreview');
|
||||||
|
if (!$itemlist)
|
||||||
|
returnServerError('No gallery');
|
||||||
|
|
||||||
|
foreach($itemlist as $idx => $element) {
|
||||||
|
if ($idx >= self::LIMIT_ITEMS)
|
||||||
|
break;
|
||||||
|
|
||||||
|
$item = array();
|
||||||
|
|
||||||
|
$title = $element->title;
|
||||||
|
$date = $element->{'data-date'};
|
||||||
|
$author = $this->feedName;
|
||||||
|
$text = '';
|
||||||
|
|
||||||
|
$objImage = $element->find('a.photoLink img', 0);
|
||||||
|
$objLink = $element->find('a.photoLink', 0);
|
||||||
|
|
||||||
|
if ($objLink) {
|
||||||
|
$page = getSimpleHTMLDOMCached($objLink->href);
|
||||||
|
|
||||||
|
if (empty($title)) {
|
||||||
|
$objTitle = $page->find('.p-title', 0);
|
||||||
|
if ($objTitle)
|
||||||
|
$title = $objTitle->plaintext;
|
||||||
|
}
|
||||||
|
if (empty($date)) {
|
||||||
|
$objDate = $page->find('.cameraDetails .date', 0);
|
||||||
|
if ($objDate)
|
||||||
|
$date = strtotime($objDate->parent()->plaintext);
|
||||||
|
}
|
||||||
|
if (empty($author)) {
|
||||||
|
$objAuthor = $page->find('.p-publisher a', 0);
|
||||||
|
if ($objAuthor)
|
||||||
|
$author = $objAuthor->plaintext;
|
||||||
|
}
|
||||||
|
|
||||||
|
$objFullImage = $page->find('img#gofullscreen', 0);
|
||||||
|
if ($objFullImage)
|
||||||
|
$objImage = $objFullImage;
|
||||||
|
|
||||||
|
$objText = $page->find('.p-desc', 0);
|
||||||
|
if ($objText)
|
||||||
|
$text = $objText->plaintext;
|
||||||
|
}
|
||||||
|
|
||||||
|
$item['title'] = $title;
|
||||||
|
$item['timestamp'] = $date;
|
||||||
|
$item['author'] = $author;
|
||||||
|
|
||||||
|
if ($objImage)
|
||||||
|
$item['content'] = '<img src="' . $objImage->src . '"/>';
|
||||||
|
if ($objLink) {
|
||||||
|
$item['uri'] = $objLink->href;
|
||||||
|
if (!empty($item['content']))
|
||||||
|
$item['content'] = '<a href="' . $objLink->href . '" target="_blank">' . $item['content'] . '</a>';
|
||||||
|
} else {
|
||||||
|
$item['uri'] = 'urn:sha1:' . hash('sha1', $item['content']);
|
||||||
|
}
|
||||||
|
if (!empty($text))
|
||||||
|
$item['content'] = '<p>' . $text . '</p>' . $item['content'];
|
||||||
|
|
||||||
|
$this->items[] = $item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName(){
|
||||||
|
if(!empty($this->feedName)) {
|
||||||
|
return $this->feedName . ' - ' . self::NAME;
|
||||||
|
}
|
||||||
|
return parent::getName();
|
||||||
|
}
|
||||||
|
}
|
28
bridges/MozillaSecurityBridge.php
Normal file
28
bridges/MozillaSecurityBridge.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -17,27 +17,25 @@ class MydealsBridge extends PepperBridgeAbstract {
|
|||||||
'hide_expired' => array(
|
'hide_expired' => array(
|
||||||
'name' => 'Abgelaufenes ausblenden',
|
'name' => 'Abgelaufenes ausblenden',
|
||||||
'type' => 'checkbox',
|
'type' => 'checkbox',
|
||||||
'required' => 'true'
|
'required' => true
|
||||||
),
|
),
|
||||||
'hide_local' => array(
|
'hide_local' => array(
|
||||||
'name' => 'Lokales ausblenden',
|
'name' => 'Lokales ausblenden',
|
||||||
'type' => 'checkbox',
|
'type' => 'checkbox',
|
||||||
'title' => 'Deals im physischen Geschäft ausblenden',
|
'title' => 'Deals im physischen Geschäft ausblenden',
|
||||||
'required' => 'true'
|
'required' => true
|
||||||
),
|
),
|
||||||
'priceFrom' => array(
|
'priceFrom' => array(
|
||||||
'name' => 'Minimaler Preis',
|
'name' => 'Minimaler Preis',
|
||||||
'type' => 'text',
|
'type' => 'text',
|
||||||
'title' => 'Minmaler Preis in Euros',
|
'title' => 'Minmaler Preis in Euros',
|
||||||
'required' => 'false',
|
'required' => false
|
||||||
'defaultValue' => ''
|
|
||||||
),
|
),
|
||||||
'priceTo' => array(
|
'priceTo' => array(
|
||||||
'name' => 'Maximaler Preis',
|
'name' => 'Maximaler Preis',
|
||||||
'type' => 'text',
|
'type' => 'text',
|
||||||
'title' => 'maximaler Preis in Euro',
|
'title' => 'maximaler Preis in Euro',
|
||||||
'required' => 'false',
|
'required' => false
|
||||||
'defaultValue' => ''
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
@@ -45,7 +43,7 @@ class MydealsBridge extends PepperBridgeAbstract {
|
|||||||
'group' => array(
|
'group' => array(
|
||||||
'name' => 'Gruppen',
|
'name' => 'Gruppen',
|
||||||
'type' => 'list',
|
'type' => 'list',
|
||||||
'required' => 'true',
|
'required' => true,
|
||||||
'title' => 'Gruppe, deren Deals angezeigt werden müssen',
|
'title' => 'Gruppe, deren Deals angezeigt werden müssen',
|
||||||
'values' => array(
|
'values' => array(
|
||||||
'Elektronik' => 'elektronik',
|
'Elektronik' => 'elektronik',
|
||||||
@@ -68,7 +66,7 @@ class MydealsBridge extends PepperBridgeAbstract {
|
|||||||
'order' => array(
|
'order' => array(
|
||||||
'name' => 'sortieren nach',
|
'name' => 'sortieren nach',
|
||||||
'type' => 'list',
|
'type' => 'list',
|
||||||
'required' => 'true',
|
'required' => true,
|
||||||
'title' => 'Sortierung der deals',
|
'title' => 'Sortierung der deals',
|
||||||
'values' => array(
|
'values' => array(
|
||||||
'Vom heißesten zum kältesten Deal' => '',
|
'Vom heißesten zum kältesten Deal' => '',
|
||||||
|
37
bridges/N26Bridge.php
Normal file
37
bridges/N26Bridge.php
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class N26Bridge extends BridgeAbstract
|
||||||
|
{
|
||||||
|
const MAINTAINER = 'quentinus95';
|
||||||
|
const NAME = 'N26 Blog';
|
||||||
|
const URI = 'https://n26.com';
|
||||||
|
const CACHE_TIMEOUT = 1800;
|
||||||
|
const DESCRIPTION = 'Returns recent blog posts from N26.';
|
||||||
|
|
||||||
|
public function getIcon()
|
||||||
|
{
|
||||||
|
return 'https://n26.com/favicon.ico';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function collectData()
|
||||||
|
{
|
||||||
|
$html = getSimpleHTMLDOM(self::URI . '/en-fr/blog-archive')
|
||||||
|
or returnServerError('Error while downloading the website content');
|
||||||
|
|
||||||
|
foreach($html->find('div.ga') as $article) {
|
||||||
|
$item = [];
|
||||||
|
|
||||||
|
$item['uri'] = self::URI . $article->find('h2 a', 0)->href;
|
||||||
|
$item['title'] = $article->find('h2 a', 0)->plaintext;
|
||||||
|
|
||||||
|
$fullArticle = getSimpleHTMLDOM($item['uri'])
|
||||||
|
or returnServerError('Error while downloading the full article');
|
||||||
|
|
||||||
|
$dateElement = $fullArticle->find('span[class="fk fl de ch fm by"]', 0);
|
||||||
|
$item['timestamp'] = strtotime($dateElement->plaintext);
|
||||||
|
$item['content'] = $fullArticle->find('main article', 0)->innertext;
|
||||||
|
|
||||||
|
$this->items[] = $item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
131
bridges/OnVaSortirBridge.php
Normal file
131
bridges/OnVaSortirBridge.php
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
<?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',
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
}
|
954
bridges/OneFortuneADayBridge.php
Normal file
954
bridges/OneFortuneADayBridge.php
Normal file
@@ -0,0 +1,954 @@
|
|||||||
|
<?php
|
||||||
|
class OneFortuneADayBridge extends BridgeAbstract {
|
||||||
|
const NAME = 'One Fortune a Day';
|
||||||
|
const URI = 'https://github.com/fulmeek';
|
||||||
|
const DESCRIPTION = 'Get a fortune quote every single day.';
|
||||||
|
const MAINTAINER = 'fulmeek';
|
||||||
|
const PARAMETERS = array(array(
|
||||||
|
'time' => array(
|
||||||
|
'name' => 'Time in UTC',
|
||||||
|
'type' => 'list',
|
||||||
|
'values' => array(
|
||||||
|
'0:00' => 0,
|
||||||
|
'1:00' => 1,
|
||||||
|
'2:00' => 2,
|
||||||
|
'3:00' => 3,
|
||||||
|
'4:00' => 4,
|
||||||
|
'5:00' => 5,
|
||||||
|
'6:00' => 6,
|
||||||
|
'7:00' => 7,
|
||||||
|
'8:00' => 8,
|
||||||
|
'9:00' => 9,
|
||||||
|
'10:00' => 10,
|
||||||
|
'11:00' => 11,
|
||||||
|
'12:00' => 12,
|
||||||
|
'13:00' => 13,
|
||||||
|
'14:00' => 14,
|
||||||
|
'15:00' => 15,
|
||||||
|
'16:00' => 16,
|
||||||
|
'17:00' => 17,
|
||||||
|
'18:00' => 18,
|
||||||
|
'19:00' => 19,
|
||||||
|
'20:00' => 20,
|
||||||
|
'21:00' => 21,
|
||||||
|
'22:00' => 22,
|
||||||
|
'23:00' => 23,
|
||||||
|
),
|
||||||
|
'defaultValue' => 5
|
||||||
|
)
|
||||||
|
));
|
||||||
|
|
||||||
|
const LIMIT_ITEMS = 7;
|
||||||
|
const DAY_SECS = 86400;
|
||||||
|
|
||||||
|
public function collectData() {
|
||||||
|
$time = gmmktime((int)$this->getInput('time'), 0, 0);
|
||||||
|
if ($time > time())
|
||||||
|
$time -= self::DAY_SECS;
|
||||||
|
|
||||||
|
for ($i = self::LIMIT_ITEMS; $i > 0; --$i) {
|
||||||
|
$seed = date('Ymd', $time);
|
||||||
|
$quote = $this->getQuote($seed);
|
||||||
|
|
||||||
|
$item['title'] = strftime('%A, %x', $time);
|
||||||
|
$item['content'] = htmlentities($quote, ENT_QUOTES, 'UTF-8');
|
||||||
|
$item['timestamp'] = $time;
|
||||||
|
$item['uri'] = 'urn:sha1:' . hash('sha1', $seed);
|
||||||
|
|
||||||
|
$this->items[] = $item;
|
||||||
|
|
||||||
|
$time -= self::DAY_SECS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getQuote($seed) {
|
||||||
|
$quotes = explode('//', <<<QUOTES
|
||||||
|
People are naturally attracted to you.
|
||||||
|
//You learn from your mistakes... You will learn a lot today.
|
||||||
|
//If you have something good in your life, don't let it go!
|
||||||
|
//What ever you're goal is in life, embrace it visualize it, and for it will be
|
||||||
|
yours.
|
||||||
|
//Your shoes will make you happy today.
|
||||||
|
//You cannot love life until you live the life you love.
|
||||||
|
//Be on the lookout for coming events; They cast their shadows beforehand.
|
||||||
|
//Land is always on the mind of a flying bird.
|
||||||
|
//The man or woman you desire feels the same about you.
|
||||||
|
//Meeting adversity well is the source of your strength.
|
||||||
|
//A dream you have will come true.
|
||||||
|
//Our deeds determine us, as much as we determine our deeds.
|
||||||
|
//Never give up. You're not a failure if you don't give up.
|
||||||
|
//You will become great if you believe in yourself.
|
||||||
|
//There is no greater pleasure than seeing your loved ones prosper.
|
||||||
|
//You will marry your lover.
|
||||||
|
//A very attractive person has a message for you.
|
||||||
|
//You already know the answer to the questions lingering inside your head.
|
||||||
|
//It is now, and in this world, that we must live.
|
||||||
|
//You must try, or hate yourself for not trying.
|
||||||
|
//You can make your own happiness.
|
||||||
|
//The greatest risk is not taking one.
|
||||||
|
//The love of your life is stepping into your planet this summer.
|
||||||
|
//Love can last a lifetime, if you want it to.
|
||||||
|
//Adversity is the parent of virtue.
|
||||||
|
//Serious trouble will bypass you.
|
||||||
|
//A short stranger will soon enter your life with blessings to share.
|
||||||
|
//Now is the time to try something new.
|
||||||
|
//Wealth awaits you very soon.
|
||||||
|
//If you feel you are right, stand firmly by your convictions.
|
||||||
|
//If winter comes, can spring be far behind?
|
||||||
|
//Keep your eye out for someone special.
|
||||||
|
//You are very talented in many ways.
|
||||||
|
//A stranger, is a friend you have not spoken to yet.
|
||||||
|
//A new voyage will fill your life with untold memories.
|
||||||
|
//You will travel to many exotic places in your lifetime.
|
||||||
|
//Your ability for accomplishment will follow with success.
|
||||||
|
//Nothing astonishes men so much as common sense and plain dealing.
|
||||||
|
//Its amazing how much good you can do if you dont care who gets the credit.
|
||||||
|
//Everyone agrees. You are the best.
|
||||||
|
//LIFE CONSIST NOT IN HOLDING GOOD CARDS, BUT IN PLAYING THOSE YOU HOLD WELL.
|
||||||
|
//Jealousy doesn't open doors, it closes them!
|
||||||
|
//It's better to be alone sometimes.
|
||||||
|
//When fear hurts you, conquer it and defeat it!
|
||||||
|
//Let the deeds speak.
|
||||||
|
//You will be called in to fulfill a position of high honor and responsibility.
|
||||||
|
//The man on the top of the mountain did not fall there.
|
||||||
|
//You will conquer obstacles to achieve success.
|
||||||
|
//Joys are often the shadows, cast by sorrows.
|
||||||
|
//Fortune favors the brave.
|
||||||
|
//An upward movement initiated in time can counteract fate.
|
||||||
|
//A journey of a thousand miles begins with a single step.
|
||||||
|
//Sometimes you just need to lay on the floor.
|
||||||
|
//Never give up. Always find a reason to keep trying.
|
||||||
|
//If you have something worth fighting for, then fight for it.
|
||||||
|
//Stop wishing. Start doing.
|
||||||
|
//Accept your past without regrets. Handle your present with confidence. Face
|
||||||
|
your future without fear.
|
||||||
|
//Stay true to those who would do the same for you.
|
||||||
|
//Ask yourself if what you are doing today is getting you closer to where you
|
||||||
|
want to be tomorrow.
|
||||||
|
//Happiness is an activity.
|
||||||
|
//Help is always needed but not always appreciated. Stay true to your heart and
|
||||||
|
help those in need weather they appreciate it or not.
|
||||||
|
//Hone your competitive instincts.
|
||||||
|
//Finish your work on hand don't be greedy.
|
||||||
|
//For success today, look first to yourself.
|
||||||
|
//Your fortune is as sweet as a cookie.
|
||||||
|
//Integrity is the essence of everything successful.
|
||||||
|
//If you're happy, you're successful.
|
||||||
|
//You will always be surrounded by true friends
|
||||||
|
//Believing that you are beautiful will make you appear beautiful to others
|
||||||
|
around you.
|
||||||
|
//Happinees comes from a good life.
|
||||||
|
//Before trying to please others think of what makes you happy.
|
||||||
|
//When hungry, order more Chinese food.
|
||||||
|
//Your golden opportunity is coming shortly.
|
||||||
|
//For hate is never conquered by hate. Hate is conquered by love .
|
||||||
|
//You will make many changes before settling down happily.
|
||||||
|
//A man is born to live and not prepare to live.
|
||||||
|
//You cannot become rich except by enriching others.
|
||||||
|
//Don't pursue happiness - create it.
|
||||||
|
//You will be successful in love.
|
||||||
|
//All your fingers can't be of the same length.
|
||||||
|
//Wise sayings often fall on barren ground, but a kind word is never thrown away.
|
||||||
|
//A lifetime of happiness is in store for you.
|
||||||
|
//It is very possible that you will achieve greatness in your lifetime.
|
||||||
|
//Be tactful; overlook your own opportunity.
|
||||||
|
//You are the controller of your destiny.
|
||||||
|
//Everything happens for a reson.
|
||||||
|
//How can you have a beutiful ending without making beautiful mistakes.
|
||||||
|
//You can open doors with your charm and patience.
|
||||||
|
//Welcome the change coming into your life.
|
||||||
|
//There will be a happy romance for you shortly.
|
||||||
|
//Your fondest dream will come true within this year.
|
||||||
|
//You have a deep interest in all that is artistic.
|
||||||
|
//Your emotional nature is strong and sensitive.
|
||||||
|
//A letter of great importance may reach you any day now.
|
||||||
|
//Good health will be yours for a long time.
|
||||||
|
//You will become better acquainted with a coworker.
|
||||||
|
//To be old and wise, you must first be young and stupid.
|
||||||
|
//Failure is only the opportunity to begin again more intelligently.
|
||||||
|
//Integrity is doing the right thing, even if nobody is watching.
|
||||||
|
//Conquer your fears or they will conquer you.
|
||||||
|
//You are a lover of words; One day you will write a book.
|
||||||
|
//In this life it is not what we take up, but what we give up, that makes us
|
||||||
|
rich.
|
||||||
|
//Fear can keep us up all night long, but faith makes one fine pillow.
|
||||||
|
//Seek out the significance of your problem at this time. Try to understand.
|
||||||
|
//Never upset the driver of the car you're in; they're the master of your
|
||||||
|
destiny until you get home.
|
||||||
|
//He who slithers among the ground is not always a foe.
|
||||||
|
//You learn from your mistakes, you will learn a lot today.
|
||||||
|
//You only need look to your own reflection for inspiration. Because you are
|
||||||
|
Beautiful!
|
||||||
|
//You are not judged by your efforts you put in; you are judged on your
|
||||||
|
performance.
|
||||||
|
//Rivers need springs.
|
||||||
|
//Good news from afar may bring you a welcome visitor.
|
||||||
|
//When all else seems to fail, smile for today and just love someone.
|
||||||
|
//Patience is a virtue, unless its against a brick wall.
|
||||||
|
//When you look down, all you see is dirt, so keep looking up.
|
||||||
|
//If you are afraid to shake the dice, you will never throw a six.
|
||||||
|
//Even if the person who appears most wrong, is also quite often right.
|
||||||
|
//A single conversation with a wise man is better than ten years of study.
|
||||||
|
//Happiness is often a rebound from hard work.
|
||||||
|
//The world may be your oyster, but that doesn't mean you'll get it's pearl.
|
||||||
|
//Your life will be filled with magical moments.
|
||||||
|
//You're true love will show himself to you under the moonlight.
|
||||||
|
//Do not follow where the path may lead. Go where there is no path...and leave a
|
||||||
|
trail
|
||||||
|
//Do not fear what you don't know
|
||||||
|
//The object of your desire comes closer.
|
||||||
|
//You have a flair for adding a fanciful dimension to any story.
|
||||||
|
//If you wish to know the mind of a man, listen to his words
|
||||||
|
//The most useless energy is trying to change what and who God so carefully
|
||||||
|
created.
|
||||||
|
//Do not be covered in sadness or be fooled in happiness they both must exist
|
||||||
|
//You will have unexpected great good luck.
|
||||||
|
//You will have a pleasant surprise
|
||||||
|
//All progress occurs because people dare to be different.
|
||||||
|
//Your ability for accomplishment will be followed by success.
|
||||||
|
//The world is always ready to receive talent with open arms.
|
||||||
|
//Things may come to those who wait, but only the things left by those who
|
||||||
|
hustle.
|
||||||
|
//We can't help everyone. But everyone can help someone.
|
||||||
|
//Every day is a new day. But tomorrow is never promised.
|
||||||
|
//Express yourself: Don't hold back!
|
||||||
|
//It is not necessary to show others you have change; the change will be obvious.
|
||||||
|
//You have a deep appreciation of the arts and music.
|
||||||
|
//If your desires are not extravagant, they will be rewarded.
|
||||||
|
//You try hard, never to fail. You don't, never to win.
|
||||||
|
//Never give up on someone that you don't go a day without thinking about.
|
||||||
|
//It never pays to kick a skunk.
|
||||||
|
//In case of fire, keep calm, pay bill and run.
|
||||||
|
//Next full moon brings an enchanting evening.
|
||||||
|
//Not all closed eye is sleeping nor open eye is seeing.
|
||||||
|
//Impossible is a word only to be found in the dictionary of fools.
|
||||||
|
//You will soon witness a miracle.
|
||||||
|
//The time is alway right to do what is right.
|
||||||
|
//Love is as necessary to human beings as food and shelter.
|
||||||
|
//You will make heads turn.
|
||||||
|
//You are extremely loved. Don't worry :)
|
||||||
|
//If you are never patient, you will never get anything done. If you believe you
|
||||||
|
can do it, you will be rewarded with success.
|
||||||
|
//You will soon embark on a business venture.
|
||||||
|
//You believe in the goodness of man kind.
|
||||||
|
//You will have a long and wealthy life.
|
||||||
|
//You will take a pleasant journey to a place far away.
|
||||||
|
//You are a person of culture.
|
||||||
|
//Keep it simple. The more you say, the less people remember.
|
||||||
|
//Life is like a dogsled team. If you ain't the lead dog, the scenery never
|
||||||
|
changes.
|
||||||
|
//Prosperity makes friends and adversity tries them.
|
||||||
|
//Nothing seems impossible to you.
|
||||||
|
//Patience is bitter, but its fruit is sweet.
|
||||||
|
//The only certainty is that nothing is certain.
|
||||||
|
//Success is the sum of my unique visions realized by the sweat of perseverance.
|
||||||
|
//When you expect your opponent to yield, you also should avoid hurting him.
|
||||||
|
//Human evolution: “wider freeway but narrower viewpoints.
|
||||||
|
//Intelligence is the door to freedom and alert attention is the mother of
|
||||||
|
intelligence.
|
||||||
|
//Back away from individuals who are impulsive.
|
||||||
|
//Enjoyed the meal? Buy one to go too.
|
||||||
|
//You believe in the goodness of mankind.
|
||||||
|
//A big fortune will descend upon you this year.
|
||||||
|
//Now these three remain, faith, hope, and love. The greatest of these is love.
|
||||||
|
//For success today look first to yourself.
|
||||||
|
//Determination is the wake-up call to the human will.
|
||||||
|
//There are no limitations to the mind except those we aknowledge.
|
||||||
|
//A merry heart does good like a medicine.
|
||||||
|
//Whenever possible, keep it simple.
|
||||||
|
//Your dearest wish will come true.
|
||||||
|
//Poverty is no disgrace.
|
||||||
|
//If you don’t do it excellently, don’t do it at all.
|
||||||
|
//You have an unusual equipment for success, use it properly.
|
||||||
|
//Emotion is energy in motion.
|
||||||
|
//You will soon be honored by someone you respect.
|
||||||
|
//Punctuality is the politeness of kings and the duty of gentle people
|
||||||
|
everywhere.
|
||||||
|
//Your happiness is intertwined with your outlook on life.
|
||||||
|
//Elegant surroundings will soon be yours.
|
||||||
|
//If you feel you are right, stand firmly by your convictions.
|
||||||
|
//Your smile brings happiness to everyone you meet.
|
||||||
|
//Instead of worrying and agonizing, move ahead constructively.
|
||||||
|
//Do you believe? Endurance and persistence will be rewarded.
|
||||||
|
//A new business venture is on the horizon.
|
||||||
|
//Never underestimate the power of the human touch.
|
||||||
|
//Hold on to the past but eventually, let the times go and keep the memories
|
||||||
|
into the present.
|
||||||
|
//Truth is an unpopular subject. Because it is unquestionably correct.
|
||||||
|
//The most important thing in communication is to hear what isn’t being said.
|
||||||
|
//You are broad minded and socially active.
|
||||||
|
//Your dearest dream is coming true. God looks after you especially.
|
||||||
|
//You will recieve some high prize or award.
|
||||||
|
//Your present question marks are going to succeed.
|
||||||
|
//You have a fine capacity for the enjoyment of life.
|
||||||
|
//You will live long and enjoy life.
|
||||||
|
//An admirer is concealing his/her affection for you.
|
||||||
|
//A wish is what makes life happen when you dream of rose petals.
|
||||||
|
//Love can turn cottage into a golden palace.
|
||||||
|
//Lend your money and lose your freind.
|
||||||
|
//You will kiss your crush ohhh lalahh
|
||||||
|
//You will be rewarded for being a good listener in the next week.
|
||||||
|
//If you never give up on love, It will never give up on you.
|
||||||
|
//Unleash your life force.
|
||||||
|
//Your wish will come true.
|
||||||
|
//There is a prospect of a thrilling time ahead for you.
|
||||||
|
//No distance is too far, if two hearts are tied together.
|
||||||
|
//Land is always in the mind of the flying birds.
|
||||||
|
//Try? No! Do or do not, there is no try.
|
||||||
|
//Do not worry, you will have great peace.
|
||||||
|
//It's about time you asked that special someone on a date.
|
||||||
|
//You create your own stage ... the audience is waiting.
|
||||||
|
//It is never too late. Just as it is never too early.
|
||||||
|
//Discover the power within yourself.
|
||||||
|
//Good things take time.
|
||||||
|
//Stop thinking about the road not taken and pave over the one you did.
|
||||||
|
//Put your unhappiness aside. Life is beautiful, be happy.
|
||||||
|
//You can still love what you can not have in life.
|
||||||
|
//Make a wise choice everyday.
|
||||||
|
//Circumstance does not make the man; it reveals him to himself.
|
||||||
|
//The man who waits till tomorrow, misses the opportunities of today.
|
||||||
|
//Life does not get better by chance. It gets better by change.
|
||||||
|
//If you never expect anything you can never be disappointed.
|
||||||
|
//People in your surroundings will be more cooperative than usual.
|
||||||
|
//True wisdom is found in happiness.
|
||||||
|
//Ones always regrets what could have done. Remember for next time.
|
||||||
|
//Follow your bliss and the Universe will open doors where there were once only
|
||||||
|
walls.
|
||||||
|
//Find a peaceful place where you can make plans for the future.
|
||||||
|
//All the water in the world can't sink a ship unless it gets inside.
|
||||||
|
//The earth is a school learn in it.
|
||||||
|
//In music, one must think with his heart and feel with his brain.
|
||||||
|
//If you speak honestly, everyone will listen.
|
||||||
|
//Ganerosity will repay itself sooner than you imagine.
|
||||||
|
//good things take time
|
||||||
|
//Do what is right, not what you should.
|
||||||
|
//To effect the quality of the day is no small achievement.
|
||||||
|
//Simplicity and clearity should be the theme in your dress.
|
||||||
|
//Virtuous find joy while Wrongdoers find grieve in their actions.
|
||||||
|
//Not all closed eye is sleeping, nor open eye is seeing.
|
||||||
|
//Bread today is better than cake tomorrow.
|
||||||
|
//In evrything there is a piece of truth.But a piece.
|
||||||
|
//A feeling is an idea with roots.
|
||||||
|
//Man is born to live and not prepare to live
|
||||||
|
//It's all right to have butterflies in your stomach. Just get them to fly in
|
||||||
|
formation.
|
||||||
|
//If you don t give something, you will not get anything
|
||||||
|
//The harder you try to not be like your parents, the more likely you will
|
||||||
|
become them
|
||||||
|
//Someday everything will all make perfect sense
|
||||||
|
//you will think for yourself when you stop letting others think for you
|
||||||
|
//Everything will be ok. Don't obsess. Time will prove you right, you must stay
|
||||||
|
where you are.
|
||||||
|
//Let's finish this up now, someone is waiting for you on that
|
||||||
|
//The finest men like the finest steels have been tempered in the hottest
|
||||||
|
furnace.
|
||||||
|
//A dream you have will come true
|
||||||
|
//The worst of friends may become the best of enemies, but you will always find
|
||||||
|
yourself hanging on.
|
||||||
|
//I think, you ate your fortune while you were eating your cookie
|
||||||
|
//If u love someone keep fighting for them
|
||||||
|
//Do what you want, when you want, and you will be rewarded
|
||||||
|
//Let your fantasies unwind...
|
||||||
|
//The cooler you think you are the dumber you look
|
||||||
|
//Expect great things and great things will come
|
||||||
|
//The Wheel of Good Fortune is finally turning in your direction!
|
||||||
|
//Don't lead if you won't lead.
|
||||||
|
//You will always be successful in your professional career
|
||||||
|
//Share your hapiness with others today.
|
||||||
|
//It's up to you to clearify.
|
||||||
|
//Your future will be happy and productive.
|
||||||
|
//Seize every second of your life and savor it.
|
||||||
|
//Those who walk in other's tracks leave no footprints.
|
||||||
|
//Failure is the mother of all success.
|
||||||
|
//Difficulty at the beginning useually means ease at the end.
|
||||||
|
//Do not seek so much to find the answer as much as to understand the question
|
||||||
|
better.
|
||||||
|
//Your way of doing what other people do their way is what makes you special.
|
||||||
|
//A beautiful, smart, and loving person will be coming into your life.
|
||||||
|
//Friendship is an ocean that you cannot see bottom.
|
||||||
|
//Your life does not get better by chance, it gets better by change.
|
||||||
|
//Our duty,as men and women,is to proceed as if limits to our ability did not
|
||||||
|
exist.
|
||||||
|
//A pleasant expeience is ahead:don't pass it by.
|
||||||
|
//Our perception and attitude toward any situation will determine the outcome
|
||||||
|
//They say you are stubborn; you call it persistence.
|
||||||
|
//Two small jumps are sometimes better than one big leap.
|
||||||
|
//A new wardrobe brings great joy and change to your life.
|
||||||
|
//The cure for grief is motion.
|
||||||
|
//It's a good thing that life is not as serious as it seems to the waiter
|
||||||
|
//I hear and I forget. I see and I remember. I do and I understand.
|
||||||
|
//I have a dream....Time to go to bed.
|
||||||
|
//Ideas you believe are absurd ultimately lead to success!
|
||||||
|
//A human being is a deciding being.
|
||||||
|
//Today is an ideal time to water your parsonal garden.
|
||||||
|
//Some men dream of fortunes, others dream of cookies.
|
||||||
|
//Things are never quite the way they seem.
|
||||||
|
//the project on your mind will soon gain momentum
|
||||||
|
//YOUR FAILURES WILL LEAD YOU TO YOUR SUCCESS.
|
||||||
|
//IN ORDER TO GET THE RAINBOW, YOU MUST ENDURE THE RAIN.
|
||||||
|
//Beauty is simply beauty. originality is magical.
|
||||||
|
//Your dream will come true when you least expect it.
|
||||||
|
//Let not your hand be stretched out to receive and shut when you should repay.
|
||||||
|
//Don't worry, half the people you know are below average.
|
||||||
|
//Vision is the art of seeing what is invisible to others.
|
||||||
|
//You don't need talent to gain experience.
|
||||||
|
//A focused mind is one of the most powerful forces in the universe.
|
||||||
|
//Today you shed your last tear. Tomorrow fortune knocks at your door.
|
||||||
|
//Be patient! The Great Wall didn't got build in one day.
|
||||||
|
//Think you can. Think you can't. Either way, you'll be right.
|
||||||
|
//Wisdom is on her way to you.
|
||||||
|
//Digital circuits are made from analog parts.
|
||||||
|
//If you eat a box of fortune cookies, anything is possible.
|
||||||
|
//The best is yet to come.
|
||||||
|
//I'm with you.
|
||||||
|
//Be direct,usually one can accomplish more that way.
|
||||||
|
//A single kind work will keep one warm for years.
|
||||||
|
//Ask a friend to join you on your next voyage.
|
||||||
|
//In God we trust.
|
||||||
|
//Love is free. Lust will cost you everything you have.
|
||||||
|
//Stop searching forever, happiness is just next to you.
|
||||||
|
//You don't need the answers to all of life's questions. Just ask your father
|
||||||
|
what to do.
|
||||||
|
//Jealousy is a useless emotion.
|
||||||
|
//You are not a ghost.
|
||||||
|
//There is someone rather annoying in your life that you need to listen to.
|
||||||
|
//You will plant the smallest seed and it will become the greatest and most
|
||||||
|
mighty tree in the world.
|
||||||
|
//The dream you've been dreaming all your life isn't worth it. Find a new dream,
|
||||||
|
and once you're sure you've found it, fight for it.
|
||||||
|
//See if you can learn anything from the children.
|
||||||
|
//It's Never Too Late For Good Things To Happen!
|
||||||
|
//A clear conscience is usually the sign of a bad memory.
|
||||||
|
//Aim high, time flies.
|
||||||
|
//One is not sleeping, does not mean they are awake.
|
||||||
|
//A great pleasure in life is doing what others say you can't.
|
||||||
|
//Isn't there something else you should be working on right now?
|
||||||
|
//Your father still loves and is in always with you. Remember that.
|
||||||
|
//Before you can be reborn you must die.
|
||||||
|
//It better to be the hammer than the nail.
|
||||||
|
//You are admired by everyone for your talent and ability.
|
||||||
|
//Save the whales. Collect the whole set.
|
||||||
|
//You will soon discover a major truth about the one you love most.
|
||||||
|
//Your life will prosper only if you acknowledge your faults and work to reduce
|
||||||
|
them.
|
||||||
|
//Pray to God, but row towards shore.
|
||||||
|
//You will soon witness a miracle.
|
||||||
|
//The early bird gets the worm, but the second mouse gets the cheese
|
||||||
|
//Help, I'm being held prisoner in a Chinese cookie factory.
|
||||||
|
//Alas! The onion you are eating is someone else’s water lily.
|
||||||
|
//You are a persoon with a good sense of justice, now it's time to act like it.
|
||||||
|
//You create enthusiasm around you.
|
||||||
|
//There are big changes ahead for you. They will be good ones!
|
||||||
|
//You will have many happy days soon.
|
||||||
|
//Out of confusion comes new patterns.
|
||||||
|
//If you love someone enough and they break your heart, you can't stop yourself
|
||||||
|
from still loving them again even after all that pain.
|
||||||
|
//Look right...Now look left...Now look forward (do this really fast) do you
|
||||||
|
feel any different? good you should feel dizzy.
|
||||||
|
//Live like you are on the bottom, even if you are on the top.
|
||||||
|
//You will soon emerge victorious from the maze you've been traveling in.
|
||||||
|
//Do not judge a book by it's color.
|
||||||
|
//Everything will come your way.
|
||||||
|
//There is a time to be practical now.
|
||||||
|
//Bend the rod while it is still hot.
|
||||||
|
//Darkness is only succesful when there is no light. Don't forget about light!
|
||||||
|
//Acting is not lying. It is findind someone hiding inside you and letting that
|
||||||
|
person run free.
|
||||||
|
//You will be forced to face fear, but if you do not run, fear will be afraid of
|
||||||
|
you.
|
||||||
|
//You are thinking about doing something. Don't do it, it won't help anything.
|
||||||
|
//Your worst enemy has a crush on you!
|
||||||
|
//Love Conquers all.
|
||||||
|
//The phrase is follow your dreams. Not dream period.
|
||||||
|
//stop nagging to your partner and take it day by day.
|
||||||
|
//Do not think that me or my brothers have supreme control over what will happen
|
||||||
|
to you.
|
||||||
|
//Bad luck and misfortune will follow you all your days.
|
||||||
|
//Remember the fate of the early Worm.
|
||||||
|
//Begin your life anew with strength, grace and wonder.
|
||||||
|
//Be a good friend and a fair enemy.
|
||||||
|
//What goes around comes around.
|
||||||
|
//Bad luck and misfortune will infest your pathetic soul for all eternity.
|
||||||
|
//The best prophet of the future is the past
|
||||||
|
//Movies have pause buttons, friends do not
|
||||||
|
//Use the force.
|
||||||
|
//Trust your intuition.
|
||||||
|
//Encourage your peers.
|
||||||
|
//Let your imagination wander.
|
||||||
|
//Your pain is the breaking of the shell that encloses your understanding.
|
||||||
|
//Patience is key, a wait short or long will have its reward.
|
||||||
|
//Tell them before it's too late...
|
||||||
|
//A bird in the hand is worth three in the bush!!
|
||||||
|
//Be assertive when decisive action is needed.
|
||||||
|
//To determine whether someone is beautiful is not by looking at his/her
|
||||||
|
appearance, but his/her heart.
|
||||||
|
//Hope brings about a better future
|
||||||
|
//While you have this day, fill it with life. While you're in this moment, give
|
||||||
|
it your own special meaning and purpose and joy.
|
||||||
|
//Even though it will often be difficult and complicated, you know you have what
|
||||||
|
it takes to get it done.
|
||||||
|
//You can choose, right now and in every moment, to put your powerful and
|
||||||
|
effective abilities to purposeful use. There is always something you can do, no
|
||||||
|
matter what the situation may be, that will move your life forward.
|
||||||
|
//IT IS NOT GOOD TO BE A USER BLESSINGS COME FROM BEING A GIVER NOT A TAKER.
|
||||||
|
//Cookie says, You crack me up
|
||||||
|
//You will prosper in the field of wacky inventions.
|
||||||
|
//Your tongue is your ambassador.
|
||||||
|
//The cure for grief is movement.
|
||||||
|
//Love Is At Your Hands Be Glad And Hold On To It.
|
||||||
|
//You are often asked if it is in yet.
|
||||||
|
//Life to you is a bold and dashing responsibility.
|
||||||
|
//Patience is a key to joy.
|
||||||
|
//A bargain is something you don't need at a price you can't resist.
|
||||||
|
//Today is going to be a disasterous day, be prepared!
|
||||||
|
//Stay to your inner-self, you will benefit in many ways.
|
||||||
|
//Rarely do great beauty and great virtue dwell together as they do in you.
|
||||||
|
//You are talented in many ways.
|
||||||
|
//You are the master of every situation.
|
||||||
|
//Your problem just got bigger. Think, what have you done.
|
||||||
|
//If your cookie still in one piece, buy lotto.
|
||||||
|
//Go with the flow will make your transition ever so much easier.
|
||||||
|
//Tomorrow Morning,Take a Left Turn As Soon As You Leave Home
|
||||||
|
//A metaphor could save your life.
|
||||||
|
//Don't wait for your ship to come in, swim out to it
|
||||||
|
//There are lessons to be learned by listening to others.
|
||||||
|
//If you want the rainbow, you have to tolerate the rain.
|
||||||
|
//Volition, Strength, Languages, Freedom and Power rests in you.
|
||||||
|
//TOO MANY PEOPLE VOLUNTEER TO CARRY THE STOOL WHEN ITS TIME TO MOVE THE PIANO
|
||||||
|
//It takes more than a good memory to have good memories.
|
||||||
|
//You are what you are; understand yourself before you react
|
||||||
|
//Word to the wise: Don't play leapfrog with a unicorn.........
|
||||||
|
//Forgive your enemies, but never forget them.
|
||||||
|
//Everything will now come your way
|
||||||
|
//Don't worry about the stock market. Invest in family.
|
||||||
|
//Your fortune is as sweet as a cookie.
|
||||||
|
//It is much easier to look for the bad, than it is to find the good
|
||||||
|
//If a person who has caused you pain and suffering has brought you, reconsider
|
||||||
|
that person's value in your life
|
||||||
|
//You are worth loving, you are also worth the effort it takes to love you
|
||||||
|
//Never trouble trouble till trouble troubles you.
|
||||||
|
//Get off to a new start - come out of your shell.
|
||||||
|
//Life is a dancefloor,you are the DJ!
|
||||||
|
//Cooperate with those who have both know how and integrith.
|
||||||
|
//Minor aches today are likely to pay off handsomely tomorrow.
|
||||||
|
//You are about to become $8.95 poorer. ($6.95 if you had the buffet)
|
||||||
|
//Your mouth may be moving, but nobody is listening.
|
||||||
|
//Focus in on the color yellow tomorrow for good luck!
|
||||||
|
//The problem with resisting temptation is that it may never come again.
|
||||||
|
//All your sorrows will vanish.
|
||||||
|
//About time I got out of that cookie.
|
||||||
|
//Love will lead the way.
|
||||||
|
//The ads revenge is massive success
|
||||||
|
//It is best to act with confidence, no matter how little right you have to it.
|
||||||
|
//Soon, a visitor shall delight you.
|
||||||
|
//What breaks in a moment may take years to mend.
|
||||||
|
//Someone stole your fortune and replaced it with this one. Your luck sucks.
|
||||||
|
Have a good day!
|
||||||
|
//Take control of your life rather than letting things happen just like that!
|
||||||
|
//You will be rewarded for your patience and understanding.
|
||||||
|
//You will achieve all your desires and pleasures.
|
||||||
|
//Never miss a chance to keep your mouth shut.
|
||||||
|
//Nothing Shows A Man's Character More Than What He Laughs At.
|
||||||
|
//Never regret anything that made you smile.
|
||||||
|
//Love Takes Pratice.
|
||||||
|
//Don't take yourself so seriously, no one else does.
|
||||||
|
//You've got what it takes, but it will take everything you've got!
|
||||||
|
//At this very moment you can change the rest of your life.
|
||||||
|
//Become who you are.
|
||||||
|
//All comes at the proper time to him who knows how to wait.
|
||||||
|
//The energy is within you. Money is Coming!
|
||||||
|
//The quotes that you do not understand, are not meant for you.
|
||||||
|
//You have an important new business development shaping up.
|
||||||
|
//if love someone a lot tell it before it's too late
|
||||||
|
//Birds are entangled by their feet and men by their tongues.
|
||||||
|
//Benefit by doing things that others give up on.
|
||||||
|
//Rest has a peaceful effect on your physical and emotional health.
|
||||||
|
//One of the best ways to persuade others is with your ears--by listening to
|
||||||
|
them.
|
||||||
|
//Plan your work and work your plan.
|
||||||
|
//Over self-confidence is equal to being blind.
|
||||||
|
//Those who bring sunshine to the lives of others cannot keep it from themselves.
|
||||||
|
//Love or money, or neither?
|
||||||
|
//Before the beginning of great brilliance, there must be chaos.
|
||||||
|
//Old friends make best friends.
|
||||||
|
//Stop searching forever. Happiness is just next to you.
|
||||||
|
//Accept something that you cannot change, and you will feel better.
|
||||||
|
//Kiss is not a kiss without the heart.
|
||||||
|
//Enhance your karma by engaging in various charitable activities.
|
||||||
|
//You will have good luck and overcome many hardships.
|
||||||
|
//You never hesitate to tackle the most difficult problems.
|
||||||
|
//Hope is like food. You will starve without it.
|
||||||
|
//WHEN FIRE AND WATER GO TO WAR WATER ALWAYS WINS.
|
||||||
|
//An angry man opens his mouth and shuts up his eyes.
|
||||||
|
//Make the system work for you, not the other way around.
|
||||||
|
//You will be hungry soon, order takeout now.
|
||||||
|
//Be prepared for extra energy.
|
||||||
|
//An unexpected relationship will become permanent.
|
||||||
|
//The love of your life is sitting across from you.
|
||||||
|
//Better be the head of a chicken than the tail of an ox.
|
||||||
|
//To forgive others one more time is to create one more blessing for yourself.
|
||||||
|
//Enjoy yourself while you can.
|
||||||
|
//The ultimate test of a relationship is to disagree but to hold hands.
|
||||||
|
//Excellence is the difference between what I do and what I am capable of.
|
||||||
|
//Do not let what you do not have prevent you from using what you do have.
|
||||||
|
//What ends on hope does not end at all.
|
||||||
|
//People enjoy having you around. Appreciate this.
|
||||||
|
//You are admired for your adventuous ways.
|
||||||
|
//It's never crowded along the extra mile
|
||||||
|
//You are blessed, today is the day to bless others.
|
||||||
|
//The Greatest War Sometimes Isn't On The Battlefield But Against Oneself.
|
||||||
|
//People in your background will be more co-operative than usual.
|
||||||
|
//A good way to stay healthy is to eat more Chinese food.
|
||||||
|
//Anyone who dares to be, can never be weak.
|
||||||
|
//Affirm it, visualize it, believe it, and it`will actualize itself.
|
||||||
|
//The measure of time to your next goal is the measure of your discipline.
|
||||||
|
//Help, I'm prisoner in a Chinese bakery!!!
|
||||||
|
//Take a minute and let it ride, then take a minute to let it breeze.
|
||||||
|
//We are here to love each other, serve each other and uplift each other.
|
||||||
|
//If everybody is a worm you should be a glow worm
|
||||||
|
//To affirm is to make firm.
|
||||||
|
//Remember this: duct tape can fix anything, so don't worry about messing things
|
||||||
|
up.
|
||||||
|
//You broke my cookie!
|
||||||
|
//Failure is not defeat until you stop trying.
|
||||||
|
//The days that make us happy make us wise.
|
||||||
|
//Men do not fail... they give up trying.
|
||||||
|
//Time may fly by. But Memories don't.
|
||||||
|
//You will win success in whatever you adopt.
|
||||||
|
//You will outdistance all your competitors.
|
||||||
|
//You have a great capability to break cookies - use it wisely!
|
||||||
|
//AT TIMES IT IS BETTER TO KNOW WHEN EXIT THAN ENTER
|
||||||
|
//Money will come to you when you are doing the right thing.
|
||||||
|
//When you get something for nothing, you just haven't been billed for it yet.
|
||||||
|
//You will discover your hidden talents.
|
||||||
|
//You'll advance for with your abilities.
|
||||||
|
//When you can't naturally feel upbeat it can sometimes help you to act as if
|
||||||
|
you did.
|
||||||
|
//You will overcome difficult times.
|
||||||
|
//Your problem just became your stepping stone. Catch the moment.
|
||||||
|
//I am a fortune. You just broke my little house. Where will i live now?
|
||||||
|
//The majority of the word can't is can.
|
||||||
|
//The secret of getting ahead is getting started.
|
||||||
|
//Be most affectionate today.
|
||||||
|
//Change your thoughts and you change the world.
|
||||||
|
//Sing and rejoice, fortune is smiling on you.
|
||||||
|
//All the preparation you've done will finally be paying off!
|
||||||
|
//A truly great person never puts away the simplicity of a child.
|
||||||
|
//Customer service is like taking a bath you have to keep doing it.
|
||||||
|
//The expanse of your intelligence is a void no universe could ever fill.
|
||||||
|
//Those grapes you cannot taste are always sour.
|
||||||
|
//An unexpected aquaintance will resurface.
|
||||||
|
//If you want the rainbow, then you have to tolerate the rain.
|
||||||
|
//You don't get harmony when everyone sings the same note.
|
||||||
|
//The race is not always to the swift, but to those who keep on running.
|
||||||
|
//The early bird gets the worm, but the second mouse gets the cheese.
|
||||||
|
//The best things in life aren't things.
|
||||||
|
//Don't bother looking for fault. The reward for finding it is low.
|
||||||
|
//Everything has beauty but not everyone sees it.
|
||||||
|
//Nothing is as good or bad as it appears.
|
||||||
|
//Never cut what you can untie.
|
||||||
|
//Meet your opponent half way. You need the exercise.
|
||||||
|
//Laughter is the shortest distance between two people.
|
||||||
|
//We cannot change the direction of the wind, but we can adjust our sails.
|
||||||
|
//We could learn a lot from crayons: Some of are sharp, some are pretty, some
|
||||||
|
have weird names, and all are different colors. But they all have to learn to
|
||||||
|
live in the same box.
|
||||||
|
//Use your instincts now.
|
||||||
|
//If you take a single step to your journey, you'll succeed; it's not best to
|
||||||
|
fail.
|
||||||
|
//In the eyes of lovers, everything is beautiful.
|
||||||
|
//Warning, do not eat your fortune.
|
||||||
|
//Demonstrate refinement in everything you do.
|
||||||
|
//Impossible standards just make life difficult.
|
||||||
|
//A different world cannot be build by indifferent people.
|
||||||
|
//Q. What is H2O? A. Caring, 2 parts Hug and 1 part Open-mind.
|
||||||
|
//All troubles you have can pass away very quickly.
|
||||||
|
//Integrity is the essense of everything successful.
|
||||||
|
//For true love? Send real roses preserved in 24kt gold!
|
||||||
|
//Sometimes the object of the journey is not the end, but the journey itself.
|
||||||
|
//Fear is just excitement in need of an attitude adjustment.
|
||||||
|
//The food here taste so good, even a cave man likes it.
|
||||||
|
//Perhaps you've been focusing too much on spending.
|
||||||
|
//Happiness isn't something you remember, it's something you experience.
|
||||||
|
//Oops... Wrong cookie.
|
||||||
|
//The dream is within you.
|
||||||
|
//Love is on its way.
|
||||||
|
//Be direct, usually one can accomplish more that way.
|
||||||
|
//Use your talents. That's what they are intended for.
|
||||||
|
//The troubles you have now will pass away quickly.
|
||||||
|
//See the light at the end of the tunnel.
|
||||||
|
//Your dream will come true when you least expect it.
|
||||||
|
//Don't 'face' reality, let it be the place from which you leap.
|
||||||
|
//Fortune smiles upon you today.
|
||||||
|
//Believing is doing.
|
||||||
|
//Your dynamic eyes have attracted a secret admirer.
|
||||||
|
//You know where you are going and how to get there.
|
||||||
|
//Go confidently in the direction of your dreams.
|
||||||
|
//Your ability to pick a winner will bring you success.
|
||||||
|
//Humor usually works at the moment of awkwardness.
|
||||||
|
//A good time to finish up old tasks.
|
||||||
|
//Stop procrastinating - starting tomorrow
|
||||||
|
//Enthusiastic leadership gets you a promotion when you least expect it.
|
||||||
|
//You love Chinese food.
|
||||||
|
//You are far more influential than you think.
|
||||||
|
//Adjust finances, make budgets, to improve your standing.
|
||||||
|
//Happiness is not the absence of conflict, but the ability to cope with it.
|
||||||
|
//An understanding heart warms all that are graced with it's presense.
|
||||||
|
//Your co-workers take pleasure in your great sense of creativity.
|
||||||
|
//You are one of the people who goes places in life.
|
||||||
|
//Others enjoy your company.
|
||||||
|
//When in doubt, let your instincts guide you.
|
||||||
|
//A cheerful message is on its way to you.
|
||||||
|
//A pleasant surprise is in store for you tonight.
|
||||||
|
//you cant go down the right path with out first discovering the path to go down
|
||||||
|
//To courageously shoulder the responsibility of one's mistake is character.
|
||||||
|
//The joyful energy of the day will have a positive affect on you.
|
||||||
|
//You have a strong desire for a home and your family interests come first.
|
||||||
|
//Dogs have owners, cats have staff.
|
||||||
|
//Be patient: in time, even an egg will walk.
|
||||||
|
//You are not a person who can be ignored.
|
||||||
|
//You always know the right times to be assertive or to simply wait.
|
||||||
|
//Reading to the mind is what exercise is to the body.
|
||||||
|
//Eat something you never tried before.
|
||||||
|
//Your life becomes more and more of an adventure!
|
||||||
|
//You need to live authentically, and you can't ignore that.
|
||||||
|
//Make all you can, save all you can, give all you can.
|
||||||
|
//A well-aimed spear is worth three.
|
||||||
|
//To build a better world, start in your community.
|
||||||
|
//When you can't naturally feel upbeat, it can sometimes help to act a if you
|
||||||
|
did.
|
||||||
|
//May you have great luck.
|
||||||
|
//A kind word will keep someone warm for years.
|
||||||
|
//Nothing in the world is accomplished without passion.
|
||||||
|
//Human invented language to satisfy the need to complain.
|
||||||
|
//Accept what comes to you each day.
|
||||||
|
//A small lucky package is on its way to you soon.
|
||||||
|
//In human endeavor, chance favors the prepared mind.
|
||||||
|
//Do not upset the penguin today.
|
||||||
|
//Don't cry.
|
||||||
|
//The best way to give credit is to give it away.
|
||||||
|
//Anything you do, do it well. The last thing you want is to be sorry for what
|
||||||
|
you didn't do.
|
||||||
|
//It takes more then good memory to have good memories.
|
||||||
|
//Grant yourself a wish this year only you can do it.
|
||||||
|
//love thy neighbour, just don't get caught
|
||||||
|
//You will be selected for a promotion because of your accomplishments.
|
||||||
|
//There are many new opportunities that are being presented to you.
|
||||||
|
//You will inherit a large sum of money.
|
||||||
|
//You will recieve a gift from someone that cares about you.
|
||||||
|
//You are not illiterate.
|
||||||
|
//Love because it is the only true adventure.
|
||||||
|
//You are contemplating some action which will bring credit upon you
|
||||||
|
//Keep true to the dreams of your youth.
|
||||||
|
//Treasure what you have.
|
||||||
|
//The greatest precept is continual awareness.
|
||||||
|
//A new friend helps you break out of an old routine.
|
||||||
|
//I have a dream.... Time to go to bed.
|
||||||
|
//Your skill will accomplish what the force of many cannot.
|
||||||
|
//You will soon be surrounded by good friends and laughter.
|
||||||
|
//The best is yet to come.
|
||||||
|
//It is better to be the hammer then the anvil.
|
||||||
|
//He who climbs a ladder must begin at the first step.
|
||||||
|
//Action speaks nothing, without the Motive.
|
||||||
|
//Give yourself some peace and quiet for at least a few hours.
|
||||||
|
//Live each day well and wisely
|
||||||
|
//Old dreams never die they just get filed away.
|
||||||
|
//You can fix it with a little extra energy and a positive attitude.
|
||||||
|
//Life is a verb
|
||||||
|
//A man without aim is like a clock without hands, as useless if it turns as if
|
||||||
|
it stands.
|
||||||
|
//Many folks are about as happy as they make up their minds to be.
|
||||||
|
//It's kind of fun to do the impossible
|
||||||
|
//Wow! A secret message from you teeth!
|
||||||
|
//You should be able to make money and hold on to it.
|
||||||
|
//The human spirit is stronger than anything that can happen to it.
|
||||||
|
//Your succeess will astonish everyone.
|
||||||
|
//It is better to have a hen tomorrow than an egg today.
|
||||||
|
//Judge each day not by the harvest you reap but by the seeds you plant.
|
||||||
|
//You like Chinese food.
|
||||||
|
//Your hard work will get payoff today.
|
||||||
|
//Today is the tomorrow we worried about yesterday
|
||||||
|
//There are no shortcuts to any place worth going
|
||||||
|
//No matter what your past has been, you have a spotless future.
|
||||||
|
//Your secret desire to completely change your life will manifest.
|
||||||
|
//Soon you will be sitting on top of the world.
|
||||||
|
//You are never selfish with your advice or your help.
|
||||||
|
//A thrilling time is in store for you.
|
||||||
|
//It's tough to be fascinating.
|
||||||
|
//Soon life will become more interesting
|
||||||
|
//Luck sometimes visits a fool, but it never sits down with him.
|
||||||
|
//Keep your plans secret for now.
|
||||||
|
//Aren't you glad you just had a great meal?
|
||||||
|
//Traveling this year will bring your life into greater perspective.
|
||||||
|
//Only talent people get help from others.
|
||||||
|
//Constant grinding can turn an iron nod into a needle.
|
||||||
|
//You will be successful in your work
|
||||||
|
//you will spend old age in confort and material wealth
|
||||||
|
//When you're about to turn your heart into a stone remember: you do not walk
|
||||||
|
alone.
|
||||||
|
//I am a bad luck person since I was born
|
||||||
|
//You are vigorous in words and action.
|
||||||
|
//The one who snores will always fall asleep first.
|
||||||
|
//An alien of some sort will be appearing to you shortly!
|
||||||
|
//Rest is a good thing, but boredom is its brother.
|
||||||
|
//Do not be overly judgemental of your loved one's intentions or actions.
|
||||||
|
//Think of how you can assist on a problem, not who to blame.
|
||||||
|
//The life of every woman or man - the heart of it - is pure and holy joy.
|
||||||
|
//Take it easy
|
||||||
|
//Trust your intuition. The universe is guiding your life.
|
||||||
|
//Use your head, but live in your heart.
|
||||||
|
//Don't find fault, find a remedy
|
||||||
|
//It may be those who do most, dream most
|
||||||
|
//Your passions sweep you away.
|
||||||
|
//Listen to yourself more often
|
||||||
|
//Think of mother's exhortations more.
|
||||||
|
//The gambler is like the fisherman both have beginners luck.
|
||||||
|
//You are given the chance to take part in an exciting adventure.
|
||||||
|
//The simplest answer is to act.
|
||||||
|
//You will always be surrounded by true friends.
|
||||||
|
//Keep your feet on the ground even though friends flatter you.
|
||||||
|
//You are the man of righteousness and integrity.
|
||||||
|
//He who seeks will find.
|
||||||
|
//The smart thing to do is to begin trusting your intuitions.
|
||||||
|
//Your many hidden talents will become obvious to those around you.
|
||||||
|
//Pick a path with heart.
|
||||||
|
//The human spirit is stronger then anything that can happen to it.
|
||||||
|
//It takes more than good memory to have good memories.
|
||||||
|
//Face facts with dignity.
|
||||||
|
//Be calm when confronting an emergency crisis.
|
||||||
|
//Do you believe? Endurance and persistence will be rewarded.
|
||||||
|
//A new wardrobe brings great joy and change in your life.
|
||||||
|
//Everyone agrees you are the best.
|
||||||
|
//A new outlook brightens your image and brings new friends.
|
||||||
|
//Everything will now come your way.
|
||||||
|
//You will be called to fill a position of high honor and responsibility.
|
||||||
|
//The eyes believe themselves; the ears believe other people.
|
||||||
|
//Good beginning is half done.
|
||||||
|
//Some pursue happiness; you create it.
|
||||||
|
//It's the worst of times, you need to summon your optimism.
|
||||||
|
//You are cautious in showing your true self to others.
|
||||||
|
//Your ability to accomplish tasks will follow with success.
|
||||||
|
//We all have extraordinary coded within us, waiting to be released.
|
||||||
|
//You will have a bright future.
|
||||||
|
//Compassion is a way of being.
|
||||||
|
//You will always have good luck in your personal affairs.
|
||||||
|
//The pleasure of what we enjoy is lost by wanting more
|
||||||
|
//Did you remember to order your take out also?
|
||||||
|
//Perhaps you've been focusing too much on that one thing..
|
||||||
|
//Right now there's an energy pushing you in a new direction.
|
||||||
|
//Everybody feels lucky for having you as a friend.
|
||||||
|
//When the moment comes, take the top one.
|
||||||
|
//Sometimes travel to new places leads to great transformation.
|
||||||
|
//There is always a way - if you are committed.
|
||||||
|
//Life is too short to waste time hating anyone.
|
||||||
|
//All the world may not love a lover but they will be watching him.
|
||||||
|
//Don't just spend time, invest it.
|
||||||
|
//Life always gets harder near the summit.
|
||||||
|
//Take the chance while you still have the choice.
|
||||||
|
//It is much easier to be cirtical than to be correct.
|
||||||
|
//Enjoy life! It is better to be happy than wise.
|
||||||
|
//To make the cart go, you must grease the wheels.
|
||||||
|
//You are contemplating some action which will bring credit upon you.
|
||||||
|
//Before you wonder Am I doing things right, ask Am I doing the right things?
|
||||||
|
//You may be disappointed if you fail, but you are doomed if you don't try.
|
||||||
|
//You will always get what you want through your charm and personality.
|
||||||
|
//The big issues are work, career, or status right now.
|
||||||
|
//Your emotional currents are flowing powerfully now.
|
||||||
|
//Any decision you have to make tomorrow is a good decsion.
|
||||||
|
//Consume less. Share more. Enjoy life.
|
||||||
|
//The secret of staying young is good health and lying about your age.
|
||||||
|
//Spring has sprung. Life is blooming.
|
||||||
|
//Go ask your mom.
|
||||||
|
//The possibility of a career change is near.
|
||||||
|
//The important thing is to never stop questioning.
|
||||||
|
//Compassion will cure more then condemnation.
|
||||||
|
//Excuses are easy to manufacture, and hard to sell.
|
||||||
|
//Put your mind into planning today. Look into the future.
|
||||||
|
//Listen to life, and you will hear the voice of life crying, Be!
|
||||||
|
//Broke is only temporaryl poor is a state of mind.
|
||||||
|
//Here we go. Moo Shu Cereal for breakfast with duck sauce.
|
||||||
|
//Teamwork: the fuel that allows common people attain uncommon results.
|
||||||
|
//Hard words break no bones, fine words butter no parsnips.
|
||||||
|
//We cannot direct the wind but we can adjust the sails.
|
||||||
|
//You are offered the dream of a lifetime. Say yes!
|
||||||
|
//Working out the kinks today will make for a better tomorrow.
|
||||||
|
//You have a curious smile and a mysterious nature.
|
||||||
|
//Questions provide the key to unlocking our unlimited potential.
|
||||||
|
//You will enjoy razon-sharp spiritual vision today.
|
||||||
|
//The wise are aware of their treasure, while fools follow their vanity
|
||||||
|
//Well-arranged time is the surest sign of a well-arranged mind.
|
||||||
|
//Never bring unhappy feelings into your home.
|
||||||
|
//This is really a lovely day. Congratulations!
|
||||||
|
//Bad luck and ill misfortune will infest your pathetic soul for all eternity.
|
||||||
|
//A golden egg of opportunity falls into your lap this month.
|
||||||
|
//You are very grateful for the small pleasures of life.
|
||||||
|
//today you should be a passenger. Stay close to a driver for a day.
|
||||||
|
//For hate is never conquered by hate. Hate is conquered by love.
|
||||||
|
//Service is the rent we pay for the privilege of living on this planet.
|
||||||
|
//Good clothes open many doors. Go shopping.
|
||||||
|
//The leader seeks to communicate his vision to his followers.
|
||||||
|
//Great works are performed not by strength, but by perseverance.
|
||||||
|
//People who are late are often happier than those who have to wait for them
|
||||||
|
//Present your best ideas today to an eager and welcoming audience.
|
||||||
|
//Friends long absent are coming back to you.
|
||||||
|
//The time is right to make new friends.
|
||||||
|
//Life to you is a dashing and bold adventure
|
||||||
|
//You may be hungry soon: order a takeout now.
|
||||||
|
//Do not hesitate to look for help, an extra hand should always be welcomed.
|
||||||
|
//How can you have a beautiful ending without making beautiful mistakes?
|
||||||
|
//Humor is an affirmation of dignity
|
||||||
|
//He who climbs a ladder must begin at the first step
|
||||||
|
//What's vice today may be virtue tomorow.
|
||||||
|
//You have an unusually magnetic personality.
|
||||||
|
//You will travel to many places.
|
||||||
|
//Accept yourself
|
||||||
|
//Be a generous friend and a fair enemy
|
||||||
|
//Never quit!
|
||||||
|
//Old friends, old wines and old gold are best
|
||||||
|
//If your desires are not extravagant, they will be granted
|
||||||
|
//Every Friend Joys in your Success
|
||||||
|
//You should be able to undertake and complete anything
|
||||||
|
//You will enjoy good health, you will be surrounded by luxury
|
||||||
|
//You are a person of strong sense of duty
|
||||||
|
//Dream lofty dreams, and as you dream, so shall you become.
|
||||||
|
//You have a quiet and unobtrusive nature.
|
||||||
|
//Great thoughts come from the heart.
|
||||||
|
//You love peace
|
||||||
|
//Judge not according to the appearance.
|
||||||
|
//One who admires you greatly is hidden before your eyes.
|
||||||
|
//Traveling more often is important for your health and happiness.
|
||||||
|
//You will be sharing great news with all people you love
|
||||||
|
//You have a reputation for being straightforward and honest.
|
||||||
|
//You are always welcome in any gathering.
|
||||||
|
//You will be traveling and coming into a fortune.
|
||||||
|
//Open up your heart - it can always be closed again.
|
||||||
|
//Being happy is not always being perfect.
|
||||||
|
//Next time you have the opportunity, go on a rollercoaster.
|
||||||
|
//Try everything once, even the things you don't think you will like.
|
||||||
|
//Life is too short to hold grudges.
|
||||||
|
//Dream your dream and your dream will dream of you.
|
||||||
|
//Being alone and being lonely are two different things.
|
||||||
|
//Don't worry about things in the past, there is nothing you can do about them
|
||||||
|
now. Don't worry about things that are happening now, make the best of a bad
|
||||||
|
situation. Don't worry about things in the future, they may never happen.
|
||||||
|
//Tomorrow, take a moment to do something just for yourself.
|
||||||
|
//Someone close to you is waiting for you to call.
|
||||||
|
//A virtual fortune cookie will not satisfy your hunger like that of a home made
|
||||||
|
one.
|
||||||
|
//Smile. Tomorrow is another day.
|
||||||
|
//You can never been certain of success, but you can be certain of failure if
|
||||||
|
you never try.
|
||||||
|
//It takes ten times as many muscles to frown as it does to smile.
|
||||||
|
//Shoot for the moon! If you miss you will still be amongst the stars.
|
||||||
|
//Keep your eyes open. You never know what you might see.
|
||||||
|
//Tell them what you really think. Otherwise, nothing will change.
|
||||||
|
//Let your heart make your decisions - it does not get as confused as your head.
|
||||||
|
//Working hard will make you live a happy life.
|
||||||
|
//A pleasant surprise is waiting for you.
|
||||||
|
QUOTES
|
||||||
|
);
|
||||||
|
|
||||||
|
$i = round(fmod(hexdec(hash('crc32', $seed)), count($quotes)), 0);
|
||||||
|
return trim(str_replace(array("\r\n", "\n", "\r"), ' ', $quotes[$i]));
|
||||||
|
}
|
||||||
|
}
|
64
bridges/OsmAndBlogBridge.php
Normal file
64
bridges/OsmAndBlogBridge.php
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
class OsmAndBlogBridge extends BridgeAbstract {
|
||||||
|
const NAME = 'OsmAnd Blog';
|
||||||
|
const URI = 'https://osmand.net/';
|
||||||
|
const DESCRIPTION = 'Get the latest news from OsmAnd.net';
|
||||||
|
const MAINTAINER = 'fulmeek';
|
||||||
|
|
||||||
|
public function collectData() {
|
||||||
|
$html = getSimpleHTMLDOM(self::URI . 'blog')
|
||||||
|
or returnServerError('Could not load content');
|
||||||
|
|
||||||
|
foreach($html->find('div.article') as $element) {
|
||||||
|
$item = array();
|
||||||
|
|
||||||
|
$objTitle = $element->find('h1', 0);
|
||||||
|
if (!$objTitle)
|
||||||
|
$objTitle = $element->find('h2', 0);
|
||||||
|
if (!$objTitle)
|
||||||
|
$objTitle = $element->find('h3', 0);
|
||||||
|
if ($objTitle)
|
||||||
|
$item['title'] = $objTitle->plaintext;
|
||||||
|
|
||||||
|
$objDate = $element->find('meta[pubdate]', 0);
|
||||||
|
if ($objDate) {
|
||||||
|
$item['timestamp'] = strtotime($objDate->pubdate);
|
||||||
|
} else {
|
||||||
|
$objDate = $element->find('.date', 0);
|
||||||
|
if ($objDate)
|
||||||
|
$item['timestamp'] = strtotime($objDate->plaintext);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->cleanupContent($element, $objTitle, $objDate, $element->find('.date', 0));
|
||||||
|
$item['content'] = $element->innertext;
|
||||||
|
|
||||||
|
$objLink = $html->find('.articlelinklist a', 0);
|
||||||
|
if ($objLink) {
|
||||||
|
$item['uri'] = $this->filterURL($objLink->href);
|
||||||
|
} else {
|
||||||
|
$item['uri'] = 'urn:sha1:' . hash('sha1', $item['content']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->items[] = $item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function filterURL($url) {
|
||||||
|
if (strpos($url, '://') === false)
|
||||||
|
return self::URI . ltrim($url, '/');
|
||||||
|
return $url;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function cleanupContent($content, ...$removeItems) {
|
||||||
|
foreach ($removeItems as $obj) {
|
||||||
|
if ($obj) $obj->outertext = '';
|
||||||
|
}
|
||||||
|
foreach ($content->find('img') as $obj) {
|
||||||
|
$obj->src = $this->filterURL($obj->src);
|
||||||
|
}
|
||||||
|
foreach ($content->find('a') as $obj) {
|
||||||
|
$obj->href = $this->filterURL($obj->href);
|
||||||
|
$obj->target = '_blank';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -15,7 +15,6 @@ class PixivBridge extends BridgeAbstract {
|
|||||||
),
|
),
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|
||||||
public function collectData(){
|
public function collectData(){
|
||||||
|
|
||||||
$html = getContents(static::URI . 'search.php?word=' . urlencode($this->getInput('tag')))
|
$html = getContents(static::URI . 'search.php?word=' . urlencode($this->getInput('tag')))
|
||||||
@@ -53,7 +52,7 @@ class PixivBridge extends BridgeAbstract {
|
|||||||
|
|
||||||
$url = str_replace('_master1200', '', $url);
|
$url = str_replace('_master1200', '', $url);
|
||||||
$url = str_replace('c/240x240/img-master/', 'img-original/', $url);
|
$url = str_replace('c/240x240/img-master/', 'img-original/', $url);
|
||||||
$path = CACHE_DIR . '/pixiv_img';
|
$path = PATH_CACHE . 'pixiv_img/';
|
||||||
|
|
||||||
if(!is_dir($path))
|
if(!is_dir($path))
|
||||||
mkdir($path, 0755, true);
|
mkdir($path, 0755, true);
|
||||||
@@ -70,5 +69,4 @@ class PixivBridge extends BridgeAbstract {
|
|||||||
return 'cache/pixiv_img/' . $illustId . '.jpeg';
|
return 'cache/pixiv_img/' . $illustId . '.jpeg';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -13,8 +13,8 @@ class RainbowSixSiegeBridge extends BridgeAbstract {
|
|||||||
|
|
||||||
public function collectData(){
|
public function collectData(){
|
||||||
$dlUrl = 'https://prod-tridionservice.ubisoft.com/live/v1/News/Latest?templateId=tcm%3A152-7677';
|
$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%3A152-194572-64';
|
$dlUrl .= '8-32&pageIndex=0&pageSize=10&language=en-US&detailPageId=tcm%3A150-194572-64';
|
||||||
$dlUrl .= '&keywordList=175426&siteId=undefined&useSeoFriendlyUrl=true';
|
$dlUrl .= '&keywordList=233416%2C316144%2C233418%2C233417&siteId=undefined&useSeoFriendlyUrl=true';
|
||||||
$jsonString = getContents($dlUrl) or returnServerError('Error while downloading the website content');
|
$jsonString = getContents($dlUrl) or returnServerError('Error while downloading the website content');
|
||||||
|
|
||||||
$json = json_decode($jsonString, true);
|
$json = json_decode($jsonString, true);
|
||||||
|
@@ -1,25 +1,48 @@
|
|||||||
<?php
|
<?php
|
||||||
class Rue89Bridge extends FeedExpander {
|
class Rue89Bridge extends BridgeAbstract {
|
||||||
|
|
||||||
const MAINTAINER = 'pit-fgfjiudghdf';
|
const MAINTAINER = 'teromene';
|
||||||
const NAME = 'Rue89';
|
const NAME = 'Rue89';
|
||||||
const URI = 'http://rue89.nouvelobs.com/';
|
const URI = 'https://www.nouvelobs.com/rue89/';
|
||||||
const DESCRIPTION = 'Returns the 5 newest posts from Rue89 (full text)';
|
const DESCRIPTION = 'Returns the newest posts from Rue89';
|
||||||
|
|
||||||
protected function parseItem($item){
|
public function collectData() {
|
||||||
$item = parent::parseItem($item);
|
|
||||||
|
|
||||||
$url = 'http://api.rue89.nouvelobs.com/export/mobile2/node/'
|
$jsonArticles = getContents('https://appdata.nouvelobs.com/rue89/feed.json')
|
||||||
. str_replace(' ', '', substr($item['uri'], -8))
|
or die('Unable to query Rue89 !');
|
||||||
. '/full';
|
$articles = json_decode($jsonArticles)->items;
|
||||||
|
foreach($articles as $article) {
|
||||||
|
$this->items[] = $this->getArticle($article);
|
||||||
|
}
|
||||||
|
|
||||||
$datas = json_decode(getContents($url), true);
|
}
|
||||||
$item['content'] = $datas['node']['body'];
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
return $item;
|
return $item;
|
||||||
}
|
|
||||||
|
|
||||||
public function collectData(){
|
|
||||||
$this->collectExpandableDatas('http://api.rue89.nouvelobs.com/feed');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -35,5 +35,4 @@ class Shimmie2Bridge extends DanbooruBridge {
|
|||||||
|
|
||||||
return $item;
|
return $item;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -804,7 +804,6 @@ EOD;
|
|||||||
EOD;
|
EOD;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the reported skimfeed version is compatible
|
* Checks if the reported skimfeed version is compatible
|
||||||
*/
|
*/
|
||||||
@@ -821,5 +820,4 @@ EOD;
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -32,15 +32,16 @@ class SoundCloudBridge extends BridgeAbstract {
|
|||||||
. self::CLIENT_ID
|
. self::CLIENT_ID
|
||||||
)) or returnServerError('No results for this user');
|
)) or returnServerError('No results for this user');
|
||||||
|
|
||||||
for($i = 0; $i < 10; $i++) {
|
$numTracks = min(count($tracks), 10);
|
||||||
|
for($i = 0; $i < $numTracks; $i++) {
|
||||||
$item = array();
|
$item = array();
|
||||||
$item['author'] = $tracks[$i]->user->username . ' - ' . $tracks[$i]->title;
|
$item['author'] = $tracks[$i]->user->username;
|
||||||
$item['title'] = $tracks[$i]->user->username . ' - ' . $tracks[$i]->title;
|
$item['title'] = $tracks[$i]->user->username . ' - ' . $tracks[$i]->title;
|
||||||
$item['content'] = '<audio src="'
|
$item['timestamp'] = strtotime($tracks[$i]->created_at);
|
||||||
. $tracks[$i]->uri
|
$item['content'] = $tracks[$i]->description;
|
||||||
|
$item['enclosures'] = array($tracks[$i]->uri
|
||||||
. '/stream?client_id='
|
. '/stream?client_id='
|
||||||
. self::CLIENT_ID
|
. self::CLIENT_ID);
|
||||||
. '">';
|
|
||||||
|
|
||||||
$item['id'] = self::URI
|
$item['id'] = self::URI
|
||||||
. urlencode($this->getInput('u'))
|
. urlencode($this->getInput('u'))
|
||||||
@@ -54,6 +55,7 @@ class SoundCloudBridge extends BridgeAbstract {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getName(){
|
public function getName(){
|
||||||
if(!is_null($this->getInput('u'))) {
|
if(!is_null($this->getInput('u'))) {
|
||||||
return self::NAME . ' - ' . $this->getInput('u');
|
return self::NAME . ' - ' . $this->getInput('u');
|
||||||
|
@@ -57,5 +57,4 @@ class SupInfoBridge extends BridgeAbstract {
|
|||||||
return $item;
|
return $item;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -16,13 +16,13 @@ class TheHackerNewsBridge extends BridgeAbstract {
|
|||||||
if($limit < 5) {
|
if($limit < 5) {
|
||||||
|
|
||||||
$article_url = $element->find('a.story-link', 0)->href;
|
$article_url = $element->find('a.story-link', 0)->href;
|
||||||
$article_author = trim($element->find('i.fa-user', 0)->parent()->plaintext);
|
$article_author = trim($element->find('i.icon-user', 0)->parent()->plaintext);
|
||||||
$article_title = $element->find('h2.home-title', 0)->plaintext;
|
$article_title = $element->find('h2.home-title', 0)->plaintext;
|
||||||
|
|
||||||
//Date without time
|
//Date without time
|
||||||
$article_timestamp = strtotime(
|
$article_timestamp = strtotime(
|
||||||
extractFromDelimiters(
|
extractFromDelimiters(
|
||||||
$element->find('i.fa-calendar', 0)->parent()->outertext,
|
$element->find('i.icon-calendar', 0)->parent()->outertext,
|
||||||
'</i>',
|
'</i>',
|
||||||
'<span>'
|
'<span>'
|
||||||
)
|
)
|
||||||
|
@@ -3,7 +3,7 @@ class ThePirateBayBridge extends BridgeAbstract {
|
|||||||
|
|
||||||
const MAINTAINER = 'mitsukarenai';
|
const MAINTAINER = 'mitsukarenai';
|
||||||
const NAME = 'The Pirate Bay';
|
const NAME = 'The Pirate Bay';
|
||||||
const URI = 'https://thepiratebay.org/';
|
const URI = 'https://thepiratebay.wf/';
|
||||||
const DESCRIPTION = 'Returns results for the keywords. You can put several
|
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
|
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
|
show"). Category based search needs the category number as input. User based
|
||||||
|
@@ -112,8 +112,7 @@ class ThingiverseBridge extends BridgeAbstract {
|
|||||||
'3D Printer Parts' => '128',
|
'3D Printer Parts' => '128',
|
||||||
'3D Printers' => '126',
|
'3D Printers' => '126',
|
||||||
'3D Printing Tests' => '129',
|
'3D Printing Tests' => '129',
|
||||||
),
|
)
|
||||||
'defaultValue' => ''
|
|
||||||
),
|
),
|
||||||
'showimage' => array(
|
'showimage' => array(
|
||||||
'name' => 'Show image in content',
|
'name' => 'Show image in content',
|
||||||
@@ -163,5 +162,4 @@ class ThingiverseBridge extends BridgeAbstract {
|
|||||||
|
|
||||||
return parent::getURI();
|
return parent::getURI();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
687
bridges/TrelloBridge.php
Normal file
687
bridges/TrelloBridge.php
Normal file
@@ -0,0 +1,687 @@
|
|||||||
|
<?php
|
||||||
|
class TrelloBridge extends BridgeAbstract {
|
||||||
|
const NAME = 'Trello Bridge';
|
||||||
|
const URI = 'https://trello.com/';
|
||||||
|
const CACHE_TIMEOUT = 300; // 5min
|
||||||
|
const DESCRIPTION = 'Returns activity on Trello boards or cards';
|
||||||
|
const MAINTAINER = 'Roliga';
|
||||||
|
const PARAMETERS = array(
|
||||||
|
'Board' => array(
|
||||||
|
'b' => array(
|
||||||
|
'name' => 'Board ID',
|
||||||
|
'required' => true,
|
||||||
|
'exampleValue' => 'g9mdhdzg',
|
||||||
|
'title' => 'Taken from Trello URL, e.g. trello.com/b/[Board ID]'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
'Card' => array(
|
||||||
|
'c' => array(
|
||||||
|
'name' => 'Card ID',
|
||||||
|
'required' => true,
|
||||||
|
'exampleValue' => '8vddc9pE',
|
||||||
|
'title' => 'Taken from Trello URL, e.g. trello.com/c/[Card ID]'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This was extracted from webpack on a Trello page, e.g. trello.com/b/g9mdhdzg
|
||||||
|
* In the browser's inspector/debugger go to the Debugger (Firefox) or
|
||||||
|
* Sources (Chromium) tab, these values can be found at:
|
||||||
|
* webpack:///resources/strings/actions/en.json
|
||||||
|
*/
|
||||||
|
const ACTION_TEXTS = array(
|
||||||
|
'action_accept_enterprise_join_request'
|
||||||
|
=> '{memberCreator} added team {organization} to the enterprise {enterprise}',
|
||||||
|
'action_add_attachment_to_card'
|
||||||
|
=> '{memberCreator} attached {attachment} to {card} {attachmentPreview}',
|
||||||
|
'action_add_attachment_to_card@card'
|
||||||
|
=> '{memberCreator} attached {attachment} to this card {attachmentPreview}',
|
||||||
|
'action_add_checklist_to_card'
|
||||||
|
=> '{memberCreator} added {checklist} to {card}',
|
||||||
|
'action_add_checklist_to_card@card'
|
||||||
|
=> '{memberCreator} added {checklist} to this card',
|
||||||
|
'action_add_label_to_card'
|
||||||
|
=> '{memberCreator} added the {label} label to {card}',
|
||||||
|
'action_add_label_to_card@card'
|
||||||
|
=> '{memberCreator} added the {label} label to this card',
|
||||||
|
'action_add_organization_to_enterprise'
|
||||||
|
=> '{memberCreator} added team {organization} to the enterprise {enterprise}',
|
||||||
|
'action_add_to_organization_board'
|
||||||
|
=> '{memberCreator} added {board} to {organization}',
|
||||||
|
'action_add_to_organization_board@board'
|
||||||
|
=> '{memberCreator} added this board to {organization}',
|
||||||
|
'action_added_a_due_date'
|
||||||
|
=> '{memberCreator} set {card} to be due {date}',
|
||||||
|
'action_added_a_due_date@card'
|
||||||
|
=> '{memberCreator} set this card to be due {date}',
|
||||||
|
'action_added_list_to_board'
|
||||||
|
=> '{memberCreator} added list {list} to {board}',
|
||||||
|
'action_added_list_to_board@board'
|
||||||
|
=> '{memberCreator} added {list} to this board',
|
||||||
|
'action_added_member_to_board'
|
||||||
|
=> '{memberCreator} added {member} to {board}',
|
||||||
|
'action_added_member_to_board@board'
|
||||||
|
=> '{memberCreator} added {member} to this board',
|
||||||
|
'action_added_member_to_board_as_admin'
|
||||||
|
=> '{memberCreator} added {member} to {board} as an admin',
|
||||||
|
'action_added_member_to_board_as_admin@board'
|
||||||
|
=> '{memberCreator} added {member} to this board as an admin',
|
||||||
|
'action_added_member_to_board_as_observer'
|
||||||
|
=> '{memberCreator} added {member} to {board} as an observer',
|
||||||
|
'action_added_member_to_board_as_observer@board'
|
||||||
|
=> '{memberCreator} added {member} to this board as an observer',
|
||||||
|
'action_added_member_to_card'
|
||||||
|
=> '{memberCreator} added {member} to {card}',
|
||||||
|
'action_added_member_to_card@card'
|
||||||
|
=> '{memberCreator} added {member} to this card',
|
||||||
|
'action_added_member_to_organization'
|
||||||
|
=> '{memberCreator} added {member} to {organization}',
|
||||||
|
'action_added_member_to_organization_as_admin'
|
||||||
|
=> '{memberCreator} added {member} to {organization} as an admin',
|
||||||
|
'action_admins_visibility'
|
||||||
|
=> 'its admins',
|
||||||
|
'action_another_board'
|
||||||
|
=> 'another board',
|
||||||
|
'action_archived_card'
|
||||||
|
=> '{memberCreator} archived {card}',
|
||||||
|
'action_archived_card@card'
|
||||||
|
=> '{memberCreator} archived this card',
|
||||||
|
'action_archived_list'
|
||||||
|
=> '{memberCreator} archived list {list}',
|
||||||
|
'action_became_a_normal_user_in_organization'
|
||||||
|
=> '{memberCreator} became a normal user in {organization}',
|
||||||
|
'action_became_a_normal_user_on'
|
||||||
|
=> '{memberCreator} became a normal user on {board}',
|
||||||
|
'action_became_a_normal_user_on@board'
|
||||||
|
=> '{memberCreator} became a normal user on this board',
|
||||||
|
'action_became_an_admin_of_organization'
|
||||||
|
=> '{memberCreator} became an admin of {organization}',
|
||||||
|
'action_board_perm_level'
|
||||||
|
=> '{memberCreator} made {board} visible to {level}',
|
||||||
|
'action_board_perm_level@board'
|
||||||
|
=> '{memberCreator} made this board visible to {level}',
|
||||||
|
'action_calendar'
|
||||||
|
=> 'calendar',
|
||||||
|
'action_cardAging'
|
||||||
|
=> 'card aging',
|
||||||
|
'action_changed_a_due_date'
|
||||||
|
=> '{memberCreator} changed the due date of {card} to {date}',
|
||||||
|
'action_changed_a_due_date@card'
|
||||||
|
=> '{memberCreator} changed the due date of this card to {date}',
|
||||||
|
'action_changed_board_background'
|
||||||
|
=> '{memberCreator} changed the background of {board}',
|
||||||
|
'action_changed_board_background@board'
|
||||||
|
=> '{memberCreator} changed the background of this board',
|
||||||
|
'action_changed_description_of_card'
|
||||||
|
=> '{memberCreator} changed description of {card}',
|
||||||
|
'action_changed_description_of_card@card'
|
||||||
|
=> '{memberCreator} changed description of this card',
|
||||||
|
'action_changed_description_of_organization'
|
||||||
|
=> '{memberCreator} changed description of {organization}',
|
||||||
|
'action_changed_display_name_of_organization'
|
||||||
|
=> '{memberCreator} changed display name of {organization}',
|
||||||
|
'action_changed_name_of_organization'
|
||||||
|
=> '{memberCreator} changed name of {organization}',
|
||||||
|
'action_changed_website_of_organization'
|
||||||
|
=> '{memberCreator} changed website of {organization}',
|
||||||
|
'action_closed_board'
|
||||||
|
=> '{memberCreator} closed {board}',
|
||||||
|
'action_closed_board@board'
|
||||||
|
=> '{memberCreator} closed this board',
|
||||||
|
'action_comment_on_card'
|
||||||
|
=> '{memberCreator} {contextOn} {card} {comment}',
|
||||||
|
'action_comment_on_card@card'
|
||||||
|
=> '{memberCreator} {comment}',
|
||||||
|
'action_completed_checkitem'
|
||||||
|
=> '{memberCreator} completed {checkitem} on {card}',
|
||||||
|
'action_completed_checkitem@card'
|
||||||
|
=> '{memberCreator} completed {checkitem} on this card',
|
||||||
|
'action_convert_to_card_from_checkitem'
|
||||||
|
=> '{memberCreator} converted {card} from a checklist item on {cardSource}',
|
||||||
|
'action_convert_to_card_from_checkitem@card'
|
||||||
|
=> '{memberCreator} converted this card from a checklist item on {cardSource}',
|
||||||
|
'action_convert_to_card_from_checkitem@cardSource'
|
||||||
|
=> '{memberCreator} converted {card} from a checklist item on this card',
|
||||||
|
'action_copy_board'
|
||||||
|
=> '{memberCreator} copied this board from {board}',
|
||||||
|
'action_copy_card'
|
||||||
|
=> '{memberCreator} copied {card} from {cardSource} in list {list}',
|
||||||
|
'action_copy_card@card'
|
||||||
|
=> '{memberCreator} copied this card from {cardSource} in list {list}',
|
||||||
|
'action_copy_comment_from_card'
|
||||||
|
=> '{memberCreator} copied comment by {member} from card {card} {comment}',
|
||||||
|
'action_create_board'
|
||||||
|
=> '{memberCreator} created {board}',
|
||||||
|
'action_create_board@board'
|
||||||
|
=> '{memberCreator} created this board',
|
||||||
|
'action_create_card'
|
||||||
|
=> '{memberCreator} added {card} to {list}',
|
||||||
|
'action_create_card@card'
|
||||||
|
=> '{memberCreator} added this card to {list}',
|
||||||
|
'action_create_custom_field'
|
||||||
|
=> '{memberCreator} created the {customField} custom field on {board}',
|
||||||
|
'action_create_custom_field@board'
|
||||||
|
=> '{memberCreator} created the {customField} custom field on this board',
|
||||||
|
'action_create_enterprise_join_request'
|
||||||
|
=> '{memberCreator} requested to add team {organization} to the enterprise {enterprise}',
|
||||||
|
'action_created_an_invitation_to_board'
|
||||||
|
=> '{memberCreator} created an invitation to {board}',
|
||||||
|
'action_created_an_invitation_to_board@board'
|
||||||
|
=> '{memberCreator} created an invitation to this board',
|
||||||
|
'action_created_an_invitation_to_organization'
|
||||||
|
=> '{memberCreator} created an invitation to {organization}',
|
||||||
|
'action_created_checklist_on_board'
|
||||||
|
=> '{memberCreator} created {checklist} on {board}',
|
||||||
|
'action_created_checklist_on_board@board'
|
||||||
|
=> '{memberCreator} created {checklist} on this board',
|
||||||
|
'action_created_organization'
|
||||||
|
=> '{memberCreator} created {organization}',
|
||||||
|
'action_decline_enterprise_join_request'
|
||||||
|
=> '{memberCreator} declined the request to add team {organization} to the enterprise {enterprise}',
|
||||||
|
'action_delete_attachment_from_card'
|
||||||
|
=> '{memberCreator} deleted the {attachment} attachment from {card}',
|
||||||
|
'action_delete_attachment_from_card@card'
|
||||||
|
=> '{memberCreator} deleted the {attachment} attachment from this card',
|
||||||
|
'action_delete_card'
|
||||||
|
=> '{memberCreator} deleted card #{idCard} from {list}',
|
||||||
|
'action_delete_custom_field'
|
||||||
|
=> '{memberCreator} deleted the {customField} custom field from {board}',
|
||||||
|
'action_delete_custom_field@board'
|
||||||
|
=> '{memberCreator} deleted the {customField} custom field from this board',
|
||||||
|
'action_deleted_account'
|
||||||
|
=> '[deleted account]',
|
||||||
|
'action_deleted_an_invitation_to_board'
|
||||||
|
=> '{memberCreator} deleted an invitation to {board}',
|
||||||
|
'action_deleted_an_invitation_to_board@board'
|
||||||
|
=> '{memberCreator} deleted an invitation to this board',
|
||||||
|
'action_deleted_an_invitation_to_organization'
|
||||||
|
=> '{memberCreator} deleted an invitation to {organization}',
|
||||||
|
'action_deleted_checkitem'
|
||||||
|
=> '{memberCreator} deleted task {checkitem} on {checklist}',
|
||||||
|
'action_disabled_calendar_feed'
|
||||||
|
=> '{memberCreator} disabled the iCalendar feed on {board}',
|
||||||
|
'action_disabled_calendar_feed@board'
|
||||||
|
=> '{memberCreator} disabled the iCalendar feed on this board',
|
||||||
|
'action_disabled_card_covers'
|
||||||
|
=> '{memberCreator} disabled card cover images on {board}',
|
||||||
|
'action_disabled_card_covers@board'
|
||||||
|
=> '{memberCreator} disabled card cover images on this board',
|
||||||
|
'action_disabled_commenting'
|
||||||
|
=> '{memberCreator} disabled commenting on {board}',
|
||||||
|
'action_disabled_commenting@board'
|
||||||
|
=> '{memberCreator} disabled commenting on this board',
|
||||||
|
'action_disabled_inviting'
|
||||||
|
=> '{memberCreator} disabled inviting on {board}',
|
||||||
|
'action_disabled_inviting@board'
|
||||||
|
=> '{memberCreator} disabled inviting on this board',
|
||||||
|
'action_disabled_plugin'
|
||||||
|
=> '{memberCreator} disabled the {plugin} Power-Up',
|
||||||
|
'action_disabled_powerup'
|
||||||
|
=> '{memberCreator} disabled the {powerup} Power-Up',
|
||||||
|
'action_disabled_self_join'
|
||||||
|
=> '{memberCreator} disabled self join on {board}',
|
||||||
|
'action_disabled_self_join@board'
|
||||||
|
=> '{memberCreator} disabled self join on this board',
|
||||||
|
'action_disabled_voting'
|
||||||
|
=> '{memberCreator} disabled voting on {board}',
|
||||||
|
'action_disabled_voting@board'
|
||||||
|
=> '{memberCreator} disabled voting on this board',
|
||||||
|
'action_due_date_change'
|
||||||
|
=> '{memberCreator}',
|
||||||
|
'action_email_card'
|
||||||
|
=> '{memberCreator} emailed {card} to {list}',
|
||||||
|
'action_email_card@card'
|
||||||
|
=> '{memberCreator} emailed this card to {list}',
|
||||||
|
'action_email_card_from'
|
||||||
|
=> '{memberCreator} emailed {card} to {list} from {from}',
|
||||||
|
'action_email_card_from@card'
|
||||||
|
=> '{memberCreator} emailed this card to {list} from {from}',
|
||||||
|
'action_enabled_calendar_feed'
|
||||||
|
=> '{memberCreator} enabled the iCalendar feed on {board}',
|
||||||
|
'action_enabled_calendar_feed@board'
|
||||||
|
=> '{memberCreator} enabled the iCalendar feed on this board',
|
||||||
|
'action_enabled_card_covers'
|
||||||
|
=> '{memberCreator} enabled card cover images on {board}',
|
||||||
|
'action_enabled_card_covers@board'
|
||||||
|
=> '{memberCreator} enabled card cover images on this board',
|
||||||
|
'action_enabled_plugin'
|
||||||
|
=> '{memberCreator} enabled the {plugin} Power-Up',
|
||||||
|
'action_enabled_powerup'
|
||||||
|
=> '{memberCreator} enabled the {powerup} Power-Up',
|
||||||
|
'action_enabled_self_join'
|
||||||
|
=> '{memberCreator} enabled self join on {board}',
|
||||||
|
'action_enabled_self_join@board'
|
||||||
|
=> '{memberCreator} enabled self join on this board',
|
||||||
|
'action_hid_board'
|
||||||
|
=> '{memberCreator} hid {board}',
|
||||||
|
'action_hid_board@board'
|
||||||
|
=> '{memberCreator} hid this board',
|
||||||
|
'action_invited_an_unconfirmed_member_to_board'
|
||||||
|
=> '{memberCreator} invited an unconfirmed member to {board}',
|
||||||
|
'action_invited_an_unconfirmed_member_to_board@board'
|
||||||
|
=> '{memberCreator} invited an unconfirmed member to this board',
|
||||||
|
'action_invited_an_unconfirmed_member_to_organization'
|
||||||
|
=> '{memberCreator} invited an unconfirmed member to {organization}',
|
||||||
|
'action_joined_board'
|
||||||
|
=> '{memberCreator} joined {board}',
|
||||||
|
'action_joined_board@board'
|
||||||
|
=> '{memberCreator} joined this board',
|
||||||
|
'action_joined_board_by_invitation_link'
|
||||||
|
=> '{memberCreator} joined {board} with an invitation link from {memberInviter}',
|
||||||
|
'action_joined_board_by_invitation_link@board'
|
||||||
|
=> '{memberCreator} joined this board with an invitation link from {memberInviter}',
|
||||||
|
'action_joined_organization'
|
||||||
|
=> '{memberCreator} joined {organization}',
|
||||||
|
'action_joined_organization_by_invitation_link'
|
||||||
|
=> '{memberCreator} joined {organization} with an invitation link from {memberInviter}',
|
||||||
|
'action_left_board'
|
||||||
|
=> '{memberCreator} left {board}',
|
||||||
|
'action_left_board@board'
|
||||||
|
=> '{memberCreator} left this board',
|
||||||
|
'action_left_organization'
|
||||||
|
=> '{memberCreator} left {organization}',
|
||||||
|
'action_made_a_normal_user_in_organization'
|
||||||
|
=> '{memberCreator} made {member} a normal user in {organization}',
|
||||||
|
'action_made_a_normal_user_on'
|
||||||
|
=> '{memberCreator} made {member} a normal user on {board}',
|
||||||
|
'action_made_a_normal_user_on@board'
|
||||||
|
=> '{memberCreator} made {member} a normal user on this board',
|
||||||
|
'action_made_admin_of_board'
|
||||||
|
=> '{memberCreator} made {member} an admin of {board}',
|
||||||
|
'action_made_admin_of_board@board'
|
||||||
|
=> '{memberCreator} made {member} an admin of this board',
|
||||||
|
'action_made_an_admin_of_organization'
|
||||||
|
=> '{memberCreator} made {member} an admin of {organization}',
|
||||||
|
'action_made_commenting_on'
|
||||||
|
=> '{memberCreator} made commenting on {board} available to {level}',
|
||||||
|
'action_made_commenting_on@board'
|
||||||
|
=> '{memberCreator} made commenting on this board available to {level}',
|
||||||
|
'action_made_inviting_on'
|
||||||
|
=> '{memberCreator} made inviting on {board} available to {level}',
|
||||||
|
'action_made_inviting_on@board'
|
||||||
|
=> '{memberCreator} made inviting on this board available to {level}',
|
||||||
|
'action_made_observer_of_board'
|
||||||
|
=> '{memberCreator} made {member} an observer of {board}',
|
||||||
|
'action_made_observer_of_board@board'
|
||||||
|
=> '{memberCreator} made {member} an observer of this board',
|
||||||
|
'action_made_self_admin_of_board'
|
||||||
|
=> '{memberCreator} made themselves an admin of {board}',
|
||||||
|
'action_made_self_admin_of_board@board'
|
||||||
|
=> '{memberCreator} made themselves an admin of this board',
|
||||||
|
'action_made_self_observer_of_board'
|
||||||
|
=> '{memberCreator} became an observer of {board}',
|
||||||
|
'action_made_self_observer_of_board@board'
|
||||||
|
=> '{memberCreator} became an observer of this board',
|
||||||
|
'action_made_voting_on'
|
||||||
|
=> '{memberCreator} made voting on {board} available to {level}',
|
||||||
|
'action_made_voting_on@board'
|
||||||
|
=> '{memberCreator} made voting on this board available to {level}',
|
||||||
|
'action_marked_checkitem_incomplete'
|
||||||
|
=> '{memberCreator} marked {checkitem} incomplete on {card}',
|
||||||
|
'action_marked_checkitem_incomplete@card'
|
||||||
|
=> '{memberCreator} marked {checkitem} incomplete on this card',
|
||||||
|
'action_marked_the_due_date_complete'
|
||||||
|
=> '{memberCreator} marked the due date on {card} complete',
|
||||||
|
'action_marked_the_due_date_complete@card'
|
||||||
|
=> '{memberCreator} marked the due date complete',
|
||||||
|
'action_marked_the_due_date_incomplete'
|
||||||
|
=> '{memberCreator} marked the due date on {card} incomplete',
|
||||||
|
'action_marked_the_due_date_incomplete@card'
|
||||||
|
=> '{memberCreator} marked the due date incomplete',
|
||||||
|
'action_member_joined_card'
|
||||||
|
=> '{memberCreator} joined {card}',
|
||||||
|
'action_member_joined_card@card'
|
||||||
|
=> '{memberCreator} joined this card',
|
||||||
|
'action_member_left_card'
|
||||||
|
=> '{memberCreator} left {card}',
|
||||||
|
'action_member_left_card@card'
|
||||||
|
=> '{memberCreator} left this card',
|
||||||
|
'action_members_visibility'
|
||||||
|
=> 'its members',
|
||||||
|
'action_move_card_from_board'
|
||||||
|
=> '{memberCreator} transferred {card} to {board}',
|
||||||
|
'action_move_card_from_board@card'
|
||||||
|
=> '{memberCreator} transferred this card to {board}',
|
||||||
|
'action_move_card_from_list_to_list'
|
||||||
|
=> '{memberCreator} moved {card} from {listBefore} to {listAfter}',
|
||||||
|
'action_move_card_from_list_to_list@card'
|
||||||
|
=> '{memberCreator} moved this card from {listBefore} to {listAfter}',
|
||||||
|
'action_move_card_to_board'
|
||||||
|
=> '{memberCreator} transferred {card} from {board}',
|
||||||
|
'action_move_card_to_board@card'
|
||||||
|
=> '{memberCreator} transferred this card from {board}',
|
||||||
|
'action_move_list_from_board'
|
||||||
|
=> '{memberCreator} transferred {list} to {board}',
|
||||||
|
'action_move_list_to_board'
|
||||||
|
=> '{memberCreator} transferred {list} from {board}',
|
||||||
|
'action_moved_card_higher'
|
||||||
|
=> '{memberCreator} moved {card} higher',
|
||||||
|
'action_moved_card_higher@card'
|
||||||
|
=> '{memberCreator} moved this card higher',
|
||||||
|
'action_moved_card_lower'
|
||||||
|
=> '{memberCreator} moved {card} lower',
|
||||||
|
'action_moved_card_lower@card'
|
||||||
|
=> '{memberCreator} moved this card lower',
|
||||||
|
'action_moved_checkitem_higher'
|
||||||
|
=> '{memberCreator} moved {checkitem} higher in the checklist {checklist}',
|
||||||
|
'action_moved_checkitem_lower'
|
||||||
|
=> '{memberCreator} moved {checkitem} higher in the checklist {checklist}',
|
||||||
|
'action_moved_list_left'
|
||||||
|
=> '{memberCreator} moved list {list} left on {board}',
|
||||||
|
'action_moved_list_left@board'
|
||||||
|
=> '{memberCreator} moved {list} left on this board',
|
||||||
|
'action_moved_list_right'
|
||||||
|
=> '{memberCreator} moved list {list} right on {board}',
|
||||||
|
'action_moved_list_right@board'
|
||||||
|
=> '{memberCreator} moved {list} right on this board',
|
||||||
|
'action_observers_visibility'
|
||||||
|
=> 'members and observers',
|
||||||
|
'action_on'
|
||||||
|
=> 'on',
|
||||||
|
'action_org_visibility'
|
||||||
|
=> 'members of its team',
|
||||||
|
'action_public_visibility'
|
||||||
|
=> 'the public',
|
||||||
|
'action_remove_checklist_from_card'
|
||||||
|
=> '{memberCreator} removed {checklist} from {card}',
|
||||||
|
'action_remove_checklist_from_card@card'
|
||||||
|
=> '{memberCreator} removed {checklist} from this card',
|
||||||
|
'action_remove_from_organization_board'
|
||||||
|
=> '{memberCreator} removed {board} from {organization}',
|
||||||
|
'action_remove_from_organization_board@board'
|
||||||
|
=> '{memberCreator} removed this board from {organization}',
|
||||||
|
'action_remove_label_from_card'
|
||||||
|
=> '{memberCreator} removed the {label} label from {card}',
|
||||||
|
'action_remove_label_from_card@card'
|
||||||
|
=> '{memberCreator} removed the {label} label from this card',
|
||||||
|
'action_remove_organization_from_enterprise'
|
||||||
|
=> '{memberCreator} removed team {organization} from the enterprise {enterprise}',
|
||||||
|
'action_removed_a_due_date'
|
||||||
|
=> '{memberCreator} removed the due date from {card}',
|
||||||
|
'action_removed_a_due_date@card'
|
||||||
|
=> '{memberCreator} removed the due date from this card',
|
||||||
|
'action_removed_from_board'
|
||||||
|
=> '{memberCreator} removed {member} from {board}',
|
||||||
|
'action_removed_from_board@board'
|
||||||
|
=> '{memberCreator} removed {member} from this board',
|
||||||
|
'action_removed_member_from_card'
|
||||||
|
=> '{memberCreator} removed {member} from {card}',
|
||||||
|
'action_removed_member_from_card@card'
|
||||||
|
=> '{memberCreator} removed {member} from this card',
|
||||||
|
'action_removed_member_from_organization'
|
||||||
|
=> '{memberCreator} removed {member} from {organization}',
|
||||||
|
'action_removed_vote_for_card'
|
||||||
|
=> '{memberCreator} removed vote for {card}',
|
||||||
|
'action_removed_vote_for_card@card'
|
||||||
|
=> '{memberCreator} removed vote for this card',
|
||||||
|
'action_rename_custom_field'
|
||||||
|
=> '{memberCreator} renamed the {customField} custom field on {board} (from {name})',
|
||||||
|
'action_rename_custom_field@board'
|
||||||
|
=> '{memberCreator} renamed the {customField} custom field on this board (from {name})',
|
||||||
|
'action_renamed_card'
|
||||||
|
=> '{memberCreator} renamed {card} (from {name})',
|
||||||
|
'action_renamed_card@card'
|
||||||
|
=> '{memberCreator} renamed this card (from {name})',
|
||||||
|
'action_renamed_checkitem'
|
||||||
|
=> '{memberCreator} renamed {checkitem} (from {name})',
|
||||||
|
'action_renamed_checklist'
|
||||||
|
=> '{memberCreator} renamed {checklist} (from {name})',
|
||||||
|
'action_renamed_list'
|
||||||
|
=> '{memberCreator} renamed list {list} (from {name})',
|
||||||
|
'action_reopened_board'
|
||||||
|
=> '{memberCreator} re-opened {board}',
|
||||||
|
'action_reopened_board@board'
|
||||||
|
=> '{memberCreator} re-opened this board',
|
||||||
|
'action_sent_card_to_board'
|
||||||
|
=> '{memberCreator} sent {card} to the board',
|
||||||
|
'action_sent_card_to_board@card'
|
||||||
|
=> '{memberCreator} sent this card to the board',
|
||||||
|
'action_sent_list_to_board'
|
||||||
|
=> '{memberCreator} sent list {list} to the board',
|
||||||
|
'action_set_card_aging_mode_pirate'
|
||||||
|
=> '{memberCreator} changed card aging to pirate mode',
|
||||||
|
'action_set_card_aging_mode_regular'
|
||||||
|
=> '{memberCreator} changed card aging to regular mode',
|
||||||
|
'action_update_board_desc'
|
||||||
|
=> '{memberCreator} changed description of {board}',
|
||||||
|
'action_update_board_desc@board'
|
||||||
|
=> '{memberCreator} changed description of this board',
|
||||||
|
'action_update_board_name'
|
||||||
|
=> '{memberCreator} renamed {board} (from {name})',
|
||||||
|
'action_update_board_name@board'
|
||||||
|
=> '{memberCreator} renamed this board (from {name})',
|
||||||
|
'action_update_custom_field'
|
||||||
|
=> '{memberCreator} updated the {customField} custom field on {board}',
|
||||||
|
'action_update_custom_field@board'
|
||||||
|
=> '{memberCreator} updated the {customField} custom field on this board',
|
||||||
|
'action_update_custom_field_item'
|
||||||
|
=> '{memberCreator} updated the value for the {customFieldItem} custom field on {card}',
|
||||||
|
'action_update_custom_field_item@card'
|
||||||
|
=> '{memberCreator} updated the value for the {customFieldItem} custom field on this card',
|
||||||
|
'action_updated_their_bio'
|
||||||
|
=> '{memberCreator} updated their bio',
|
||||||
|
'action_updated_their_display_name'
|
||||||
|
=> '{memberCreator} updated their display name',
|
||||||
|
'action_updated_their_initials'
|
||||||
|
=> '{memberCreator} updated their initials',
|
||||||
|
'action_updated_their_username'
|
||||||
|
=> '{memberCreator} updated their username',
|
||||||
|
'action_vote_on_card'
|
||||||
|
=> '{memberCreator} voted for {card}',
|
||||||
|
'action_vote_on_card@card'
|
||||||
|
=> '{memberCreator} voted for this card',
|
||||||
|
'action_voting'
|
||||||
|
=> 'voting',
|
||||||
|
'action_withdraw_enterprise_join_request'
|
||||||
|
=> '{memberCreator} withdrew a request to add team {organization} to the enterprise {enterprise}'
|
||||||
|
);
|
||||||
|
|
||||||
|
const REQUEST_ACTIONS_BOARDS = array(
|
||||||
|
'addAttachmentToCard',
|
||||||
|
'addChecklistToCard',
|
||||||
|
'addMemberToCard',
|
||||||
|
'commentCard',
|
||||||
|
'copyCommentCard',
|
||||||
|
'convertToCardFromCheckItem',
|
||||||
|
'createCard',
|
||||||
|
'copyCard',
|
||||||
|
'deleteAttachmentFromCard',
|
||||||
|
'emailCard',
|
||||||
|
'moveCardFromBoard',
|
||||||
|
'moveCardToBoard',
|
||||||
|
'removeChecklistFromCard',
|
||||||
|
'removeMemberFromCard',
|
||||||
|
'updateCard:idList',
|
||||||
|
'updateCard:closed',
|
||||||
|
'updateCard:due',
|
||||||
|
'updateCard:dueComplete',
|
||||||
|
'updateCheckItemStateOnCard',
|
||||||
|
'updateCustomFieldItem',
|
||||||
|
'addMemberToBoard',
|
||||||
|
'addToOrganizationBoard',
|
||||||
|
'copyBoard',
|
||||||
|
'createBoard',
|
||||||
|
'createCustomField',
|
||||||
|
'createList',
|
||||||
|
'deleteCard',
|
||||||
|
'deleteCustomField',
|
||||||
|
'disablePlugin',
|
||||||
|
'disablePowerUp',
|
||||||
|
'enablePlugin',
|
||||||
|
'enablePowerUp',
|
||||||
|
'makeAdminOfBoard',
|
||||||
|
'makeNormalMemberOfBoard',
|
||||||
|
'makeObserverOfBoard',
|
||||||
|
'moveListFromBoard',
|
||||||
|
'moveListToBoard',
|
||||||
|
'removeFromOrganizationBoard',
|
||||||
|
'unconfirmedBoardInvitation',
|
||||||
|
'unconfirmedOrganizationInvitation',
|
||||||
|
'updateBoard',
|
||||||
|
'updateCustomField',
|
||||||
|
'updateList:closed'
|
||||||
|
);
|
||||||
|
|
||||||
|
const REQUEST_ACTIONS_CARDS = array(
|
||||||
|
'addAttachmentToCard',
|
||||||
|
'addChecklistToCard',
|
||||||
|
'addMemberToCard',
|
||||||
|
'commentCard',
|
||||||
|
'copyCommentCard',
|
||||||
|
'convertToCardFromCheckItem',
|
||||||
|
'createCard',
|
||||||
|
'copyCard',
|
||||||
|
'deleteAttachmentFromCard',
|
||||||
|
'emailCard',
|
||||||
|
'moveCardFromBoard',
|
||||||
|
'moveCardToBoard',
|
||||||
|
'removeChecklistFromCard',
|
||||||
|
'removeMemberFromCard',
|
||||||
|
'updateCard:idList',
|
||||||
|
'updateCard:closed',
|
||||||
|
'updateCard:due',
|
||||||
|
'updateCard:dueComplete',
|
||||||
|
'updateCheckItemStateOnCard',
|
||||||
|
'updateCustomFieldItem'
|
||||||
|
);
|
||||||
|
|
||||||
|
private $feedName = '';
|
||||||
|
private $feedURI = '';
|
||||||
|
|
||||||
|
private function queryAPI($path, $params = array()) {
|
||||||
|
$data = json_decode(getContents('https://trello.com/1/'
|
||||||
|
. $path
|
||||||
|
. '?'
|
||||||
|
. http_build_query($params)))
|
||||||
|
or returnServerError('Failed to query trello API');
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function renderAction($action, $textOnly = false) {
|
||||||
|
if(!array_key_exists($action->display->translationKey, self::ACTION_TEXTS)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$strings = array();
|
||||||
|
$entities = (array)$action->display->entities;
|
||||||
|
|
||||||
|
foreach($entities as $entity_name => $entity) {
|
||||||
|
$type = $entity->type;
|
||||||
|
if($type === 'attachmentPreview'
|
||||||
|
&& !$textOnly
|
||||||
|
&& isset($entity->originalUrl)) {
|
||||||
|
$string = '<p><a href="'
|
||||||
|
. $entity->originalUrl
|
||||||
|
. '"><img src="'
|
||||||
|
. $entity->previewUrl
|
||||||
|
. '"></a></p>';
|
||||||
|
} elseif($type === 'card' && !$textOnly) {
|
||||||
|
$string = '<a href="https://trello.com/c/'
|
||||||
|
. $entity->shortLink
|
||||||
|
. '">'
|
||||||
|
. $entity->text
|
||||||
|
. '</a>';
|
||||||
|
} elseif($type === 'member' && !$textOnly) {
|
||||||
|
$string = '<a href="https://trello.com/'
|
||||||
|
. $entity->username
|
||||||
|
. '">'
|
||||||
|
. $entity->text
|
||||||
|
. '</a>';
|
||||||
|
} elseif($type === 'date') {
|
||||||
|
$string = gmdate('M j, Y \a\t g:i A T', strtotime($entity->date));
|
||||||
|
} elseif($type === 'translatable') {
|
||||||
|
$string = self::ACTION_TEXTS[$entity->translationKey];
|
||||||
|
} else {
|
||||||
|
if(isset($entity->text)) {
|
||||||
|
$string = $entity->text;
|
||||||
|
} else {
|
||||||
|
$string = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$strings['{' . $entity_name . '}'] = $string;
|
||||||
|
}
|
||||||
|
|
||||||
|
return str_replace(array_keys($strings),
|
||||||
|
array_values($strings),
|
||||||
|
self::ACTION_TEXTS[$action->display->translationKey]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function collectData() {
|
||||||
|
$apiParams = array(
|
||||||
|
'actions_display' => 'true',
|
||||||
|
'fields' => 'name,url'
|
||||||
|
);
|
||||||
|
switch($this->queriedContext) {
|
||||||
|
case 'Board':
|
||||||
|
$apiParams['actions'] = implode(',', self::REQUEST_ACTIONS_BOARDS);
|
||||||
|
$data = $this->queryAPI('boards/' . $this->getInput('b'), $apiParams);
|
||||||
|
break;
|
||||||
|
case 'Card':
|
||||||
|
$apiParams['actions'] = implode(',', self::REQUEST_ACTIONS_CARDS);
|
||||||
|
$data = $this->queryAPI('cards/' . $this->getInput('c'), $apiParams);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
returnClientError('Invalid context');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->feedName = $data->name;
|
||||||
|
$this->feedURI = $data->url;
|
||||||
|
|
||||||
|
foreach($data->actions as $action) {
|
||||||
|
$item = array();
|
||||||
|
|
||||||
|
$item['title'] = $this->renderAction($action, true);
|
||||||
|
$item['timestamp'] = strtotime($action->date);
|
||||||
|
$item['author'] = $action->memberCreator->fullName;
|
||||||
|
$item['categories'] = array(
|
||||||
|
'trello',
|
||||||
|
$action->data->board->name,
|
||||||
|
$action->type
|
||||||
|
);
|
||||||
|
if(isset($action->data->card)) {
|
||||||
|
$item['categories'][] = $action->data->card->name;
|
||||||
|
$item['uri'] = 'https://trello.com/c/'
|
||||||
|
. $action->data->card->shortLink
|
||||||
|
. '#action-'
|
||||||
|
. $action->id;
|
||||||
|
} else {
|
||||||
|
$item['uri'] = 'https://trello.com/b/'
|
||||||
|
. $action->data->board->shortLink;
|
||||||
|
}
|
||||||
|
$item['content'] = $this->renderAction($action, false);
|
||||||
|
if(isset($action->data->attachment->url)) {
|
||||||
|
$item['enclosures'] = array($action->data->attachment->url);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->items[] = $item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function detectParameters($url) {
|
||||||
|
$regex = '/^(https?:\/\/)?trello\.com\/([bc])\/([^\/?\n]+)/';
|
||||||
|
if(preg_match($regex, $url, $matches) > 0) {
|
||||||
|
return array($matches[2] => $matches[3]);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getURI() {
|
||||||
|
switch($this->queriedContext) {
|
||||||
|
case 'Board':
|
||||||
|
case 'Card':
|
||||||
|
return $this->feedURI;
|
||||||
|
default: return parent::getURI();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName() {
|
||||||
|
switch($this->queriedContext) {
|
||||||
|
case 'Board':
|
||||||
|
case 'Card':
|
||||||
|
return $this->feedName;
|
||||||
|
default: return parent::getName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -16,6 +16,11 @@ class TwitterBridge extends BridgeAbstract {
|
|||||||
'name' => 'Hide images in tweets',
|
'name' => 'Hide images in tweets',
|
||||||
'type' => 'checkbox',
|
'type' => 'checkbox',
|
||||||
'title' => 'Activate to hide images in tweets'
|
'title' => 'Activate to hide images in tweets'
|
||||||
|
),
|
||||||
|
'noimgscaling' => array(
|
||||||
|
'name' => 'Disable image scaling',
|
||||||
|
'type' => 'checkbox',
|
||||||
|
'title' => 'Activate to disable image scaling in tweets (keeps original image)'
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
'By keyword or hashtag' => array(
|
'By keyword or hashtag' => array(
|
||||||
@@ -66,6 +71,41 @@ 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(){
|
public function getName(){
|
||||||
switch($this->queriedContext) {
|
switch($this->queriedContext) {
|
||||||
case 'By keyword or hashtag':
|
case 'By keyword or hashtag':
|
||||||
@@ -144,9 +184,9 @@ class TwitterBridge extends BridgeAbstract {
|
|||||||
|
|
||||||
$item = array();
|
$item = array();
|
||||||
// extract username and sanitize
|
// extract username and sanitize
|
||||||
$item['username'] = $tweet->getAttribute('data-screen-name');
|
$item['username'] = htmlspecialchars_decode($tweet->getAttribute('data-screen-name'), ENT_QUOTES);
|
||||||
// extract fullname (pseudonym)
|
// extract fullname (pseudonym)
|
||||||
$item['fullname'] = $tweet->getAttribute('data-name');
|
$item['fullname'] = htmlspecialchars_decode($tweet->getAttribute('data-name'), ENT_QUOTES);
|
||||||
// get author
|
// get author
|
||||||
$item['author'] = $item['fullname'] . ' (@' . $item['username'] . ')';
|
$item['author'] = $item['fullname'] . ' (@' . $item['username'] . ')';
|
||||||
// get avatar link
|
// get avatar link
|
||||||
@@ -158,7 +198,8 @@ class TwitterBridge extends BridgeAbstract {
|
|||||||
// extract tweet timestamp
|
// extract tweet timestamp
|
||||||
$item['timestamp'] = $tweet->find('span.js-short-timestamp', 0)->getAttribute('data-time');
|
$item['timestamp'] = $tweet->find('span.js-short-timestamp', 0)->getAttribute('data-time');
|
||||||
// generate the title
|
// generate the title
|
||||||
$item['title'] = strip_tags($this->fixAnchorSpacing($tweet->find('p.js-tweet-text', 0), '<a>'));
|
$item['title'] = strip_tags($this->fixAnchorSpacing(htmlspecialchars_decode(
|
||||||
|
$tweet->find('p.js-tweet-text', 0), ENT_QUOTES), '<a>'));
|
||||||
|
|
||||||
switch($this->queriedContext) {
|
switch($this->queriedContext) {
|
||||||
case 'By list':
|
case 'By list':
|
||||||
@@ -203,14 +244,18 @@ EOD;
|
|||||||
$image_html = '';
|
$image_html = '';
|
||||||
$image = $this->getImageURI($tweet);
|
$image = $this->getImageURI($tweet);
|
||||||
if(!$this->getInput('noimg') && !is_null($image)) {
|
if(!$this->getInput('noimg') && !is_null($image)) {
|
||||||
|
// Set image scaling
|
||||||
|
$image_orig = $this->getInput('noimgscaling') ? $image : $image . ':orig';
|
||||||
|
$image_thumb = $this->getInput('noimgscaling') ? $image : $image . ':thumb';
|
||||||
|
|
||||||
// add enclosures
|
// add enclosures
|
||||||
$item['enclosures'] = array($image . ':orig');
|
$item['enclosures'] = array($image_orig);
|
||||||
|
|
||||||
$image_html = <<<EOD
|
$image_html = <<<EOD
|
||||||
<a href="{$image}:orig">
|
<a href="{$image_orig}">
|
||||||
<img
|
<img
|
||||||
style="align:top; max-width:558px; border:1px solid black;"
|
style="align:top; max-width:558px; border:1px solid black;"
|
||||||
src="{$image}:thumb" />
|
src="{$image_thumb}" />
|
||||||
</a>
|
</a>
|
||||||
EOD;
|
EOD;
|
||||||
}
|
}
|
||||||
@@ -245,29 +290,34 @@ EOD;
|
|||||||
$quotedImage_html = '';
|
$quotedImage_html = '';
|
||||||
$quotedImage = $this->getQuotedImageURI($tweet);
|
$quotedImage = $this->getQuotedImageURI($tweet);
|
||||||
if(!$this->getInput('noimg') && !is_null($quotedImage)) {
|
if(!$this->getInput('noimg') && !is_null($quotedImage)) {
|
||||||
|
// Set image scaling
|
||||||
|
$quotedImage_orig = $this->getInput('noimgscaling') ? $quotedImage : $quotedImage . ':orig';
|
||||||
|
$quotedImage_thumb = $this->getInput('noimgscaling') ? $quotedImage : $quotedImage . ':thumb';
|
||||||
|
|
||||||
// add enclosures
|
// add enclosures
|
||||||
$item['enclosures'] = array($quotedImage . ':orig');
|
$item['enclosures'] = array($quotedImage_orig);
|
||||||
|
|
||||||
$quotedImage_html = <<<EOD
|
$quotedImage_html = <<<EOD
|
||||||
<a href="{$quotedImage}:orig">
|
<a href="{$quotedImage_orig}">
|
||||||
<img
|
<img
|
||||||
style="align:top; max-width:558px; border:1px solid black;"
|
style="align:top; max-width:558px; border:1px solid black;"
|
||||||
src="{$quotedImage}:thumb" />
|
src="{$quotedImage_thumb}" />
|
||||||
</a>
|
</a>
|
||||||
EOD;
|
EOD;
|
||||||
}
|
}
|
||||||
|
|
||||||
$item['content'] = <<<EOD
|
$item['content'] = <<<EOD
|
||||||
|
{$item['content']}
|
||||||
|
<hr>
|
||||||
<div style="display: inline-block; vertical-align: top;">
|
<div style="display: inline-block; vertical-align: top;">
|
||||||
<blockquote>{$cleanedQuotedTweet}</blockquote>
|
<blockquote>{$cleanedQuotedTweet}</blockquote>
|
||||||
</div>
|
</div>
|
||||||
<div style="display: block; vertical-align: top;">
|
<div style="display: block; vertical-align: top;">
|
||||||
<blockquote>{$quotedImage_html}</blockquote>
|
<blockquote>{$quotedImage_html}</blockquote>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
|
||||||
{$item['content']}
|
|
||||||
EOD;
|
EOD;
|
||||||
}
|
}
|
||||||
|
$item['content'] = htmlspecialchars_decode($item['content'], ENT_QUOTES);
|
||||||
|
|
||||||
// put out
|
// put out
|
||||||
$this->items[] = $item;
|
$this->items[] = $item;
|
||||||
|
@@ -106,5 +106,4 @@ class UsbekEtRicaBridge extends BridgeAbstract {
|
|||||||
private function replaceUriInHtmlElement($element){
|
private function replaceUriInHtmlElement($element){
|
||||||
return str_replace('href="/', 'href="' . $this->getURI() . '/', $element->innertext);
|
return str_replace('href="/', 'href="' . $this->getURI() . '/', $element->innertext);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -55,6 +55,7 @@ class WhydBridge extends BridgeAbstract {
|
|||||||
$this->items[] = $item;
|
$this->items[] = $item;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getName(){
|
public function getName(){
|
||||||
return (!empty($this->userName) ? $this->userName . ' - ' : '') . 'Whyd Bridge';
|
return (!empty($this->userName) ? $this->userName . ' - ' : '') . 'Whyd Bridge';
|
||||||
}
|
}
|
||||||
|
@@ -93,7 +93,7 @@ class WordPressBridge extends FeedExpander {
|
|||||||
}
|
}
|
||||||
try{
|
try{
|
||||||
$this->collectExpandableDatas($this->getURI() . '/feed/atom/');
|
$this->collectExpandableDatas($this->getURI() . '/feed/atom/');
|
||||||
} catch (HttpException $e) {
|
} catch (Exception $e) {
|
||||||
$this->collectExpandableDatas($this->getURI() . '/?feed=atom');
|
$this->collectExpandableDatas($this->getURI() . '/?feed=atom');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -64,7 +64,6 @@ class WordPressPluginUpdateBridge extends BridgeAbstract {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function getName(){
|
public function getName(){
|
||||||
if(!is_null($this->getInput('q'))) {
|
if(!is_null($this->getInput('q'))) {
|
||||||
return $this->getInput('q') . ' : ' . self::NAME;
|
return $this->getInput('q') . ' : ' . self::NAME;
|
||||||
@@ -74,10 +73,10 @@ class WordPressPluginUpdateBridge extends BridgeAbstract {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private function getCachedDate($url){
|
private function getCachedDate($url){
|
||||||
debugMessage('getting pubdate from url ' . $url . '');
|
Debug::log('getting pubdate from url ' . $url . '');
|
||||||
// Initialize cache
|
// Initialize cache
|
||||||
$cache = Cache::create('FileCache');
|
$cache = Cache::create('FileCache');
|
||||||
$cache->setPath(CACHE_DIR . '/pages');
|
$cache->setPath(PATH_CACHE . 'pages/');
|
||||||
$params = [$url];
|
$params = [$url];
|
||||||
$cache->setParameters($params);
|
$cache->setParameters($params);
|
||||||
// Get cachefile timestamp
|
// Get cachefile timestamp
|
||||||
|
@@ -75,8 +75,7 @@ class XenForoBridge extends BridgeAbstract {
|
|||||||
|
|
||||||
$this->threadurl = filter_var(
|
$this->threadurl = filter_var(
|
||||||
$this->getInput('url'),
|
$this->getInput('url'),
|
||||||
FILTER_VALIDATE_URL,
|
FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED);
|
||||||
FILTER_FLAG_SCHEME_REQUIRED | FILTER_FLAG_HOST_REQUIRED | FILTER_FLAG_PATH_REQUIRED);
|
|
||||||
|
|
||||||
if($this->threadurl === false) {
|
if($this->threadurl === false) {
|
||||||
returnClientError('The URL you provided is invalid!');
|
returnClientError('The URL you provided is invalid!');
|
||||||
@@ -453,10 +452,9 @@ class XenForoBridge extends BridgeAbstract {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// debugMessage(date_format($df, 'U'));
|
// Debug::log(date_format($df, 'U'));
|
||||||
|
|
||||||
return date_format($df, 'U');
|
return date_format($df, 'U');
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -115,6 +115,7 @@ class YGGTorrentBridge extends BridgeAbstract {
|
|||||||
$item = array();
|
$item = array();
|
||||||
$item['timestamp'] = $row->find('.hidden', 1)->plaintext;
|
$item['timestamp'] = $row->find('.hidden', 1)->plaintext;
|
||||||
$item['title'] = $row->find('a', 1)->plaintext;
|
$item['title'] = $row->find('a', 1)->plaintext;
|
||||||
|
$item['uri'] = $row->find('a', 1)->href;
|
||||||
$torrentData = $this->collectTorrentData($row->find('a', 1)->href);
|
$torrentData = $this->collectTorrentData($row->find('a', 1)->href);
|
||||||
$item['author'] = $torrentData['author'];
|
$item['author'] = $torrentData['author'];
|
||||||
$item['content'] = $torrentData['content'];
|
$item['content'] = $torrentData['content'];
|
||||||
|
@@ -1,8 +1,15 @@
|
|||||||
<?php
|
<?php
|
||||||
class ZoneTelechargementBridge extends BridgeAbstract {
|
class ZoneTelechargementBridge extends BridgeAbstract {
|
||||||
const NAME = 'Zone Telechargement';
|
|
||||||
const URI = 'https://www.zone-telechargement1.org/';
|
/* This bridge was initally done for the Website Zone Telechargement,
|
||||||
const DESCRIPTION = 'Suivi de série sur 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 MAINTAINER = 'sysadminstory';
|
const MAINTAINER = 'sysadminstory';
|
||||||
const PARAMETERS = array(
|
const PARAMETERS = array(
|
||||||
'Suivre la publication des épisodes d\'une série en cours de diffusion' => array(
|
'Suivre la publication des épisodes d\'une série en cours de diffusion' => array(
|
||||||
@@ -10,14 +17,14 @@ class ZoneTelechargementBridge extends BridgeAbstract {
|
|||||||
'name' => 'URL de la série',
|
'name' => 'URL de la série',
|
||||||
'type' => 'text',
|
'type' => 'text',
|
||||||
'required' => true,
|
'required' => true,
|
||||||
'title' => 'URL d\'une série sans le https://ww4.zone-telechargement1.org/',
|
'title' => 'URL d\'une série sans le https://www.annuaire-telechargement.com/',
|
||||||
'exampleValue' => 'telecharger-series/31079-halt-and-catch-fire-saison-4-french-hd720p.html'
|
'exampleValue' => 'telecharger-series/31079-halt-and-catch-fire-saison-4-french-hd720p.html'
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
public function getIcon() {
|
public function getIcon() {
|
||||||
return 'https://ww7.zone-telechargement1.org/templates/Default/images/favicon.ico';
|
return 'https://www.annuaire-telechargement.com/templates/Default/images/favicon.ico';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function collectData(){
|
public function collectData(){
|
||||||
|
@@ -5,7 +5,6 @@
|
|||||||
* http://tools.ietf.org/html/rfc4287
|
* http://tools.ietf.org/html/rfc4287
|
||||||
*/
|
*/
|
||||||
class AtomFormat extends FormatAbstract{
|
class AtomFormat extends FormatAbstract{
|
||||||
|
|
||||||
public function stringify(){
|
public function stringify(){
|
||||||
$https = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on' ? 's' : '';
|
$https = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on' ? 's' : '';
|
||||||
$httpHost = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : '';
|
$httpHost = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : '';
|
||||||
@@ -15,7 +14,7 @@ class AtomFormat extends FormatAbstract{
|
|||||||
|
|
||||||
$extraInfos = $this->getExtraInfos();
|
$extraInfos = $this->getExtraInfos();
|
||||||
$title = $this->xml_encode($extraInfos['name']);
|
$title = $this->xml_encode($extraInfos['name']);
|
||||||
$uri = !empty($extraInfos['uri']) ? $extraInfos['uri'] : 'https://github.com/RSS-Bridge/rss-bridge';
|
$uri = !empty($extraInfos['uri']) ? $extraInfos['uri'] : REPOSITORY;
|
||||||
|
|
||||||
$uriparts = parse_url($uri);
|
$uriparts = parse_url($uri);
|
||||||
if(!empty($extraInfos['icon'])) {
|
if(!empty($extraInfos['icon'])) {
|
||||||
@@ -28,30 +27,26 @@ class AtomFormat extends FormatAbstract{
|
|||||||
|
|
||||||
$entries = '';
|
$entries = '';
|
||||||
foreach($this->getItems() as $item) {
|
foreach($this->getItems() as $item) {
|
||||||
$entryAuthor = isset($item['author']) ? $this->xml_encode($item['author']) : '';
|
$entryAuthor = $this->xml_encode($item->getAuthor());
|
||||||
$entryTitle = isset($item['title']) ? $this->xml_encode($item['title']) : '';
|
$entryTitle = $this->xml_encode($item->getTitle());
|
||||||
$entryUri = isset($item['uri']) ? $this->xml_encode($item['uri']) : '';
|
$entryUri = $this->xml_encode($item->getURI());
|
||||||
$entryTimestamp = isset($item['timestamp']) ? $this->xml_encode(date(DATE_ATOM, $item['timestamp'])) : '';
|
$entryTimestamp = $this->xml_encode(date(DATE_ATOM, $item->getTimestamp()));
|
||||||
$entryContent = isset($item['content']) ? $this->xml_encode($this->sanitizeHtml($item['content'])) : '';
|
$entryContent = $this->xml_encode($this->sanitizeHtml($item->getContent()));
|
||||||
|
|
||||||
$entryEnclosures = '';
|
$entryEnclosures = '';
|
||||||
if(isset($item['enclosures'])) {
|
foreach($item->getEnclosures() as $enclosure) {
|
||||||
foreach($item['enclosures'] as $enclosure) {
|
$entryEnclosures .= '<link rel="enclosure" href="'
|
||||||
$entryEnclosures .= '<link rel="enclosure" href="'
|
. $this->xml_encode($enclosure)
|
||||||
. $this->xml_encode($enclosure)
|
. '" type="' . getMimeType($enclosure) . '" />'
|
||||||
. '" type="' . getMimeType($enclosure) . '" />'
|
. PHP_EOL;
|
||||||
. PHP_EOL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$entryCategories = '';
|
$entryCategories = '';
|
||||||
if(isset($item['categories'])) {
|
foreach($item->getCategories() as $category) {
|
||||||
foreach($item['categories'] as $category) {
|
$entryCategories .= '<category term="'
|
||||||
$entryCategories .= '<category term="'
|
. $this->xml_encode($category)
|
||||||
. $this->xml_encode($category)
|
. '"/>'
|
||||||
. '"/>'
|
. PHP_EOL;
|
||||||
. PHP_EOL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$entries .= <<<EOD
|
$entries .= <<<EOD
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
class HtmlFormat extends FormatAbstract {
|
class HtmlFormat extends FormatAbstract {
|
||||||
|
|
||||||
public function stringify(){
|
public function stringify(){
|
||||||
$extraInfos = $this->getExtraInfos();
|
$extraInfos = $this->getExtraInfos();
|
||||||
$title = htmlspecialchars($extraInfos['name']);
|
$title = htmlspecialchars($extraInfos['name']);
|
||||||
@@ -10,31 +9,31 @@ class HtmlFormat extends FormatAbstract {
|
|||||||
|
|
||||||
$entries = '';
|
$entries = '';
|
||||||
foreach($this->getItems() as $item) {
|
foreach($this->getItems() as $item) {
|
||||||
$entryAuthor = isset($item['author']) ? '<br /><p class="author">by: ' . $item['author'] . '</p>' : '';
|
$entryAuthor = $item->getAuthor() ? '<br /><p class="author">by: ' . $item->getAuthor() . '</p>' : '';
|
||||||
$entryTitle = isset($item['title']) ? $this->sanitizeHtml(strip_tags($item['title'])) : '';
|
$entryTitle = $this->sanitizeHtml(strip_tags($item->getTitle()));
|
||||||
$entryUri = isset($item['uri']) ? $item['uri'] : $uri;
|
$entryUri = $item->getURI() ?: $uri;
|
||||||
|
|
||||||
$entryTimestamp = '';
|
$entryTimestamp = '';
|
||||||
if(isset($item['timestamp'])) {
|
if($item->getTimestamp()) {
|
||||||
$entryTimestamp = '<time datetime="'
|
$entryTimestamp = '<time datetime="'
|
||||||
. date(DATE_ATOM, $item['timestamp'])
|
. date(DATE_ATOM, $item->getTimestamp())
|
||||||
. '">'
|
. '">'
|
||||||
. date(DATE_ATOM, $item['timestamp'])
|
. date(DATE_ATOM, $item->getTimestamp())
|
||||||
. '</time>';
|
. '</time>';
|
||||||
}
|
}
|
||||||
|
|
||||||
$entryContent = '';
|
$entryContent = '';
|
||||||
if(isset($item['content'])) {
|
if($item->getContent()) {
|
||||||
$entryContent = '<div class="content">'
|
$entryContent = '<div class="content">'
|
||||||
. $this->sanitizeHtml($item['content'])
|
. $this->sanitizeHtml($item->getContent())
|
||||||
. '</div>';
|
. '</div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
$entryEnclosures = '';
|
$entryEnclosures = '';
|
||||||
if(isset($item['enclosures'])) {
|
if(!empty($item->getEnclosures())) {
|
||||||
$entryEnclosures = '<div class="attachments"><p>Attachments:</p>';
|
$entryEnclosures = '<div class="attachments"><p>Attachments:</p>';
|
||||||
|
|
||||||
foreach($item['enclosures'] as $enclosure) {
|
foreach($item->getEnclosures() as $enclosure) {
|
||||||
$url = $this->sanitizeHtml($enclosure);
|
$url = $this->sanitizeHtml($enclosure);
|
||||||
|
|
||||||
$entryEnclosures .= '<li class="enclosure"><a href="'
|
$entryEnclosures .= '<li class="enclosure"><a href="'
|
||||||
@@ -48,10 +47,10 @@ class HtmlFormat extends FormatAbstract {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$entryCategories = '';
|
$entryCategories = '';
|
||||||
if(isset($item['categories']) && count($item['categories']) > 0) {
|
if(!empty($item->getCategories())) {
|
||||||
$entryCategories = '<div class="categories"><p>Categories:</p>';
|
$entryCategories = '<div class="categories"><p>Categories:</p>';
|
||||||
|
|
||||||
foreach($item['categories'] as $category) {
|
foreach($item->getCategories() as $category) {
|
||||||
|
|
||||||
$entryCategories .= '<li class="category">'
|
$entryCategories .= '<li class="category">'
|
||||||
. $this->sanitizeHtml($category)
|
. $this->sanitizeHtml($category)
|
||||||
|
@@ -1,13 +1,110 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* Json
|
* JsonFormat - JSON Feed Version 1
|
||||||
* Builds a JSON string from $this->items and return it to browser.
|
* https://jsonfeed.org/version/1
|
||||||
*/
|
*
|
||||||
|
* Validators:
|
||||||
|
* https://validator.jsonfeed.org
|
||||||
|
* https://github.com/vigetlabs/json-feed-validator
|
||||||
|
*/
|
||||||
class JsonFormat extends FormatAbstract {
|
class JsonFormat extends FormatAbstract {
|
||||||
|
const VENDOR_EXCLUDES = array(
|
||||||
|
'author',
|
||||||
|
'title',
|
||||||
|
'uri',
|
||||||
|
'timestamp',
|
||||||
|
'content',
|
||||||
|
'enclosures',
|
||||||
|
'categories',
|
||||||
|
);
|
||||||
|
|
||||||
public function stringify(){
|
public function stringify(){
|
||||||
$items = $this->getItems();
|
$urlScheme = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 'https://' : 'http://';
|
||||||
$toReturn = json_encode($items, JSON_PRETTY_PRINT);
|
$urlHost = (isset($_SERVER['HTTP_HOST'])) ? $_SERVER['HTTP_HOST'] : '';
|
||||||
|
$urlPath = (isset($_SERVER['PATH_INFO'])) ? $_SERVER['PATH_INFO'] : '';
|
||||||
|
$urlRequest = (isset($_SERVER['REQUEST_URI'])) ? $_SERVER['REQUEST_URI'] : '';
|
||||||
|
|
||||||
|
$extraInfos = $this->getExtraInfos();
|
||||||
|
|
||||||
|
$data = array(
|
||||||
|
'version' => 'https://jsonfeed.org/version/1',
|
||||||
|
'title' => (!empty($extraInfos['name'])) ? $extraInfos['name'] : $urlHost,
|
||||||
|
'home_page_url' => (!empty($extraInfos['uri'])) ? $extraInfos['uri'] : REPOSITORY,
|
||||||
|
'feed_url' => $urlScheme . $urlHost . $urlRequest
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!empty($extraInfos['icon'])) {
|
||||||
|
$data['icon'] = $extraInfos['icon'];
|
||||||
|
$data['favicon'] = $extraInfos['icon'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$items = array();
|
||||||
|
foreach ($this->getItems() as $item) {
|
||||||
|
$entry = array();
|
||||||
|
|
||||||
|
$entryAuthor = $item->getAuthor();
|
||||||
|
$entryTitle = $item->getTitle();
|
||||||
|
$entryUri = $item->getURI();
|
||||||
|
$entryTimestamp = $item->getTimestamp();
|
||||||
|
$entryContent = $this->sanitizeHtml($item->getContent());
|
||||||
|
$entryEnclosures = $item->getEnclosures();
|
||||||
|
$entryCategories = $item->getCategories();
|
||||||
|
|
||||||
|
$vendorFields = $item->toArray();
|
||||||
|
foreach (self::VENDOR_EXCLUDES as $key) {
|
||||||
|
unset($vendorFields[$key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$entry['id'] = $entryUri;
|
||||||
|
|
||||||
|
if (!empty($entryTitle)) {
|
||||||
|
$entry['title'] = $entryTitle;
|
||||||
|
}
|
||||||
|
if (!empty($entryAuthor)) {
|
||||||
|
$entry['author'] = array(
|
||||||
|
'name' => $entryAuthor
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!empty($entryTimestamp)) {
|
||||||
|
$entry['date_modified'] = gmdate(DATE_ATOM, $entryTimestamp);
|
||||||
|
}
|
||||||
|
if (!empty($entryUri)) {
|
||||||
|
$entry['url'] = $entryUri;
|
||||||
|
}
|
||||||
|
if (!empty($entryContent)) {
|
||||||
|
if ($this->isHTML($entryContent)) {
|
||||||
|
$entry['content_html'] = $entryContent;
|
||||||
|
} else {
|
||||||
|
$entry['content_text'] = $entryContent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!empty($entryEnclosures)) {
|
||||||
|
$entry['attachments'] = array();
|
||||||
|
foreach ($entryEnclosures as $enclosure) {
|
||||||
|
$entry['attachments'][] = array(
|
||||||
|
'url' => $enclosure,
|
||||||
|
'mime_type' => getMimeType($enclosure)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!empty($entryCategories)) {
|
||||||
|
$entry['tags'] = array();
|
||||||
|
foreach ($entryCategories as $category) {
|
||||||
|
$entry['tags'][] = $category;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!empty($vendorFields)) {
|
||||||
|
$entry['_rssbridge'] = $vendorFields;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($entry['id']))
|
||||||
|
$entry['id'] = hash('sha1', $entryTitle . $entryContent);
|
||||||
|
|
||||||
|
$items[] = $entry;
|
||||||
|
}
|
||||||
|
$data['items'] = $items;
|
||||||
|
|
||||||
|
$toReturn = json_encode($data, JSON_PRETTY_PRINT);
|
||||||
|
|
||||||
// Remove invalid non-UTF8 characters
|
// Remove invalid non-UTF8 characters
|
||||||
ini_set('mbstring.substitute_character', 'none');
|
ini_set('mbstring.substitute_character', 'none');
|
||||||
@@ -22,4 +119,8 @@ class JsonFormat extends FormatAbstract {
|
|||||||
|
|
||||||
return parent::display();
|
return parent::display();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function isHTML($text) {
|
||||||
|
return (strlen(strip_tags($text)) != strlen($text));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,7 +4,6 @@
|
|||||||
* Documentation Source http://www.rssboard.org/media-rss
|
* Documentation Source http://www.rssboard.org/media-rss
|
||||||
*/
|
*/
|
||||||
class MrssFormat extends FormatAbstract {
|
class MrssFormat extends FormatAbstract {
|
||||||
|
|
||||||
public function stringify(){
|
public function stringify(){
|
||||||
$https = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on' ? 's' : '';
|
$https = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on' ? 's' : '';
|
||||||
$httpHost = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : '';
|
$httpHost = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : '';
|
||||||
@@ -18,7 +17,7 @@ class MrssFormat extends FormatAbstract {
|
|||||||
if(!empty($extraInfos['uri'])) {
|
if(!empty($extraInfos['uri'])) {
|
||||||
$uri = $this->xml_encode($extraInfos['uri']);
|
$uri = $this->xml_encode($extraInfos['uri']);
|
||||||
} else {
|
} else {
|
||||||
$uri = 'https://github.com/RSS-Bridge/rss-bridge';
|
$uri = REPOSITORY;
|
||||||
}
|
}
|
||||||
|
|
||||||
$uriparts = parse_url($uri);
|
$uriparts = parse_url($uri);
|
||||||
@@ -26,24 +25,24 @@ class MrssFormat extends FormatAbstract {
|
|||||||
|
|
||||||
$items = '';
|
$items = '';
|
||||||
foreach($this->getItems() as $item) {
|
foreach($this->getItems() as $item) {
|
||||||
$itemAuthor = isset($item['author']) ? $this->xml_encode($item['author']) : '';
|
$itemAuthor = $this->xml_encode($item->getAuthor());
|
||||||
$itemTitle = strip_tags(isset($item['title']) ? $this->xml_encode($item['title']) : '');
|
$itemTitle = $this->xml_encode($item->getTitle());
|
||||||
$itemUri = isset($item['uri']) ? $this->xml_encode($item['uri']) : '';
|
$itemUri = $this->xml_encode($item->getURI());
|
||||||
$itemTimestamp = isset($item['timestamp']) ? $this->xml_encode(date(DATE_RFC2822, $item['timestamp'])) : '';
|
$itemTimestamp = $this->xml_encode(date(DATE_RFC2822, $item->getTimestamp()));
|
||||||
$itemContent = isset($item['content']) ? $this->xml_encode($this->sanitizeHtml($item['content'])) : '';
|
$itemContent = $this->xml_encode($this->sanitizeHtml($item->getContent()));
|
||||||
|
|
||||||
$entryEnclosuresWarning = '';
|
$entryEnclosuresWarning = '';
|
||||||
$entryEnclosures = '';
|
$entryEnclosures = '';
|
||||||
if(isset($item['enclosures'])) {
|
if(!empty($item->getEnclosures())) {
|
||||||
$entryEnclosures .= '<enclosure url="'
|
$entryEnclosures .= '<enclosure url="'
|
||||||
. $this->xml_encode($item['enclosures'][0])
|
. $this->xml_encode($item->getEnclosures()[0])
|
||||||
. '" type="' . getMimeType($item['enclosures'][0]) . '" />';
|
. '" type="' . getMimeType($item->getEnclosures()[0]) . '" />';
|
||||||
|
|
||||||
if(count($item['enclosures']) > 1) {
|
if(count($item->getEnclosures()) > 1) {
|
||||||
$entryEnclosures .= PHP_EOL;
|
$entryEnclosures .= PHP_EOL;
|
||||||
$entryEnclosuresWarning = '<br>Warning:
|
$entryEnclosuresWarning = '<br>Warning:
|
||||||
Some media files might not be shown to you. Consider using the ATOM format instead!';
|
Some media files might not be shown to you. Consider using the ATOM format instead!';
|
||||||
foreach($item['enclosures'] as $enclosure) {
|
foreach($item->getEnclosures() as $enclosure) {
|
||||||
$entryEnclosures .= '<atom:link rel="enclosure" href="'
|
$entryEnclosures .= '<atom:link rel="enclosure" href="'
|
||||||
. $enclosure . '" type="' . getMimeType($enclosure) . '" />'
|
. $enclosure . '" type="' . getMimeType($enclosure) . '" />'
|
||||||
. PHP_EOL;
|
. PHP_EOL;
|
||||||
@@ -52,13 +51,10 @@ Some media files might not be shown to you. Consider using the ATOM format inste
|
|||||||
}
|
}
|
||||||
|
|
||||||
$entryCategories = '';
|
$entryCategories = '';
|
||||||
if(isset($item['categories'])) {
|
foreach($item->getCategories() as $category) {
|
||||||
|
$entryCategories .= '<category>'
|
||||||
foreach($item['categories'] as $category) {
|
. $category . '</category>'
|
||||||
$entryCategories .= '<category>'
|
. PHP_EOL;
|
||||||
. $category . '</category>'
|
|
||||||
. PHP_EOL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$items .= <<<EOD
|
$items .= <<<EOD
|
||||||
|
@@ -4,10 +4,15 @@
|
|||||||
* Returns $this->items as raw php data.
|
* Returns $this->items as raw php data.
|
||||||
*/
|
*/
|
||||||
class PlaintextFormat extends FormatAbstract {
|
class PlaintextFormat extends FormatAbstract {
|
||||||
|
|
||||||
public function stringify(){
|
public function stringify(){
|
||||||
$items = $this->getItems();
|
$items = $this->getItems();
|
||||||
$toReturn = print_r($items, true);
|
$data = array();
|
||||||
|
|
||||||
|
foreach($items as $item) {
|
||||||
|
$data[] = $item->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
$toReturn = print_r($data, true);
|
||||||
|
|
||||||
// Remove invalid non-UTF8 characters
|
// Remove invalid non-UTF8 characters
|
||||||
ini_set('mbstring.substitute_character', 'none');
|
ini_set('mbstring.substitute_character', 'none');
|
||||||
|
200
index.php
200
index.php
@@ -1,40 +1,5 @@
|
|||||||
<?php
|
<?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::verifyInstallation();
|
||||||
Configuration::loadConfiguration();
|
Configuration::loadConfiguration();
|
||||||
@@ -54,13 +19,15 @@ if (isset($argv)) {
|
|||||||
$params = $_GET;
|
$params = $_GET;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME : beta test UA spoofing, please report any blacklisting by PHP-fopen-unfriendly websites
|
define('USER_AGENT',
|
||||||
|
'Mozilla/5.0 (X11; Linux x86_64; rv:30.0) Gecko/20121202 Firefox/30.0(rss-bridge/'
|
||||||
|
. Configuration::$VERSION
|
||||||
|
. ';+'
|
||||||
|
. REPOSITORY
|
||||||
|
. ')'
|
||||||
|
);
|
||||||
|
|
||||||
$userAgent = 'Mozilla/5.0(X11; Linux x86_64; rv:30.0)';
|
ini_set('user_agent', USER_AGENT);
|
||||||
$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
|
// default whitelist
|
||||||
$whitelist_default = array(
|
$whitelist_default = array(
|
||||||
@@ -69,7 +36,7 @@ $whitelist_default = array(
|
|||||||
'DansTonChatBridge',
|
'DansTonChatBridge',
|
||||||
'DuckDuckGoBridge',
|
'DuckDuckGoBridge',
|
||||||
'FacebookBridge',
|
'FacebookBridge',
|
||||||
'FlickrExploreBridge',
|
'FlickrBridge',
|
||||||
'GooglePlusPostBridge',
|
'GooglePlusPostBridge',
|
||||||
'GoogleSearchBridge',
|
'GoogleSearchBridge',
|
||||||
'IdenticaBridge',
|
'IdenticaBridge',
|
||||||
@@ -83,26 +50,7 @@ $whitelist_default = array(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
Bridge::setDir(__DIR__ . '/bridges/');
|
Bridge::setWhitelist($whitelist_default);
|
||||||
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);
|
$showInactive = filter_input(INPUT_GET, 'show_inactive', FILTER_VALIDATE_BOOLEAN);
|
||||||
$action = array_key_exists('action', $params) ? $params['action'] : null;
|
$action = array_key_exists('action', $params) ? $params['action'] : null;
|
||||||
@@ -115,7 +63,7 @@ try {
|
|||||||
$list->bridges = array();
|
$list->bridges = array();
|
||||||
$list->total = 0;
|
$list->total = 0;
|
||||||
|
|
||||||
foreach(Bridge::listBridges() as $bridgeName) {
|
foreach(Bridge::getBridgeNames() as $bridgeName) {
|
||||||
|
|
||||||
$bridge = Bridge::create($bridgeName);
|
$bridge = Bridge::create($bridgeName);
|
||||||
|
|
||||||
@@ -129,7 +77,7 @@ try {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$status = Bridge::isWhitelisted($whitelist_selection, strtolower($bridgeName)) ? 'active' : 'inactive';
|
$status = Bridge::isWhitelisted($bridgeName) ? 'active' : 'inactive';
|
||||||
|
|
||||||
$list->bridges[$bridgeName] = array(
|
$list->bridges[$bridgeName] = array(
|
||||||
'status' => $status,
|
'status' => $status,
|
||||||
@@ -148,13 +96,44 @@ try {
|
|||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
echo json_encode($list, JSON_PRETTY_PRINT);
|
echo json_encode($list, JSON_PRETTY_PRINT);
|
||||||
|
|
||||||
} elseif($action === 'display' && !empty($bridge)) {
|
} elseif($action === 'detect') {
|
||||||
// DEPRECATED: 'nameBridge' scheme is replaced by 'name' in bridge parameter values
|
|
||||||
// this is to keep compatibility until futher complete removal
|
$targetURL = $params['url']
|
||||||
if(($pos = strpos($bridge, 'Bridge')) === (strlen($bridge) - strlen('Bridge'))) {
|
or returnClientError('You must specify a url!');
|
||||||
$bridge = substr($bridge, 0, $pos);
|
|
||||||
|
$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)) {
|
||||||
|
|
||||||
$format = $params['format']
|
$format = $params['format']
|
||||||
or returnClientError('You must specify a format!');
|
or returnClientError('You must specify a format!');
|
||||||
|
|
||||||
@@ -165,8 +144,8 @@ try {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// whitelist control
|
// whitelist control
|
||||||
if(!Bridge::isWhitelisted($whitelist_selection, strtolower($bridge))) {
|
if(!Bridge::isWhitelisted($bridge)) {
|
||||||
throw new \HttpException('This bridge is not whitelisted', 401);
|
throw new \Exception('This bridge is not whitelisted', 401);
|
||||||
die;
|
die;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,7 +162,10 @@ try {
|
|||||||
if(array_key_exists('_cache_timeout', $params)) {
|
if(array_key_exists('_cache_timeout', $params)) {
|
||||||
|
|
||||||
if(!CUSTOM_CACHE_TIMEOUT) {
|
if(!CUSTOM_CACHE_TIMEOUT) {
|
||||||
throw new \HttpException('This server doesn\'t support "_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();
|
||||||
}
|
}
|
||||||
|
|
||||||
$cache_timeout = filter_var($params['_cache_timeout'], FILTER_VALIDATE_INT);
|
$cache_timeout = filter_var($params['_cache_timeout'], FILTER_VALIDATE_INT);
|
||||||
@@ -221,7 +203,7 @@ try {
|
|||||||
|
|
||||||
// Initialize cache
|
// Initialize cache
|
||||||
$cache = Cache::create('FileCache');
|
$cache = Cache::create('FileCache');
|
||||||
$cache->setPath(CACHE_DIR);
|
$cache->setPath(PATH_CACHE);
|
||||||
$cache->purgeCache(86400); // 24 hours
|
$cache->purgeCache(86400); // 24 hours
|
||||||
$cache->setParameters($cache_params);
|
$cache->setParameters($cache_params);
|
||||||
|
|
||||||
@@ -231,7 +213,7 @@ try {
|
|||||||
|
|
||||||
if($mtime !== false
|
if($mtime !== false
|
||||||
&& (time() - $cache_timeout < $mtime)
|
&& (time() - $cache_timeout < $mtime)
|
||||||
&& (!defined('DEBUG') || DEBUG !== true)) { // Load cached data
|
&& !Debug::isEnabled()) { // Load cached data
|
||||||
|
|
||||||
// Send "Not Modified" response if client supports it
|
// Send "Not Modified" response if client supports it
|
||||||
// Implementation based on https://stackoverflow.com/a/10847262
|
// Implementation based on https://stackoverflow.com/a/10847262
|
||||||
@@ -239,7 +221,7 @@ try {
|
|||||||
$stime = strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']);
|
$stime = strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']);
|
||||||
|
|
||||||
if($mtime <= $stime) { // Cached data is older or same
|
if($mtime <= $stime) { // Cached data is older or same
|
||||||
header('HTTP/1.1 304 Not Modified');
|
header('Last-Modified: ' . gmdate('D, d M Y H:i:s ', $mtime) . 'GMT', true, 304);
|
||||||
die();
|
die();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -247,7 +229,10 @@ try {
|
|||||||
$cached = $cache->loadData();
|
$cached = $cache->loadData();
|
||||||
|
|
||||||
if(isset($cached['items']) && isset($cached['extraInfos'])) {
|
if(isset($cached['items']) && isset($cached['extraInfos'])) {
|
||||||
$items = $cached['items'];
|
foreach($cached['items'] as $item) {
|
||||||
|
$items[] = new \FeedItem($item);
|
||||||
|
}
|
||||||
|
|
||||||
$infos = $cached['extraInfos'];
|
$infos = $cached['extraInfos'];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,6 +243,19 @@ try {
|
|||||||
$bridge->collectData();
|
$bridge->collectData();
|
||||||
|
|
||||||
$items = $bridge->getItems();
|
$items = $bridge->getItems();
|
||||||
|
|
||||||
|
// Transform "legacy" items to FeedItems if necessary.
|
||||||
|
// Remove this code when support for "legacy" items ends!
|
||||||
|
if(isset($items[0]) && is_array($items[0])) {
|
||||||
|
$feedItems = array();
|
||||||
|
|
||||||
|
foreach($items as $item) {
|
||||||
|
$feedItems[] = new \FeedItem($item);
|
||||||
|
}
|
||||||
|
|
||||||
|
$items = $feedItems;
|
||||||
|
}
|
||||||
|
|
||||||
$infos = array(
|
$infos = array(
|
||||||
'name' => $bridge->getName(),
|
'name' => $bridge->getName(),
|
||||||
'uri' => $bridge->getURI(),
|
'uri' => $bridge->getURI(),
|
||||||
@@ -266,42 +264,52 @@ try {
|
|||||||
} catch(Error $e) {
|
} catch(Error $e) {
|
||||||
error_log($e);
|
error_log($e);
|
||||||
|
|
||||||
$item = array();
|
$item = new \FeedItem();
|
||||||
|
|
||||||
// Create "new" error message every 24 hours
|
// Create "new" error message every 24 hours
|
||||||
$params['_error_time'] = urlencode((int)(time() / 86400));
|
$params['_error_time'] = urlencode((int)(time() / 86400));
|
||||||
|
|
||||||
// Error 0 is a special case (i.e. "trying to get property of non-object")
|
// Error 0 is a special case (i.e. "trying to get property of non-object")
|
||||||
if($e->getCode() === 0) {
|
if($e->getCode() === 0) {
|
||||||
$item['title'] = 'Bridge encountered an unexpected situation! (' . $params['_error_time'] . ')';
|
$item->setTitle('Bridge encountered an unexpected situation! (' . $params['_error_time'] . ')');
|
||||||
} else {
|
} else {
|
||||||
$item['title'] = 'Bridge returned error ' . $e->getCode() . '! (' . $params['_error_time'] . ')';
|
$item->setTitle('Bridge returned error ' . $e->getCode() . '! (' . $params['_error_time'] . ')');
|
||||||
}
|
}
|
||||||
|
|
||||||
$item['uri'] = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) . '?' . http_build_query($params);
|
$item->setURI(
|
||||||
$item['timestamp'] = time();
|
(isset($_SERVER['REQUEST_URI']) ? parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) : '')
|
||||||
$item['content'] = buildBridgeException($e, $bridge);
|
. '?'
|
||||||
|
. http_build_query($params)
|
||||||
|
);
|
||||||
|
|
||||||
|
$item->setTimestamp(time());
|
||||||
|
$item->setContent(buildBridgeException($e, $bridge));
|
||||||
|
|
||||||
$items[] = $item;
|
$items[] = $item;
|
||||||
} catch(Exception $e) {
|
} catch(Exception $e) {
|
||||||
error_log($e);
|
error_log($e);
|
||||||
|
|
||||||
$item = array();
|
$item = new \FeedItem();
|
||||||
|
|
||||||
// Create "new" error message every 24 hours
|
// Create "new" error message every 24 hours
|
||||||
$params['_error_time'] = urlencode((int)(time() / 86400));
|
$params['_error_time'] = urlencode((int)(time() / 86400));
|
||||||
|
|
||||||
$item['uri'] = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) . '?' . http_build_query($params);
|
$item->setURI(
|
||||||
$item['title'] = 'Bridge returned error ' . $e->getCode() . '! (' . $params['_error_time'] . ')';
|
(isset($_SERVER['REQUEST_URI']) ? parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) : '')
|
||||||
$item['timestamp'] = time();
|
. '?'
|
||||||
$item['content'] = buildBridgeException($e, $bridge);
|
. http_build_query($params)
|
||||||
|
);
|
||||||
|
|
||||||
|
$item->setTitle('Bridge returned error ' . $e->getCode() . '! (' . $params['_error_time'] . ')');
|
||||||
|
$item->setTimestamp(time());
|
||||||
|
$item->setContent(buildBridgeException($e, $bridge));
|
||||||
|
|
||||||
$items[] = $item;
|
$items[] = $item;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store data in cache
|
// Store data in cache
|
||||||
$cache->saveData(array(
|
$cache->saveData(array(
|
||||||
'items' => $items,
|
'items' => array_map(function($i){ return $i->toArray(); }, $items),
|
||||||
'extraInfos' => $infos
|
'extraInfos' => $infos
|
||||||
));
|
));
|
||||||
|
|
||||||
@@ -316,24 +324,18 @@ try {
|
|||||||
$format->display();
|
$format->display();
|
||||||
} catch(Error $e) {
|
} catch(Error $e) {
|
||||||
error_log($e);
|
error_log($e);
|
||||||
http_response_code($e->getCode());
|
header('Content-Type: text/html', true, $e->getCode());
|
||||||
header('Content-Type: text/html');
|
|
||||||
die(buildTransformException($e, $bridge));
|
die(buildTransformException($e, $bridge));
|
||||||
} catch(Exception $e) {
|
} catch(Exception $e) {
|
||||||
error_log($e);
|
error_log($e);
|
||||||
http_response_code($e->getCode());
|
header('Content-Type: text/html', true, $e->getCode());
|
||||||
header('Content-Type: text/html');
|
|
||||||
die(buildTransformException($e, $bridge));
|
die(buildTransformException($e, $bridge));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
echo BridgeList::create($whitelist_selection, $showInactive);
|
echo BridgeList::create($showInactive);
|
||||||
}
|
}
|
||||||
} catch(HttpException $e) {
|
|
||||||
error_log($e);
|
|
||||||
http_response_code($e->getCode());
|
|
||||||
header('Content-Type: text/plain');
|
|
||||||
die($e->getMessage());
|
|
||||||
} catch(\Exception $e) {
|
} catch(\Exception $e) {
|
||||||
error_log($e);
|
error_log($e);
|
||||||
|
header('Content-Type: text/plain', true, $e->getCode());
|
||||||
die($e->getMessage());
|
die($e->getMessage());
|
||||||
}
|
}
|
||||||
|
@@ -1,12 +1,60 @@
|
|||||||
<?php
|
<?php
|
||||||
class Authentication {
|
/**
|
||||||
|
* 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() {
|
public static function showPromptIfNeeded() {
|
||||||
|
|
||||||
if(Configuration::getConfig('authentication', 'enable') === true) {
|
if(Configuration::getConfig('authentication', 'enable') === true) {
|
||||||
if(!Authentication::verifyPrompt()) {
|
if(!Authentication::verifyPrompt()) {
|
||||||
header('WWW-Authenticate: Basic realm="RSS-Bridge"');
|
header('WWW-Authenticate: Basic realm="RSS-Bridge"', true, 401);
|
||||||
header('HTTP/1.0 401 Unauthorized');
|
|
||||||
die('Please authenticate in order to access this instance !');
|
die('Please authenticate in order to access this instance !');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -14,6 +62,13 @@ 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() {
|
public static function verifyPrompt() {
|
||||||
|
|
||||||
if(isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])) {
|
if(isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])) {
|
||||||
@@ -27,5 +82,4 @@ class Authentication {
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
310
lib/Bridge.php
310
lib/Bridge.php
@@ -1,88 +1,296 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once(__DIR__ . '/BridgeInterface.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');
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
class Bridge {
|
class Bridge {
|
||||||
|
|
||||||
static protected $dirBridge;
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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(){
|
public function __construct(){
|
||||||
throw new \LogicException('Please use ' . __CLASS__ . '::create for new object.');
|
throw new \LogicException('Use ' . __CLASS__ . '::create($name) to create bridge objects!');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new bridge object
|
* Creates a new bridge object from the working directory.
|
||||||
* @param string $nameBridge Defined bridge name you want use
|
*
|
||||||
* @return Bridge object dedicated
|
* @throws \InvalidArgumentException if the requested bridge name is invalid.
|
||||||
*/
|
* @throws \Exception if the requested bridge doesn't exist in the working
|
||||||
static public function create($nameBridge){
|
* directory.
|
||||||
if(!preg_match('@^[A-Z][a-zA-Z0-9-]*$@', $nameBridge)) {
|
* @param string $name Name of the bridge object.
|
||||||
$message = <<<EOD
|
* @return object|bool The bridge object or false if the class is not instantiable.
|
||||||
'nameBridge' must start with one uppercase character followed or not by
|
*/
|
||||||
alphanumeric or dash characters!
|
public static function create($name){
|
||||||
EOD;
|
if(!self::isBridgeName($name)) {
|
||||||
throw new \InvalidArgumentException($message);
|
throw new \InvalidArgumentException('Bridge name invalid!');
|
||||||
}
|
}
|
||||||
|
|
||||||
$nameBridge = $nameBridge . 'Bridge';
|
$name = self::sanitizeBridgeName($name) . 'Bridge';
|
||||||
$pathBridge = self::getDir() . $nameBridge . '.php';
|
$filePath = self::getWorkingDir() . $name . '.php';
|
||||||
|
|
||||||
if(!file_exists($pathBridge)) {
|
if(!file_exists($filePath)) {
|
||||||
throw new \Exception('The bridge you looking for does not exist. It should be at path '
|
throw new \Exception('Bridge file ' . $filePath . ' does not exist!');
|
||||||
. $pathBridge);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
require_once $pathBridge;
|
require_once $filePath;
|
||||||
|
|
||||||
if((new ReflectionClass($nameBridge))->isInstantiable()) {
|
if((new \ReflectionClass($name))->isInstantiable()) {
|
||||||
return new $nameBridge();
|
return new $name();
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static public function setDir($dirBridge){
|
/**
|
||||||
if(!is_string($dirBridge)) {
|
* Sets the working directory.
|
||||||
throw new \InvalidArgumentException('Dir bridge must be a string.');
|
*
|
||||||
|
* @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!');
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!file_exists($dirBridge)) {
|
if(!file_exists($dir)) {
|
||||||
throw new \Exception('Dir bridge does not exist.');
|
throw new \Exception('Working directory does not exist!');
|
||||||
}
|
}
|
||||||
|
|
||||||
self::$dirBridge = $dirBridge;
|
if(!is_dir($dir)) {
|
||||||
}
|
throw new \InvalidArgumentException('Working directory is not a directory!');
|
||||||
|
|
||||||
static public function getDir(){
|
|
||||||
if(is_null(self::$dirBridge)) {
|
|
||||||
throw new \LogicException(__CLASS__ . ' class need to know bridge path !');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return self::$dirBridge;
|
self::$workingDir = realpath($dir) . '/';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lists the available bridges.
|
* Returns the working directory.
|
||||||
* @return array List of the bridges
|
* The working directory must be specified with {@see Bridge::setWorkingDir()}!
|
||||||
*/
|
*
|
||||||
static public function listBridges(){
|
* @throws \LogicException if the working directory is not set.
|
||||||
$listBridge = array();
|
* @return string The current working directory.
|
||||||
$dirFiles = scandir(self::getDir());
|
*/
|
||||||
|
public static function getWorkingDir(){
|
||||||
|
if(is_null(self::$workingDir)) {
|
||||||
|
throw new \LogicException('Working directory is not set!');
|
||||||
|
}
|
||||||
|
|
||||||
if($dirFiles !== false) {
|
return self::$workingDir;
|
||||||
foreach($dirFiles as $fileName) {
|
}
|
||||||
if(preg_match('@^([^.]+)Bridge\.php$@U', $fileName, $out)) {
|
|
||||||
$listBridge[] = $out[1];
|
/**
|
||||||
|
* 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];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $listBridge;
|
return $bridgeNames;
|
||||||
}
|
}
|
||||||
|
|
||||||
static public function isWhitelisted($whitelist, $name){
|
/**
|
||||||
return in_array($name, $whitelist)
|
* Checks if a bridge is whitelisted.
|
||||||
|| in_array($name . '.php', $whitelist)
|
*
|
||||||
|| in_array($name . 'bridge', $whitelist) // DEPRECATED
|
* @param string $name Name of the bridge.
|
||||||
|| in_array($name . 'bridge.php', $whitelist) // DEPRECATED
|
* @return bool True if the bridge is whitelisted.
|
||||||
|| (count($whitelist) === 1 && trim($whitelist[0]) === '*');
|
*/
|
||||||
|
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
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,32 +1,112 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once(__DIR__ . '/BridgeInterface.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
|
||||||
|
*/
|
||||||
abstract class BridgeAbstract implements BridgeInterface {
|
abstract class BridgeAbstract implements BridgeInterface {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of the bridge
|
||||||
|
*
|
||||||
|
* Use {@see BridgeAbstract::getName()} to read this parameter
|
||||||
|
*/
|
||||||
const NAME = 'Unnamed bridge';
|
const NAME = 'Unnamed bridge';
|
||||||
const URI = '';
|
|
||||||
const DESCRIPTION = 'No description provided';
|
|
||||||
const MAINTAINER = 'No maintainer';
|
|
||||||
const CACHE_TIMEOUT = 3600;
|
|
||||||
const PARAMETERS = array();
|
|
||||||
|
|
||||||
protected $items = array();
|
|
||||||
protected $inputs = array();
|
|
||||||
protected $queriedContext = '';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return items stored in the bridge
|
* URI to the site the bridge is intended to be used for.
|
||||||
* @return mixed
|
*
|
||||||
*/
|
* 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} */
|
||||||
public function getItems(){
|
public function getItems(){
|
||||||
return $this->items;
|
return $this->items;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the input values for a given context. Existing values are
|
* Sets the input values for a given context.
|
||||||
* overwritten.
|
|
||||||
*
|
*
|
||||||
* @param array $inputs Associative array of inputs
|
* @param array $inputs Associative array of inputs
|
||||||
* @param string $context The context name
|
* @param string $queriedContext The context name
|
||||||
|
* @return void
|
||||||
*/
|
*/
|
||||||
protected function setInputs(array $inputs, $queriedContext){
|
protected function setInputs(array $inputs, $queriedContext){
|
||||||
// Import and assign all inputs to their context
|
// Import and assign all inputs to their context
|
||||||
@@ -103,9 +183,15 @@ abstract class BridgeAbstract implements BridgeInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defined datas with parameters depending choose bridge
|
* Set inputs for the bridge
|
||||||
* @param array array with expected bridge paramters
|
*
|
||||||
*/
|
* Returns errors and aborts execution if the provided input parameters are
|
||||||
|
* invalid.
|
||||||
|
*
|
||||||
|
* @param array List of input parameters. Each element in this list must
|
||||||
|
* relate to an item in {@see BridgeAbstract::PARAMETERS}
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
public function setDatas(array $inputs){
|
public function setDatas(array $inputs){
|
||||||
|
|
||||||
if(empty(static::PARAMETERS)) {
|
if(empty(static::PARAMETERS)) {
|
||||||
@@ -148,7 +234,7 @@ abstract class BridgeAbstract implements BridgeInterface {
|
|||||||
* Returns the value for the provided input
|
* Returns the value for the provided input
|
||||||
*
|
*
|
||||||
* @param string $input The input name
|
* @param string $input The input name
|
||||||
* @return mixed Returns the input value or null if the input is not defined
|
* @return mixed|null The input value or null if the input is not defined
|
||||||
*/
|
*/
|
||||||
protected function getInput($input){
|
protected function getInput($input){
|
||||||
if(!isset($this->inputs[$this->queriedContext][$input]['value'])) {
|
if(!isset($this->inputs[$this->queriedContext][$input]['value'])) {
|
||||||
@@ -157,32 +243,51 @@ abstract class BridgeAbstract implements BridgeInterface {
|
|||||||
return $this->inputs[$this->queriedContext][$input]['value'];
|
return $this->inputs[$this->queriedContext][$input]['value'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** {@inheritdoc} */
|
||||||
public function getDescription(){
|
public function getDescription(){
|
||||||
return static::DESCRIPTION;
|
return static::DESCRIPTION;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** {@inheritdoc} */
|
||||||
public function getMaintainer(){
|
public function getMaintainer(){
|
||||||
return static::MAINTAINER;
|
return static::MAINTAINER;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** {@inheritdoc} */
|
||||||
public function getName(){
|
public function getName(){
|
||||||
return static::NAME;
|
return static::NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** {@inheritdoc} */
|
||||||
public function getIcon(){
|
public function getIcon(){
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** {@inheritdoc} */
|
||||||
public function getParameters(){
|
public function getParameters(){
|
||||||
return static::PARAMETERS;
|
return static::PARAMETERS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** {@inheritdoc} */
|
||||||
public function getURI(){
|
public function getURI(){
|
||||||
return static::URI;
|
return static::URI;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** {@inheritdoc} */
|
||||||
public function getCacheTimeout(){
|
public function getCacheTimeout(){
|
||||||
return static::CACHE_TIMEOUT;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,31 @@
|
|||||||
<?php
|
<?php
|
||||||
final class BridgeCard {
|
/**
|
||||||
|
* 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) {
|
private static function buildFormatButtons($formats) {
|
||||||
$buttons = '';
|
$buttons = '';
|
||||||
|
|
||||||
@@ -16,6 +41,13 @@ final class BridgeCard {
|
|||||||
return $buttons;
|
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) {
|
private static function getFormHeader($bridgeName, $isHttps = false) {
|
||||||
$form = <<<EOD
|
$form = <<<EOD
|
||||||
<form method="GET" action="?">
|
<form method="GET" action="?">
|
||||||
@@ -31,13 +63,24 @@ This bridge is not fetching its content through a secure connection</div>';
|
|||||||
return $form;
|
return $form;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the form body for a bridge
|
||||||
|
*
|
||||||
|
* @param string $bridgeName The bridge name
|
||||||
|
* @param array $formats A list of supported formats
|
||||||
|
* @param bool $isActive Indicates if a bridge is enabled or not
|
||||||
|
* @param bool $isHttps Indicates if a bridge uses HTTPS or not
|
||||||
|
* @param string $parameterName Sets the bridge context for the current form
|
||||||
|
* @param array $parameters The bridge parameters
|
||||||
|
* @return string The form body
|
||||||
|
*/
|
||||||
private static function getForm($bridgeName,
|
private static function getForm($bridgeName,
|
||||||
$formats,
|
$formats,
|
||||||
$isActive = false,
|
$isActive = false,
|
||||||
$isHttps = false,
|
$isHttps = false,
|
||||||
$parameterName = '',
|
$parameterName = '',
|
||||||
$parameters = array()) {
|
$parameters = array()) {
|
||||||
$form = BridgeCard::getFormHeader($bridgeName, $isHttps);
|
$form = self::getFormHeader($bridgeName, $isHttps);
|
||||||
|
|
||||||
if(count($parameters) > 0) {
|
if(count($parameters) > 0) {
|
||||||
|
|
||||||
@@ -65,13 +108,13 @@ This bridge is not fetching its content through a secure connection</div>';
|
|||||||
. PHP_EOL;
|
. PHP_EOL;
|
||||||
|
|
||||||
if(!isset($inputEntry['type']) || $inputEntry['type'] === 'text') {
|
if(!isset($inputEntry['type']) || $inputEntry['type'] === 'text') {
|
||||||
$form .= BridgeCard::getTextInput($inputEntry, $idArg, $id);
|
$form .= self::getTextInput($inputEntry, $idArg, $id);
|
||||||
} elseif($inputEntry['type'] === 'number') {
|
} elseif($inputEntry['type'] === 'number') {
|
||||||
$form .= BridgeCard::getNumberInput($inputEntry, $idArg, $id);
|
$form .= self::getNumberInput($inputEntry, $idArg, $id);
|
||||||
} else if($inputEntry['type'] === 'list') {
|
} else if($inputEntry['type'] === 'list') {
|
||||||
$form .= BridgeCard::getListInput($inputEntry, $idArg, $id);
|
$form .= self::getListInput($inputEntry, $idArg, $id);
|
||||||
} elseif($inputEntry['type'] === 'checkbox') {
|
} elseif($inputEntry['type'] === 'checkbox') {
|
||||||
$form .= BridgeCard::getCheckboxInput($inputEntry, $idArg, $id);
|
$form .= self::getCheckboxInput($inputEntry, $idArg, $id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,7 +123,7 @@ This bridge is not fetching its content through a secure connection</div>';
|
|||||||
}
|
}
|
||||||
|
|
||||||
if($isActive) {
|
if($isActive) {
|
||||||
$form .= BridgeCard::buildFormatButtons($formats);
|
$form .= self::buildFormatButtons($formats);
|
||||||
} else {
|
} else {
|
||||||
$form .= '<span style="font-weight: bold;">Inactive</span>';
|
$form .= '<span style="font-weight: bold;">Inactive</span>';
|
||||||
}
|
}
|
||||||
@@ -88,6 +131,12 @@ This bridge is not fetching its content through a secure connection</div>';
|
|||||||
return $form . '</form>' . PHP_EOL;
|
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) {
|
private static function getInputAttributes($entry) {
|
||||||
$retVal = '';
|
$retVal = '';
|
||||||
|
|
||||||
@@ -103,9 +152,17 @@ This bridge is not fetching its content through a secure connection</div>';
|
|||||||
return $retVal;
|
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) {
|
private static function getTextInput($entry, $id, $name) {
|
||||||
return '<input '
|
return '<input '
|
||||||
. BridgeCard::getInputAttributes($entry)
|
. self::getInputAttributes($entry)
|
||||||
. ' id="'
|
. ' id="'
|
||||||
. $id
|
. $id
|
||||||
. '" type="text" value="'
|
. '" type="text" value="'
|
||||||
@@ -118,9 +175,17 @@ This bridge is not fetching its content through a secure connection</div>';
|
|||||||
. PHP_EOL;
|
. 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) {
|
private static function getNumberInput($entry, $id, $name) {
|
||||||
return '<input '
|
return '<input '
|
||||||
. BridgeCard::getInputAttributes($entry)
|
. self::getInputAttributes($entry)
|
||||||
. ' id="'
|
. ' id="'
|
||||||
. $id
|
. $id
|
||||||
. '" type="number" value="'
|
. '" type="number" value="'
|
||||||
@@ -133,9 +198,17 @@ This bridge is not fetching its content through a secure connection</div>';
|
|||||||
. PHP_EOL;
|
. 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) {
|
private static function getListInput($entry, $id, $name) {
|
||||||
$list = '<select '
|
$list = '<select '
|
||||||
. BridgeCard::getInputAttributes($entry)
|
. self::getInputAttributes($entry)
|
||||||
. ' id="'
|
. ' id="'
|
||||||
. $id
|
. $id
|
||||||
. '" name="'
|
. '" name="'
|
||||||
@@ -185,9 +258,17 @@ This bridge is not fetching its content through a secure connection</div>';
|
|||||||
return $list;
|
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) {
|
private static function getCheckboxInput($entry, $id, $name) {
|
||||||
return '<input '
|
return '<input '
|
||||||
. BridgeCard::getInputAttributes($entry)
|
. self::getInputAttributes($entry)
|
||||||
. ' id="'
|
. ' id="'
|
||||||
. $id
|
. $id
|
||||||
. '" type="checkbox" name="'
|
. '" type="checkbox" name="'
|
||||||
@@ -198,6 +279,14 @@ This bridge is not fetching its content through a secure connection</div>';
|
|||||||
. PHP_EOL;
|
. 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){
|
static function displayBridgeCard($bridgeName, $formats, $isActive = true){
|
||||||
|
|
||||||
$bridge = Bridge::create($bridgeName);
|
$bridge = Bridge::create($bridgeName);
|
||||||
@@ -240,7 +329,7 @@ CARD;
|
|||||||
if(count($parameters) === 0
|
if(count($parameters) === 0
|
||||||
|| count($parameters) === 1 && array_key_exists('global', $parameters)) {
|
|| count($parameters) === 1 && array_key_exists('global', $parameters)) {
|
||||||
|
|
||||||
$card .= BridgeCard::getForm($bridgeName, $formats, $isActive, $isHttps);
|
$card .= self::getForm($bridgeName, $formats, $isActive, $isHttps);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
@@ -254,7 +343,7 @@ CARD;
|
|||||||
if(!is_numeric($parameterName))
|
if(!is_numeric($parameterName))
|
||||||
$card .= '<h5>' . $parameterName . '</h5>' . PHP_EOL;
|
$card .= '<h5>' . $parameterName . '</h5>' . PHP_EOL;
|
||||||
|
|
||||||
$card .= BridgeCard::getForm($bridgeName, $formats, $isActive, $isHttps, $parameterName, $parameter);
|
$card .= self::getForm($bridgeName, $formats, $isActive, $isHttps, $parameterName, $parameter);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,58 @@
|
|||||||
<?php
|
<?php
|
||||||
interface BridgeInterface {
|
/**
|
||||||
|
* 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 {
|
||||||
/**
|
/**
|
||||||
* Collects data from the site
|
* Collects data from the site
|
||||||
*/
|
*/
|
||||||
@@ -61,4 +113,12 @@ interface BridgeInterface {
|
|||||||
* @return int Cache timeout
|
* @return int Cache timeout
|
||||||
*/
|
*/
|
||||||
public function getCacheTimeout();
|
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);
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,30 @@
|
|||||||
<?php
|
<?php
|
||||||
final class BridgeList {
|
/**
|
||||||
|
* 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() {
|
private static function getHead() {
|
||||||
return <<<EOD
|
return <<<EOD
|
||||||
<head>
|
<head>
|
||||||
@@ -22,20 +46,29 @@ final class BridgeList {
|
|||||||
EOD;
|
EOD;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function getBridges($whitelist, $showInactive, &$totalBridges, &$totalActiveBridges) {
|
/**
|
||||||
|
* Get the document body for all bridge cards
|
||||||
|
*
|
||||||
|
* @param bool $showInactive Inactive bridges are visible on the home page if
|
||||||
|
* enabled.
|
||||||
|
* @param int $totalBridges (ref) Returns the total number of bridges.
|
||||||
|
* @param int $totalActiveBridges (ref) Returns the number of active bridges.
|
||||||
|
* @return string The document body for all bridge cards.
|
||||||
|
*/
|
||||||
|
private static function getBridges($showInactive, &$totalBridges, &$totalActiveBridges) {
|
||||||
|
|
||||||
$body = '';
|
$body = '';
|
||||||
$totalActiveBridges = 0;
|
$totalActiveBridges = 0;
|
||||||
$inactiveBridges = '';
|
$inactiveBridges = '';
|
||||||
|
|
||||||
$bridgeList = Bridge::listBridges();
|
$bridgeList = Bridge::getBridgeNames();
|
||||||
$formats = Format::searchInformation();
|
$formats = Format::getFormatNames();
|
||||||
|
|
||||||
$totalBridges = count($bridgeList);
|
$totalBridges = count($bridgeList);
|
||||||
|
|
||||||
foreach($bridgeList as $bridgeName) {
|
foreach($bridgeList as $bridgeName) {
|
||||||
|
|
||||||
if(Bridge::isWhitelisted($whitelist, strtolower($bridgeName))) {
|
if(Bridge::isWhitelisted($bridgeName)) {
|
||||||
|
|
||||||
$body .= BridgeCard::displayBridgeCard($bridgeName, $formats);
|
$body .= BridgeCard::displayBridgeCard($bridgeName, $formats);
|
||||||
$totalActiveBridges++;
|
$totalActiveBridges++;
|
||||||
@@ -54,19 +87,24 @@ EOD;
|
|||||||
return $body;
|
return $body;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the document header
|
||||||
|
*
|
||||||
|
* @return string The document header
|
||||||
|
*/
|
||||||
private static function getHeader() {
|
private static function getHeader() {
|
||||||
$warning = '';
|
$warning = '';
|
||||||
|
|
||||||
if(defined('DEBUG') && DEBUG === true) {
|
if(Debug::isEnabled()) {
|
||||||
if(defined('DEBUG_INSECURE') && DEBUG_INSECURE === true) {
|
if(!Debug::isSecure()) {
|
||||||
$warning .= <<<EOD
|
$warning .= <<<EOD
|
||||||
<section class="critical-warning">Warning : Debug mode is active from any location,
|
<section class="critical-warning">Warning : Debug mode is active from any location,
|
||||||
make sure only you can access RSS-Bridge.</section>
|
make sure only you can access RSS-Bridge.</section>
|
||||||
EOD;
|
EOD;
|
||||||
} else {
|
} else {
|
||||||
$warning .= <<<EOD
|
$warning .= <<<EOD
|
||||||
<section class="warning">Warning : Debug mode is active from your IP address,
|
<section class="warning">Warning : Debug mode is active from your IP address,
|
||||||
your requests will bypass the cache.</section>
|
your requests will bypass the cache.</section>
|
||||||
EOD;
|
EOD;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,6 +118,11 @@ EOD;
|
|||||||
EOD;
|
EOD;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the searchbar
|
||||||
|
*
|
||||||
|
* @return string The searchbar
|
||||||
|
*/
|
||||||
private static function getSearchbar() {
|
private static function getSearchbar() {
|
||||||
$query = filter_input(INPUT_GET, 'q');
|
$query = filter_input(INPUT_GET, 'q');
|
||||||
|
|
||||||
@@ -93,6 +136,16 @@ EOD;
|
|||||||
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) {
|
private static function getFooter($totalBridges, $totalActiveBridges, $showInactive) {
|
||||||
$version = Configuration::getVersion();
|
$version = Configuration::getVersion();
|
||||||
|
|
||||||
@@ -131,7 +184,14 @@ EOD;
|
|||||||
EOD;
|
EOD;
|
||||||
}
|
}
|
||||||
|
|
||||||
static function create($whitelist, $showInactive = true) {
|
/**
|
||||||
|
* Create the entire home page
|
||||||
|
*
|
||||||
|
* @param bool $showInactive Inactive bridges are displayed on the home page,
|
||||||
|
* if enabled.
|
||||||
|
* @return string The home page
|
||||||
|
*/
|
||||||
|
static function create($showInactive = true) {
|
||||||
|
|
||||||
$totalBridges = 0;
|
$totalBridges = 0;
|
||||||
$totalActiveBridges = 0;
|
$totalActiveBridges = 0;
|
||||||
@@ -141,7 +201,7 @@ EOD;
|
|||||||
. '<body onload="search()">'
|
. '<body onload="search()">'
|
||||||
. BridgeList::getHeader()
|
. BridgeList::getHeader()
|
||||||
. BridgeList::getSearchbar()
|
. BridgeList::getSearchbar()
|
||||||
. BridgeList::getBridges($whitelist, $showInactive, $totalBridges, $totalActiveBridges)
|
. BridgeList::getBridges($showInactive, $totalBridges, $totalActiveBridges)
|
||||||
. BridgeList::getFooter($totalBridges, $totalActiveBridges, $showInactive)
|
. BridgeList::getFooter($totalBridges, $totalActiveBridges, $showInactive)
|
||||||
. '</body></html>';
|
. '</body></html>';
|
||||||
|
|
||||||
|
139
lib/Cache.php
139
lib/Cache.php
@@ -1,53 +1,140 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once(__DIR__ . '/CacheInterface.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');
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
class Cache {
|
class Cache {
|
||||||
|
|
||||||
static protected $dirCache;
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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(){
|
public function __construct(){
|
||||||
throw new \LogicException('Please use ' . __CLASS__ . '::create for new object.');
|
throw new \LogicException('Use ' . __CLASS__ . '::create($name) to create cache objects!');
|
||||||
}
|
}
|
||||||
|
|
||||||
static public function create($nameCache){
|
/**
|
||||||
if(!static::isValidNameCache($nameCache)) {
|
* Creates a new cache object from the working directory.
|
||||||
throw new \InvalidArgumentException('Name cache must be at least one
|
*
|
||||||
uppercase follow or not by alphanumeric or dash characters.');
|
* @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!');
|
||||||
}
|
}
|
||||||
|
|
||||||
$pathCache = self::getDir() . $nameCache . '.php';
|
$filePath = self::getWorkingDir() . $name . '.php';
|
||||||
|
|
||||||
if(!file_exists($pathCache)) {
|
if(!file_exists($filePath)) {
|
||||||
throw new \Exception('The cache you looking for does not exist.');
|
throw new \Exception('Cache file ' . $filePath . ' does not exist!');
|
||||||
}
|
}
|
||||||
|
|
||||||
require_once $pathCache;
|
require_once $filePath;
|
||||||
|
|
||||||
return new $nameCache();
|
if((new \ReflectionClass($name))->isInstantiable()) {
|
||||||
|
return new $name();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static public function setDir($dirCache){
|
/**
|
||||||
if(!is_string($dirCache)) {
|
* Sets the working directory.
|
||||||
throw new \InvalidArgumentException('Dir cache must be a string.');
|
*
|
||||||
|
* @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!');
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!file_exists($dirCache)) {
|
if(!file_exists($dir)) {
|
||||||
throw new \Exception('Dir cache does not exist.');
|
throw new \Exception('Working directory does not exist!');
|
||||||
}
|
}
|
||||||
|
|
||||||
self::$dirCache = $dirCache;
|
if(!is_dir($dir)) {
|
||||||
|
throw new \InvalidArgumentException('Working directory is not a directory!');
|
||||||
|
}
|
||||||
|
|
||||||
|
self::$workingDir = realpath($dir) . '/';
|
||||||
}
|
}
|
||||||
|
|
||||||
static public function getDir(){
|
/**
|
||||||
$dirCache = self::$dirCache;
|
* Returns the working directory.
|
||||||
|
* The working directory must be set with {@see Cache::setWorkingDir()}!
|
||||||
if(is_null($dirCache)) {
|
*
|
||||||
throw new \LogicException(__CLASS__ . ' class need to know cache path !');
|
* @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!');
|
||||||
}
|
}
|
||||||
|
|
||||||
return $dirCache;
|
return self::$workingDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
static public function isValidNameCache($nameCache){
|
/**
|
||||||
return preg_match('@^[A-Z][a-zA-Z0-9-]*$@', $nameCache);
|
* 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,50 @@
|
|||||||
<?php
|
<?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 {
|
interface CacheInterface {
|
||||||
|
/**
|
||||||
|
* Loads data from cache
|
||||||
|
*
|
||||||
|
* @return mixed The cache data
|
||||||
|
*/
|
||||||
public function loadData();
|
public function loadData();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores data to the cache
|
||||||
|
*
|
||||||
|
* @param mixed $datas The data to store
|
||||||
|
* @return self The cache object
|
||||||
|
*/
|
||||||
public function saveData($datas);
|
public function saveData($datas);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the timestamp for the curent cache file
|
||||||
|
*
|
||||||
|
* @return int Timestamp
|
||||||
|
*/
|
||||||
public function getTime();
|
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);
|
public function purgeCache($duration);
|
||||||
}
|
}
|
||||||
|
@@ -1,15 +1,86 @@
|
|||||||
<?php
|
<?php
|
||||||
class Configuration {
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
public static $VERSION = 'dev.2018-10-15';
|
/**
|
||||||
|
* Configuration module for RSS-Bridge.
|
||||||
|
*
|
||||||
|
* This class implements a configuration module for RSS-Bridge.
|
||||||
|
*/
|
||||||
|
final class Configuration {
|
||||||
|
|
||||||
public static $config = null;
|
/**
|
||||||
|
* 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 = '2019-01-13';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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() {
|
public static function verifyInstallation() {
|
||||||
|
|
||||||
// Check PHP version
|
// Check PHP version
|
||||||
if(version_compare(PHP_VERSION, PHP_VERSION_REQUIRED) === -1)
|
if(version_compare(PHP_VERSION, '5.6.0') === -1)
|
||||||
die('RSS-Bridge requires at least PHP version ' . PHP_VERSION_REQUIRED . '!');
|
die('RSS-Bridge requires at least PHP version 5.6.0!');
|
||||||
|
|
||||||
// extensions check
|
// extensions check
|
||||||
if(!extension_loaded('openssl'))
|
if(!extension_loaded('openssl'))
|
||||||
@@ -24,34 +95,61 @@ class Configuration {
|
|||||||
if(!extension_loaded('simplexml'))
|
if(!extension_loaded('simplexml'))
|
||||||
die('"simplexml" extension not loaded. Please check "php.ini"');
|
die('"simplexml" extension not loaded. Please check "php.ini"');
|
||||||
|
|
||||||
if(!extension_loaded('curl'))
|
// Allow RSS-Bridge to run without curl module in CLI mode without root certificates
|
||||||
|
if(!extension_loaded('curl') && !(php_sapi_name() === 'cli' && empty(ini_get('curl.cainfo'))))
|
||||||
die('"curl" extension not loaded. Please check "php.ini"');
|
die('"curl" extension not loaded. Please check "php.ini"');
|
||||||
|
|
||||||
if(!extension_loaded('json'))
|
if(!extension_loaded('json'))
|
||||||
die('"json" extension not loaded. Please check "php.ini"');
|
die('"json" extension not loaded. Please check "php.ini"');
|
||||||
|
|
||||||
// Check cache folder permissions (write permissions required)
|
// Check cache folder permissions (write permissions required)
|
||||||
if(!is_writable(CACHE_DIR))
|
if(!is_writable(PATH_CACHE))
|
||||||
die('RSS-Bridge does not have write permissions for ' . CACHE_DIR . '!');
|
die('RSS-Bridge does not have write permissions for ' . PATH_CACHE . '!');
|
||||||
|
|
||||||
// Check whitelist file permissions (only in DEBUG mode)
|
// Check whitelist file permissions
|
||||||
if(!file_exists(WHITELIST_FILE) && !is_writable(dirname(WHITELIST_FILE)))
|
if(!file_exists(WHITELIST) && !is_writable(dirname(WHITELIST)))
|
||||||
die('RSS-Bridge does not have write permissions for ' . WHITELIST_FILE . '!');
|
die('RSS-Bridge does not have write permissions for ' . WHITELIST . '!');
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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() {
|
public static function loadConfiguration() {
|
||||||
|
|
||||||
if(!file_exists('config.default.ini.php'))
|
if(!file_exists(PATH_ROOT . 'config.default.ini.php'))
|
||||||
die('The default configuration file "config.default.ini.php" is missing!');
|
die('The default configuration file "config.default.ini.php" is missing!');
|
||||||
|
|
||||||
Configuration::$config = parse_ini_file('config.default.ini.php', true, INI_SCANNER_TYPED);
|
Configuration::$config = parse_ini_file(PATH_ROOT . 'config.default.ini.php', true, INI_SCANNER_TYPED);
|
||||||
if(!Configuration::$config)
|
if(!Configuration::$config)
|
||||||
die('Error parsing config.default.ini.php');
|
die('Error parsing config.default.ini.php');
|
||||||
|
|
||||||
if(file_exists('config.ini.php')) {
|
if(file_exists(PATH_ROOT . 'config.ini.php')) {
|
||||||
// Replace default configuration with custom settings
|
// Replace default configuration with custom settings
|
||||||
foreach(parse_ini_file('config.ini.php', true, INI_SCANNER_TYPED) as $header => $section) {
|
foreach(parse_ini_file(PATH_ROOT . 'config.ini.php', true, INI_SCANNER_TYPED) as $header => $section) {
|
||||||
foreach($section as $key => $value) {
|
foreach($section as $key => $value) {
|
||||||
// Skip unknown sections and keys
|
// Skip unknown sections and keys
|
||||||
if(array_key_exists($header, Configuration::$config) && array_key_exists($key, Configuration::$config[$header])) {
|
if(array_key_exists($header, Configuration::$config) && array_key_exists($key, Configuration::$config[$header])) {
|
||||||
@@ -64,22 +162,27 @@ class Configuration {
|
|||||||
if(!is_string(self::getConfig('proxy', 'url')))
|
if(!is_string(self::getConfig('proxy', 'url')))
|
||||||
die('Parameter [proxy] => "url" is not a valid string! Please check "config.ini.php"!');
|
die('Parameter [proxy] => "url" is not a valid string! Please check "config.ini.php"!');
|
||||||
|
|
||||||
if(!empty(self::getConfig('proxy', 'url')))
|
if(!empty(self::getConfig('proxy', 'url'))) {
|
||||||
|
/** URL of the proxy server */
|
||||||
define('PROXY_URL', self::getConfig('proxy', 'url'));
|
define('PROXY_URL', self::getConfig('proxy', 'url'));
|
||||||
|
}
|
||||||
|
|
||||||
if(!is_bool(self::getConfig('proxy', 'by_bridge')))
|
if(!is_bool(self::getConfig('proxy', 'by_bridge')))
|
||||||
die('Parameter [proxy] => "by_bridge" is not a valid Boolean! Please check "config.ini.php"!');
|
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'));
|
define('PROXY_BYBRIDGE', self::getConfig('proxy', 'by_bridge'));
|
||||||
|
|
||||||
if(!is_string(self::getConfig('proxy', 'name')))
|
if(!is_string(self::getConfig('proxy', 'name')))
|
||||||
die('Parameter [proxy] => "name" is not a valid string! Please check "config.ini.php"!');
|
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'));
|
define('PROXY_NAME', self::getConfig('proxy', 'name'));
|
||||||
|
|
||||||
if(!is_bool(self::getConfig('cache', 'custom_timeout')))
|
if(!is_bool(self::getConfig('cache', 'custom_timeout')))
|
||||||
die('Parameter [cache] => "custom_timeout" is not a valid Boolean! Please check "config.ini.php"!');
|
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'));
|
define('CUSTOM_CACHE_TIMEOUT', self::getConfig('cache', 'custom_timeout'));
|
||||||
|
|
||||||
if(!is_bool(self::getConfig('authentication', 'enable')))
|
if(!is_bool(self::getConfig('authentication', 'enable')))
|
||||||
@@ -91,21 +194,41 @@ class Configuration {
|
|||||||
if(!is_string(self::getConfig('authentication', 'password')))
|
if(!is_string(self::getConfig('authentication', 'password')))
|
||||||
die('Parameter [authentication] => "password" is not a valid string! Please check "config.ini.php"!');
|
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"!');
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getConfig($category, $key) {
|
/**
|
||||||
|
* Returns the value of a parameter identified by section and key.
|
||||||
|
*
|
||||||
|
* @param string $section The section name.
|
||||||
|
* @param string $key The property name (key).
|
||||||
|
* @return mixed|null The parameter value.
|
||||||
|
*/
|
||||||
|
public static function getConfig($section, $key) {
|
||||||
|
|
||||||
if(array_key_exists($category, self::$config) && array_key_exists($key, self::$config[$category])) {
|
if(array_key_exists($section, self::$config) && array_key_exists($key, self::$config[$section])) {
|
||||||
return self::$config[$category][$key];
|
return self::$config[$section][$key];
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
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() {
|
public static function getVersion() {
|
||||||
|
|
||||||
$headFile = '.git/HEAD';
|
$headFile = PATH_ROOT . '.git/HEAD';
|
||||||
|
|
||||||
// '@' is used to mute open_basedir warning
|
// '@' is used to mute open_basedir warning
|
||||||
if(@is_readable($headFile)) {
|
if(@is_readable($headFile)) {
|
||||||
@@ -120,5 +243,4 @@ class Configuration {
|
|||||||
return Configuration::$VERSION;
|
return Configuration::$VERSION;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
121
lib/Debug.php
Normal file
121
lib/Debug.php
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
<?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);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,17 +1,28 @@
|
|||||||
<?php
|
<?php
|
||||||
class HttpException extends \Exception{}
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an URL that automatically populates a new issue on GitHub based
|
* Returns an URL that automatically populates a new issue on GitHub based
|
||||||
* on the information provided
|
* on the information provided
|
||||||
*
|
*
|
||||||
* @param $title string Sets the title of the issue
|
* @param string $title string Sets the title of the issue
|
||||||
* @param $body string Sets the body of the issue (GitHub markdown applies)
|
* @param string $body string Sets the body of the issue (GitHub markdown applies)
|
||||||
* @param $labels mixed (optional) Specifies labels to add to the issue
|
* @param string $labels mixed (optional) Specifies labels to add to the issue
|
||||||
* @param $maintainer string (optional) Specifies the maintainer for the issue.
|
* @param string $maintainer string (optional) Specifies the maintainer for the issue.
|
||||||
* The maintainer only applies if part of the development team!
|
* The maintainer only applies if part of the development team!
|
||||||
* @return string Returns a qualified URL to a new issue with populated conent.
|
* @return string|null A qualified URL to a new issue with populated conent or null.
|
||||||
* Returns null if title or body is null or empty
|
*
|
||||||
|
* @todo This function belongs inside a class
|
||||||
*/
|
*/
|
||||||
function buildGitHubIssueQuery($title, $body, $labels = null, $maintainer = null){
|
function buildGitHubIssueQuery($title, $body, $labels = null, $maintainer = null){
|
||||||
if(!isset($title) || !isset($body) || empty($title) || empty($body)) {
|
if(!isset($title) || !isset($body) || empty($title) || empty($body)) {
|
||||||
@@ -19,7 +30,8 @@ function buildGitHubIssueQuery($title, $body, $labels = null, $maintainer = null
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add title and body
|
// Add title and body
|
||||||
$uri = 'https://github.com/rss-bridge/rss-bridge/issues/new?title='
|
$uri = REPOSITORY
|
||||||
|
. 'issues/new?title='
|
||||||
. urlencode($title)
|
. urlencode($title)
|
||||||
. '&body='
|
. '&body='
|
||||||
. urlencode($body);
|
. urlencode($body);
|
||||||
@@ -48,10 +60,11 @@ function buildGitHubIssueQuery($title, $body, $labels = null, $maintainer = null
|
|||||||
/**
|
/**
|
||||||
* Returns the exception message as HTML string
|
* Returns the exception message as HTML string
|
||||||
*
|
*
|
||||||
* @param $e Exception The exception to show
|
* @param object $e Exception The exception to show
|
||||||
* @param $bridge object The bridge object
|
* @param object $bridge object The bridge object
|
||||||
* @return string Returns the exception as HTML string. Returns null if the
|
* @return string|null Returns the exception as HTML string or null.
|
||||||
* provided parameter are invalid
|
*
|
||||||
|
* @todo This function belongs inside a class
|
||||||
*/
|
*/
|
||||||
function buildBridgeException($e, $bridge){
|
function buildBridgeException($e, $bridge){
|
||||||
if(( !($e instanceof \Exception) && !($e instanceof \Error)) || !($bridge instanceof \BridgeInterface)) {
|
if(( !($e instanceof \Exception) && !($e instanceof \Error)) || !($bridge instanceof \BridgeInterface)) {
|
||||||
@@ -64,7 +77,7 @@ function buildBridgeException($e, $bridge){
|
|||||||
$body = 'Error message: `'
|
$body = 'Error message: `'
|
||||||
. $e->getMessage()
|
. $e->getMessage()
|
||||||
. "`\nQuery string: `"
|
. "`\nQuery string: `"
|
||||||
. $_SERVER['QUERY_STRING']
|
. (isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '')
|
||||||
. "`\nVersion: `"
|
. "`\nVersion: `"
|
||||||
. Configuration::getVersion()
|
. Configuration::getVersion()
|
||||||
. '`';
|
. '`';
|
||||||
@@ -86,10 +99,11 @@ EOD;
|
|||||||
/**
|
/**
|
||||||
* Returns the exception message as HTML string
|
* Returns the exception message as HTML string
|
||||||
*
|
*
|
||||||
* @param $e Exception The exception to show
|
* @param object $e Exception The exception to show
|
||||||
* @param $bridge object The bridge object
|
* @param object $bridge object The bridge object
|
||||||
* @return string Returns the exception as HTML string. Returns null if the
|
* @return string|null Returns the exception as HTML string or null.
|
||||||
* provided parameter are invalid
|
*
|
||||||
|
* @todo This function belongs inside a class
|
||||||
*/
|
*/
|
||||||
function buildTransformException($e, $bridge){
|
function buildTransformException($e, $bridge){
|
||||||
if(( !($e instanceof \Exception) && !($e instanceof \Error)) || !($bridge instanceof \BridgeInterface)) {
|
if(( !($e instanceof \Exception) && !($e instanceof \Error)) || !($bridge instanceof \BridgeInterface)) {
|
||||||
@@ -102,7 +116,8 @@ function buildTransformException($e, $bridge){
|
|||||||
$body = 'Error message: `'
|
$body = 'Error message: `'
|
||||||
. $e->getMessage()
|
. $e->getMessage()
|
||||||
. "`\nQuery string: `"
|
. "`\nQuery string: `"
|
||||||
. $_SERVER['QUERY_STRING'] . '`';
|
. (isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '')
|
||||||
|
. '`';
|
||||||
|
|
||||||
$link = buildGitHubIssueQuery($title, $body, 'bug report', $bridge->getMaintainer());
|
$link = buildGitHubIssueQuery($title, $body, 'bug report', $bridge->getMaintainer());
|
||||||
$header = buildHeader($e, $bridge);
|
$header = buildHeader($e, $bridge);
|
||||||
@@ -113,6 +128,15 @@ function buildTransformException($e, $bridge){
|
|||||||
return buildPage($title, $header, $section);
|
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){
|
function buildHeader($e, $bridge){
|
||||||
return <<<EOD
|
return <<<EOD
|
||||||
<header>
|
<header>
|
||||||
@@ -123,6 +147,17 @@ function buildHeader($e, $bridge){
|
|||||||
EOD;
|
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){
|
function buildSection($e, $bridge, $message, $link){
|
||||||
return <<<EOD
|
return <<<EOD
|
||||||
<section>
|
<section>
|
||||||
@@ -141,6 +176,16 @@ function buildSection($e, $bridge, $message, $link){
|
|||||||
EOD;
|
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){
|
function buildPage($title, $header, $section){
|
||||||
return <<<EOD
|
return <<<EOD
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
|
@@ -1,17 +1,86 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once(__DIR__ . '/BridgeInterface.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.
|
||||||
|
*/
|
||||||
abstract class FeedExpander extends BridgeAbstract {
|
abstract class FeedExpander extends BridgeAbstract {
|
||||||
|
|
||||||
private $name;
|
/** 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 $uri;
|
private $uri;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds the feed type during internal operations.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
private $feedType;
|
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){
|
public function collectExpandableDatas($url, $maxItems = -1){
|
||||||
if(empty($url)) {
|
if(empty($url)) {
|
||||||
returnServerError('There is no $url for this RSS expander');
|
returnServerError('There is no $url for this RSS expander');
|
||||||
}
|
}
|
||||||
|
|
||||||
debugMessage('Loading from ' . $url);
|
Debug::log('Loading from ' . $url);
|
||||||
|
|
||||||
/* Notice we do not use cache here on purpose:
|
/* Notice we do not use cache here on purpose:
|
||||||
* we want a fresh view of the RSS stream each time
|
* we want a fresh view of the RSS stream each time
|
||||||
@@ -20,34 +89,49 @@ abstract class FeedExpander extends BridgeAbstract {
|
|||||||
or returnServerError('Could not request ' . $url);
|
or returnServerError('Could not request ' . $url);
|
||||||
$rssContent = simplexml_load_string(trim($content));
|
$rssContent = simplexml_load_string(trim($content));
|
||||||
|
|
||||||
debugMessage('Detecting feed format/version');
|
Debug::log('Detecting feed format/version');
|
||||||
switch(true) {
|
switch(true) {
|
||||||
case isset($rssContent->item[0]):
|
case isset($rssContent->item[0]):
|
||||||
debugMessage('Detected RSS 1.0 format');
|
Debug::log('Detected RSS 1.0 format');
|
||||||
$this->feedType = 'RSS_1_0';
|
$this->feedType = self::FEED_TYPE_RSS_1_0;
|
||||||
break;
|
break;
|
||||||
case isset($rssContent->channel[0]):
|
case isset($rssContent->channel[0]):
|
||||||
debugMessage('Detected RSS 0.9x or 2.0 format');
|
Debug::log('Detected RSS 0.9x or 2.0 format');
|
||||||
$this->feedType = 'RSS_2_0';
|
$this->feedType = self::FEED_TYPE_RSS_2_0;
|
||||||
break;
|
break;
|
||||||
case isset($rssContent->entry[0]):
|
case isset($rssContent->entry[0]):
|
||||||
debugMessage('Detected ATOM format');
|
Debug::log('Detected ATOM format');
|
||||||
$this->feedType = 'ATOM_1_0';
|
$this->feedType = self::FEED_TYPE_ATOM_1_0;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
debugMessage('Unknown feed format/version');
|
Debug::log('Unknown feed format/version');
|
||||||
returnServerError('The feed format is unknown!');
|
returnServerError('The feed format is unknown!');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
debugMessage('Calling function "collect_' . $this->feedType . '_data"');
|
Debug::log('Calling function "collect_' . $this->feedType . '_data"');
|
||||||
$this->{'collect_' . $this->feedType . '_data'}($rssContent, $maxItems);
|
$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){
|
protected function collect_RSS_1_0_data($rssContent, $maxItems){
|
||||||
$this->load_RSS_2_0_feed_data($rssContent->channel[0]);
|
$this->load_RSS_2_0_feed_data($rssContent->channel[0]);
|
||||||
foreach($rssContent->item as $item) {
|
foreach($rssContent->item as $item) {
|
||||||
debugMessage('parsing item ' . var_export($item, true));
|
Debug::log('parsing item ' . var_export($item, true));
|
||||||
$tmp_item = $this->parseItem($item);
|
$tmp_item = $this->parseItem($item);
|
||||||
if (!empty($tmp_item)) {
|
if (!empty($tmp_item)) {
|
||||||
$this->items[] = $tmp_item;
|
$this->items[] = $tmp_item;
|
||||||
@@ -56,15 +140,28 @@ 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){
|
protected function collect_RSS_2_0_data($rssContent, $maxItems){
|
||||||
$rssContent = $rssContent->channel[0];
|
$rssContent = $rssContent->channel[0];
|
||||||
debugMessage('RSS content is ===========\n'
|
Debug::log('RSS content is ===========\n'
|
||||||
. var_export($rssContent, true)
|
. var_export($rssContent, true)
|
||||||
. '===========');
|
. '===========');
|
||||||
|
|
||||||
$this->load_RSS_2_0_feed_data($rssContent);
|
$this->load_RSS_2_0_feed_data($rssContent);
|
||||||
foreach($rssContent->item as $item) {
|
foreach($rssContent->item as $item) {
|
||||||
debugMessage('parsing item ' . var_export($item, true));
|
Debug::log('parsing item ' . var_export($item, true));
|
||||||
$tmp_item = $this->parseItem($item);
|
$tmp_item = $this->parseItem($item);
|
||||||
if (!empty($tmp_item)) {
|
if (!empty($tmp_item)) {
|
||||||
$this->items[] = $tmp_item;
|
$this->items[] = $tmp_item;
|
||||||
@@ -73,10 +170,23 @@ 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){
|
protected function collect_ATOM_1_0_data($content, $maxItems){
|
||||||
$this->load_ATOM_feed_data($content);
|
$this->load_ATOM_feed_data($content);
|
||||||
foreach($content->entry as $item) {
|
foreach($content->entry as $item) {
|
||||||
debugMessage('parsing item ' . var_export($item, true));
|
Debug::log('parsing item ' . var_export($item, true));
|
||||||
$tmp_item = $this->parseItem($item);
|
$tmp_item = $this->parseItem($item);
|
||||||
if (!empty($tmp_item)) {
|
if (!empty($tmp_item)) {
|
||||||
$this->items[] = $tmp_item;
|
$this->items[] = $tmp_item;
|
||||||
@@ -85,18 +195,37 @@ 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){
|
protected function RSS_2_0_time_to_timestamp($item){
|
||||||
return DateTime::createFromFormat('D, d M Y H:i:s e', $item->pubDate)->getTimestamp();
|
return DateTime::createFromFormat('D, d M Y H:i:s e', $item->pubDate)->getTimestamp();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO set title, link, description, language, and so on
|
/**
|
||||||
|
* Load RSS 2.0 feed data into RSS-Bridge
|
||||||
|
*
|
||||||
|
* @param object $rssContent The RSS content
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @todo set title, link, description, language, and so on
|
||||||
|
*/
|
||||||
protected function load_RSS_2_0_feed_data($rssContent){
|
protected function load_RSS_2_0_feed_data($rssContent){
|
||||||
$this->name = trim((string)$rssContent->title);
|
$this->title = trim((string)$rssContent->title);
|
||||||
$this->uri = trim((string)$rssContent->link);
|
$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){
|
protected function load_ATOM_feed_data($content){
|
||||||
$this->name = (string)$content->title;
|
$this->title = (string)$content->title;
|
||||||
|
|
||||||
// Find best link (only one, or first of 'alternate')
|
// Find best link (only one, or first of 'alternate')
|
||||||
if(!isset($content->link)) {
|
if(!isset($content->link)) {
|
||||||
@@ -114,6 +243,16 @@ 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){
|
protected function parseATOMItem($feedItem){
|
||||||
// Some ATOM entries also contain RSS 2.0 fields
|
// Some ATOM entries also contain RSS 2.0 fields
|
||||||
$item = $this->parseRSS_2_0_Item($feedItem);
|
$item = $this->parseRSS_2_0_Item($feedItem);
|
||||||
@@ -139,6 +278,16 @@ abstract class FeedExpander extends BridgeAbstract {
|
|||||||
return $item;
|
return $item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the contents of a single RSS 0.91 feed item into a RSS-Bridge item
|
||||||
|
* for further transformation.
|
||||||
|
*
|
||||||
|
* @param object $feedItem A single feed item
|
||||||
|
* @return object The RSS-Bridge item
|
||||||
|
*
|
||||||
|
* @todo To reduce confusion, the RSS-Bridge item should maybe have a class
|
||||||
|
* of its own?
|
||||||
|
*/
|
||||||
protected function parseRSS_0_9_1_Item($feedItem){
|
protected function parseRSS_0_9_1_Item($feedItem){
|
||||||
$item = array();
|
$item = array();
|
||||||
if(isset($feedItem->link)) $item['uri'] = (string)$feedItem->link;
|
if(isset($feedItem->link)) $item['uri'] = (string)$feedItem->link;
|
||||||
@@ -150,6 +299,16 @@ abstract class FeedExpander extends BridgeAbstract {
|
|||||||
return $item;
|
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){
|
protected function parseRSS_1_0_Item($feedItem){
|
||||||
// 1.0 adds optional elements around the 0.91 standard
|
// 1.0 adds optional elements around the 0.91 standard
|
||||||
$item = $this->parseRSS_0_9_1_Item($feedItem);
|
$item = $this->parseRSS_0_9_1_Item($feedItem);
|
||||||
@@ -164,6 +323,16 @@ abstract class FeedExpander extends BridgeAbstract {
|
|||||||
return $item;
|
return $item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the contents of a single RSS 2.0 feed item into a RSS-Bridge item
|
||||||
|
* for further transformation.
|
||||||
|
*
|
||||||
|
* @param object $feedItem A single feed item
|
||||||
|
* @return object The RSS-Bridge item
|
||||||
|
*
|
||||||
|
* @todo To reduce confusion, the RSS-Bridge item should maybe have a class
|
||||||
|
* of its own?
|
||||||
|
*/
|
||||||
protected function parseRSS_2_0_Item($feedItem){
|
protected function parseRSS_2_0_Item($feedItem){
|
||||||
// Primary data is compatible to 0.91 with some additional data
|
// Primary data is compatible to 0.91 with some additional data
|
||||||
$item = $this->parseRSS_0_9_1_Item($feedItem);
|
$item = $this->parseRSS_0_9_1_Item($feedItem);
|
||||||
@@ -211,33 +380,38 @@ abstract class FeedExpander extends BridgeAbstract {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method should return, from a source RSS item given by lastRSS, one of our Items objects
|
* Parse the contents of a single feed item, depending on the current feed
|
||||||
* @param $item the input rss item
|
* type, into a RSS-Bridge item.
|
||||||
* @return a RSS-Bridge Item, with (hopefully) the whole content)
|
*
|
||||||
|
* @param object $item The current feed item
|
||||||
|
* @return object A RSS-Bridge item, with (hopefully) the whole content
|
||||||
*/
|
*/
|
||||||
protected function parseItem($item){
|
protected function parseItem($item){
|
||||||
switch($this->feedType) {
|
switch($this->feedType) {
|
||||||
case 'RSS_1_0':
|
case self::FEED_TYPE_RSS_1_0:
|
||||||
return $this->parseRSS_1_0_Item($item);
|
return $this->parseRSS_1_0_Item($item);
|
||||||
break;
|
break;
|
||||||
case 'RSS_2_0':
|
case self::FEED_TYPE_RSS_2_0:
|
||||||
return $this->parseRSS_2_0_Item($item);
|
return $this->parseRSS_2_0_Item($item);
|
||||||
break;
|
break;
|
||||||
case 'ATOM_1_0':
|
case self::FEED_TYPE_ATOM_1_0:
|
||||||
return $this->parseATOMItem($item);
|
return $this->parseATOMItem($item);
|
||||||
break;
|
break;
|
||||||
default: returnClientError('Unknown version ' . $this->getInput('version') . '!');
|
default: returnClientError('Unknown version ' . $this->getInput('version') . '!');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** {@inheritdoc} */
|
||||||
public function getURI(){
|
public function getURI(){
|
||||||
return !empty($this->uri) ? $this->uri : parent::getURI();
|
return !empty($this->uri) ? $this->uri : parent::getURI();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** {@inheritdoc} */
|
||||||
public function getName(){
|
public function getName(){
|
||||||
return !empty($this->name) ? $this->name : parent::getName();
|
return !empty($this->title) ? $this->title : parent::getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** {@inheritdoc} */
|
||||||
public function getIcon(){
|
public function getIcon(){
|
||||||
return !empty($this->icon) ? $this->icon : parent::getIcon();
|
return !empty($this->icon) ? $this->icon : parent::getIcon();
|
||||||
}
|
}
|
||||||
|
485
lib/FeedItem.php
Normal file
485
lib/FeedItem.php
Normal file
@@ -0,0 +1,485 @@
|
|||||||
|
<?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
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a simple feed item for transformation into various feed formats.
|
||||||
|
*
|
||||||
|
* This class represents a feed item. A feed item is an entity that can be
|
||||||
|
* transformed into various feed formats. It holds a set of pre-defined
|
||||||
|
* properties:
|
||||||
|
*
|
||||||
|
* - **URI**: URI to the full article (i.e. "https://...")
|
||||||
|
* - **Title**: The title
|
||||||
|
* - **Timestamp**: A timestamp of when the item was first released
|
||||||
|
* - **Author**: Name of the author
|
||||||
|
* - **Content**: Body of the feed, as text or HTML
|
||||||
|
* - **Enclosures**: A list of links to media objects (images, videos, etc...)
|
||||||
|
* - **Categories**: A list of category names or tags to categorize the item
|
||||||
|
*
|
||||||
|
* _Note_: A feed item can have any number of additional parameters, all of which
|
||||||
|
* may or may not be transformed to the selected output format.
|
||||||
|
*
|
||||||
|
* _Remarks_: This class supports legacy items via {@see FeedItem::__construct()}
|
||||||
|
* (i.e. `$feedItem = \FeedItem($item);`). Support for legacy items may be removed
|
||||||
|
* in future versions of RSS-Bridge.
|
||||||
|
*/
|
||||||
|
class FeedItem {
|
||||||
|
/** @var string|null URI to the full article */
|
||||||
|
protected $uri = null;
|
||||||
|
|
||||||
|
/** @var string|null Title of the item */
|
||||||
|
protected $title = null;
|
||||||
|
|
||||||
|
/** @var int|null Timestamp of when the item was first released */
|
||||||
|
protected $timestamp = null;
|
||||||
|
|
||||||
|
/** @var string|null Name of the author */
|
||||||
|
protected $author = null;
|
||||||
|
|
||||||
|
/** @var string|null Body of the feed */
|
||||||
|
protected $content = null;
|
||||||
|
|
||||||
|
/** @var array List of links to media objects */
|
||||||
|
protected $enclosures = array();
|
||||||
|
|
||||||
|
/** @var array List of category names or tags */
|
||||||
|
protected $categories = array();
|
||||||
|
|
||||||
|
/** @var array Associative list of additional parameters */
|
||||||
|
protected $misc = array(); // Custom parameters
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create object from legacy item.
|
||||||
|
*
|
||||||
|
* The provided array must be an associative array of key-value-pairs, where
|
||||||
|
* keys may correspond to any of the properties of this class.
|
||||||
|
*
|
||||||
|
* Example use:
|
||||||
|
*
|
||||||
|
* ```PHP
|
||||||
|
* <?php
|
||||||
|
* $item = array();
|
||||||
|
*
|
||||||
|
* $item['uri'] = 'https://www.github.com/rss-bridge/rss-bridge/';
|
||||||
|
* $item['title'] = 'Title';
|
||||||
|
* $item['timestamp'] = strtotime('now');
|
||||||
|
* $item['autor'] = 'Unknown author';
|
||||||
|
* $item['content'] = 'Hello World!';
|
||||||
|
* $item['enclosures'] = array('https://github.com/favicon.ico');
|
||||||
|
* $item['categories'] = array('php', 'rss-bridge', 'awesome');
|
||||||
|
*
|
||||||
|
* $feedItem = new \FeedItem($item);
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* The result of the code above is the same as the code below:
|
||||||
|
*
|
||||||
|
* ```PHP
|
||||||
|
* <?php
|
||||||
|
* $feedItem = \FeedItem();
|
||||||
|
*
|
||||||
|
* $feedItem->uri = 'https://www.github.com/rss-bridge/rss-bridge/';
|
||||||
|
* $feedItem->title = 'Title';
|
||||||
|
* $feedItem->timestamp = strtotime('now');
|
||||||
|
* $feedItem->autor = 'Unknown author';
|
||||||
|
* $feedItem->content = 'Hello World!';
|
||||||
|
* $feedItem->enclosures = array('https://github.com/favicon.ico');
|
||||||
|
* $feedItem->categories = array('php', 'rss-bridge', 'awesome');
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param array $item (optional) A legacy item (empty: no legacy support).
|
||||||
|
* @return object A new object of this class
|
||||||
|
*/
|
||||||
|
public function __construct($item = array()) {
|
||||||
|
if(!is_array($item))
|
||||||
|
Debug::log('Item must be an array!');
|
||||||
|
|
||||||
|
foreach($item as $key => $value) {
|
||||||
|
$this->__set($key, $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current URI.
|
||||||
|
*
|
||||||
|
* Use {@see FeedItem::setURI()} to set the URI.
|
||||||
|
*
|
||||||
|
* @return string|null The URI or null if it hasn't been set.
|
||||||
|
*/
|
||||||
|
public function getURI() {
|
||||||
|
return $this->uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set URI to the full article.
|
||||||
|
*
|
||||||
|
* Use {@see FeedItem::getURI()} to get the URI.
|
||||||
|
*
|
||||||
|
* _Note_: Removes whitespace from the beginning and end of the URI.
|
||||||
|
*
|
||||||
|
* _Remarks_: Uses the attribute "href" or "src" if the provided URI is an
|
||||||
|
* object of simple_html_dom_node.
|
||||||
|
*
|
||||||
|
* @param object|string $uri URI to the full article.
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function setURI($uri) {
|
||||||
|
$this->uri = null; // Clear previous data
|
||||||
|
|
||||||
|
if($uri instanceof simple_html_dom_node) {
|
||||||
|
if($uri->hasAttribute('href')) { // Anchor
|
||||||
|
$uri = $uri->href;
|
||||||
|
} elseif($uri->hasAttribute('src')) { // Image
|
||||||
|
$uri = $uri->src;
|
||||||
|
} else {
|
||||||
|
Debug::log('The item provided as URI is unknown!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!is_string($uri)) {
|
||||||
|
Debug::log('URI must be a string!');
|
||||||
|
} elseif(!filter_var(
|
||||||
|
$uri,
|
||||||
|
FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED)) {
|
||||||
|
Debug::log('URI must include a scheme, host and path!');
|
||||||
|
} else {
|
||||||
|
$scheme = parse_url($uri, PHP_URL_SCHEME);
|
||||||
|
|
||||||
|
if($scheme !== 'http' && $scheme !== 'https') {
|
||||||
|
Debug::log('URI scheme must be "http" or "https"!');
|
||||||
|
} else {
|
||||||
|
$this->uri = trim($uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current title.
|
||||||
|
*
|
||||||
|
* Use {@see FeedItem::setTitle()} to set the title.
|
||||||
|
*
|
||||||
|
* @return string|null The current title or null if it hasn't been set.
|
||||||
|
*/
|
||||||
|
public function getTitle() {
|
||||||
|
return $this->title;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set title.
|
||||||
|
*
|
||||||
|
* Use {@see FeedItem::getTitle()} to get the title.
|
||||||
|
*
|
||||||
|
* _Note_: Removes whitespace from beginning and end of the title.
|
||||||
|
*
|
||||||
|
* @param string $title The title
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function setTitle($title) {
|
||||||
|
$this->title = null; // Clear previous data
|
||||||
|
|
||||||
|
if(!is_string($title)) {
|
||||||
|
Debug::log('Title must be a string!');
|
||||||
|
} else {
|
||||||
|
$this->title = trim($title);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current timestamp.
|
||||||
|
*
|
||||||
|
* Use {@see FeedItem::setTimestamp()} to set the timestamp.
|
||||||
|
*
|
||||||
|
* @return int|null The current timestamp or null if it hasn't been set.
|
||||||
|
*/
|
||||||
|
public function getTimestamp() {
|
||||||
|
return $this->timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set timestamp of first release.
|
||||||
|
*
|
||||||
|
* _Note_: The timestamp should represent the number of seconds since
|
||||||
|
* January 1 1970 00:00:00 GMT (Unix time).
|
||||||
|
*
|
||||||
|
* _Remarks_: If the provided timestamp is a string (not numeric), this
|
||||||
|
* function automatically attempts to parse the string using
|
||||||
|
* [strtotime](http://php.net/manual/en/function.strtotime.php)
|
||||||
|
*
|
||||||
|
* @link http://php.net/manual/en/function.strtotime.php strtotime (PHP)
|
||||||
|
* @link https://en.wikipedia.org/wiki/Unix_time Unix time (Wikipedia)
|
||||||
|
*
|
||||||
|
* @param string|int $timestamp A timestamp of when the item was first released
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function setTimestamp($timestamp) {
|
||||||
|
$this->timestamp = null; // Clear previous data
|
||||||
|
|
||||||
|
if(!is_numeric($timestamp)
|
||||||
|
&& !$timestamp = strtotime($timestamp)) {
|
||||||
|
Debug::log('Unable to parse timestamp!');
|
||||||
|
}
|
||||||
|
|
||||||
|
if($timestamp <= 0) {
|
||||||
|
Debug::log('Timestamp must be greater than zero!');
|
||||||
|
} else {
|
||||||
|
$this->timestamp = $timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current author name.
|
||||||
|
*
|
||||||
|
* Use {@see FeedItem::setAuthor()} to set the author.
|
||||||
|
*
|
||||||
|
* @return string|null The author or null if it hasn't been set.
|
||||||
|
*/
|
||||||
|
public function getAuthor() {
|
||||||
|
return $this->author;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the author name.
|
||||||
|
*
|
||||||
|
* Use {@see FeedItem::getAuthor()} to get the author.
|
||||||
|
*
|
||||||
|
* @param string $author The author name.
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function setAuthor($author) {
|
||||||
|
$this->author = null; // Clear previous data
|
||||||
|
|
||||||
|
if(!is_string($author)) {
|
||||||
|
Debug::log('Author must be a string!');
|
||||||
|
} else {
|
||||||
|
$this->author = $author;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get item content.
|
||||||
|
*
|
||||||
|
* Use {@see FeedItem::setContent()} to set the item content.
|
||||||
|
*
|
||||||
|
* @return string|null The item content or null if it hasn't been set.
|
||||||
|
*/
|
||||||
|
public function getContent() {
|
||||||
|
return $this->content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set item content.
|
||||||
|
*
|
||||||
|
* Note: This function casts objects of type simple_html_dom and
|
||||||
|
* simple_html_dom_node to string.
|
||||||
|
*
|
||||||
|
* Use {@see FeedItem::getContent()} to get the current item content.
|
||||||
|
*
|
||||||
|
* @param string|object $content The item content as text or simple_html_dom
|
||||||
|
* object.
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function setContent($content) {
|
||||||
|
$this->content = null; // Clear previous data
|
||||||
|
|
||||||
|
if($content instanceof simple_html_dom
|
||||||
|
|| $content instanceof simple_html_dom_node) {
|
||||||
|
$content = (string)$content;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!is_string($content)) {
|
||||||
|
Debug::log('Content must be a string!');
|
||||||
|
} else {
|
||||||
|
$this->content = $content;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get item enclosures.
|
||||||
|
*
|
||||||
|
* Use {@see FeedItem::setEnclosures()} to set feed enclosures.
|
||||||
|
*
|
||||||
|
* @return array Enclosures as array of enclosure URIs.
|
||||||
|
*/
|
||||||
|
public function getEnclosures() {
|
||||||
|
return $this->enclosures;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set item enclosures.
|
||||||
|
*
|
||||||
|
* Use {@see FeedItem::getEnclosures()} to get the current item enclosures.
|
||||||
|
*
|
||||||
|
* @param array $enclosures Array of enclosures, where each element links to
|
||||||
|
* one enclosure.
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function setEnclosures($enclosures) {
|
||||||
|
$this->enclosures = array(); // Clear previous data
|
||||||
|
|
||||||
|
if(!is_array($enclosures)) {
|
||||||
|
Debug::log('Enclosures must be an array!');
|
||||||
|
} else {
|
||||||
|
foreach($enclosures as $enclosure) {
|
||||||
|
if(!filter_var(
|
||||||
|
$enclosure,
|
||||||
|
FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED)) {
|
||||||
|
Debug::log('Each enclosure must contain a scheme, host and path!');
|
||||||
|
} else {
|
||||||
|
$this->enclosures[] = $enclosure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get item categories.
|
||||||
|
*
|
||||||
|
* Use {@see FeedItem::setCategories()} to set item categories.
|
||||||
|
*
|
||||||
|
* @param array The item categories.
|
||||||
|
*/
|
||||||
|
public function getCategories() {
|
||||||
|
return $this->categories;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set item categories.
|
||||||
|
*
|
||||||
|
* Use {@see FeedItem::getCategories()} to get the current item categories.
|
||||||
|
*
|
||||||
|
* @param array $categories Array of categories, where each element defines
|
||||||
|
* a single category name.
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function setCategories($categories) {
|
||||||
|
$this->categories = array(); // Clear previous data
|
||||||
|
|
||||||
|
if(!is_array($categories)) {
|
||||||
|
Debug::log('Categories must be an array!');
|
||||||
|
} else {
|
||||||
|
foreach($categories as $category) {
|
||||||
|
if(!is_string($category)) {
|
||||||
|
Debug::log('Category must be a string!');
|
||||||
|
} else {
|
||||||
|
$this->categories[] = $category;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add miscellaneous elements to the item.
|
||||||
|
*
|
||||||
|
* @param string $key Name of the element.
|
||||||
|
* @param mixed $value Value of the element.
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function addMisc($key, $value) {
|
||||||
|
|
||||||
|
if(!is_string($key)) {
|
||||||
|
Debug::log('Key must be a string!');
|
||||||
|
} elseif(in_array($key, get_object_vars($this))) {
|
||||||
|
Debug::log('Key must be unique!');
|
||||||
|
} else {
|
||||||
|
$this->misc[$key] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform current object to array
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function toArray() {
|
||||||
|
return array_merge(
|
||||||
|
array(
|
||||||
|
'uri' => $this->uri,
|
||||||
|
'title' => $this->title,
|
||||||
|
'timestamp' => $this->timestamp,
|
||||||
|
'author' => $this->author,
|
||||||
|
'content' => $this->content,
|
||||||
|
'enclosures' => $this->enclosures,
|
||||||
|
'categories' => $this->categories,
|
||||||
|
), $this->misc
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set item property
|
||||||
|
*
|
||||||
|
* Allows simple assignment to parameters. This method is slower, but easier
|
||||||
|
* to implement in some cases:
|
||||||
|
*
|
||||||
|
* ```PHP
|
||||||
|
* $item = new \FeedItem();
|
||||||
|
* $item->content = 'Hello World!';
|
||||||
|
* $item->my_id = 42;
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param string $name Property name
|
||||||
|
* @param mixed $value Property value
|
||||||
|
*/
|
||||||
|
function __set($name, $value) {
|
||||||
|
switch($name) {
|
||||||
|
case 'uri': $this->setURI($value); break;
|
||||||
|
case 'title': $this->setTitle($value); break;
|
||||||
|
case 'timestamp': $this->setTimestamp($value); break;
|
||||||
|
case 'author': $this->setAuthor($value); break;
|
||||||
|
case 'content': $this->setContent($value); break;
|
||||||
|
case 'enclosures': $this->setEnclosures($value); break;
|
||||||
|
case 'categories': $this->setCategories($value); break;
|
||||||
|
default: $this->addMisc($name, $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get item property
|
||||||
|
*
|
||||||
|
* Allows simple assignment to parameters. This method is slower, but easier
|
||||||
|
* to implement in some cases.
|
||||||
|
*
|
||||||
|
* @param string $name Property name
|
||||||
|
* @return mixed Property value
|
||||||
|
*/
|
||||||
|
function __get($name) {
|
||||||
|
switch($name) {
|
||||||
|
case 'uri': return $this->getURI();
|
||||||
|
case 'title': return $this->getTitle();
|
||||||
|
case 'timestamp': return $this->getTimestamp();
|
||||||
|
case 'author': return $this->getAuthor();
|
||||||
|
case 'content': return $this->getContent();
|
||||||
|
case 'enclosures': return $this->getEnclosures();
|
||||||
|
case 'categories': return $this->getCategories();
|
||||||
|
default:
|
||||||
|
if(array_key_exists($name, $this->misc))
|
||||||
|
return $this->misc[$name];
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
181
lib/Format.php
181
lib/Format.php
@@ -1,73 +1,166 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once(__DIR__ . '/FormatInterface.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');
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
class Format {
|
class Format {
|
||||||
|
|
||||||
static protected $dirFormat;
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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(){
|
public function __construct(){
|
||||||
throw new \LogicException('Please use ' . __CLASS__ . '::create for new object.');
|
throw new \LogicException('Use ' . __CLASS__ . '::create($name) to create cache objects!');
|
||||||
}
|
}
|
||||||
|
|
||||||
static public function create($nameFormat){
|
/**
|
||||||
if(!preg_match('@^[A-Z][a-zA-Z]*$@', $nameFormat)) {
|
* Creates a new format object from the working directory.
|
||||||
throw new \InvalidArgumentException('Name format must be at least
|
*
|
||||||
one uppercase follow or not by alphabetic characters.');
|
* @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!');
|
||||||
}
|
}
|
||||||
|
|
||||||
$nameFormat = $nameFormat . 'Format';
|
$name = $name . 'Format';
|
||||||
$pathFormat = self::getDir() . $nameFormat . '.php';
|
$pathFormat = self::getWorkingDir() . $name . '.php';
|
||||||
|
|
||||||
if(!file_exists($pathFormat)) {
|
if(!file_exists($pathFormat)) {
|
||||||
throw new \Exception('The format you looking for does not exist.');
|
throw new \Exception('Format file ' . $filePath . ' does not exist!');
|
||||||
}
|
}
|
||||||
|
|
||||||
require_once $pathFormat;
|
require_once $pathFormat;
|
||||||
|
|
||||||
return new $nameFormat();
|
if((new \ReflectionClass($name))->isInstantiable()) {
|
||||||
}
|
return new $name();
|
||||||
|
|
||||||
static public function setDir($dirFormat){
|
|
||||||
if(!is_string($dirFormat)) {
|
|
||||||
throw new \InvalidArgumentException('Dir format must be a string.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!file_exists($dirFormat)) {
|
return false;
|
||||||
throw new \Exception('Dir format does not exist.');
|
|
||||||
}
|
|
||||||
|
|
||||||
self::$dirFormat = $dirFormat;
|
|
||||||
}
|
|
||||||
|
|
||||||
static public function getDir(){
|
|
||||||
$dirFormat = self::$dirFormat;
|
|
||||||
|
|
||||||
if(is_null($dirFormat)) {
|
|
||||||
throw new \LogicException(__CLASS__ . ' class need to know format path !');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $dirFormat;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read format dir and catch informations about each format depending annotation
|
* Sets the working directory.
|
||||||
* @return array Informations about each format
|
*
|
||||||
*/
|
* @param string $dir Path to a directory containing cache classes
|
||||||
static public function searchInformation(){
|
* @throws \InvalidArgumentException if $dir is not a string.
|
||||||
$pathDirFormat = self::getDir();
|
* @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;
|
||||||
|
|
||||||
$listFormat = array();
|
if(!is_string($dir)) {
|
||||||
|
throw new \InvalidArgumentException('Dir format must be a string.');
|
||||||
|
}
|
||||||
|
|
||||||
$searchCommonPattern = array('name');
|
if(!file_exists($dir)) {
|
||||||
|
throw new \Exception('Working directory does not exist!');
|
||||||
|
}
|
||||||
|
|
||||||
$dirFiles = scandir($pathDirFormat);
|
if(!is_dir($dir)) {
|
||||||
if($dirFiles !== false) {
|
throw new \InvalidArgumentException('Working directory is not a directory!');
|
||||||
foreach($dirFiles as $fileName) {
|
}
|
||||||
if(preg_match('@^([^.]+)Format\.php$@U', $fileName, $out)) { // Is PHP file ?
|
|
||||||
$listFormat[] = $out[1];
|
self::$workingDir = realpath($dir) . '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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!');
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::$workingDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $listFormat;
|
return $formatNames;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,41 +1,103 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once(__DIR__ . '/FormatInterface.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}
|
||||||
|
*/
|
||||||
abstract class FormatAbstract implements FormatInterface {
|
abstract class FormatAbstract implements FormatInterface {
|
||||||
|
|
||||||
|
/** The default charset (UTF-8) */
|
||||||
const DEFAULT_CHARSET = 'UTF-8';
|
const DEFAULT_CHARSET = 'UTF-8';
|
||||||
|
|
||||||
protected
|
/** @var string|null $contentType The content type */
|
||||||
$contentType,
|
protected $contentType = null;
|
||||||
$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){
|
public function setCharset($charset){
|
||||||
$this->charset = $charset;
|
$this->charset = $charset;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** {@inheritdoc} */
|
||||||
public function getCharset(){
|
public function getCharset(){
|
||||||
$charset = $this->charset;
|
$charset = $this->charset;
|
||||||
|
|
||||||
return is_null($charset) ? static::DEFAULT_CHARSET : $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){
|
protected function setContentType($contentType){
|
||||||
$this->contentType = $contentType;
|
$this->contentType = $contentType;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the last modified time
|
||||||
|
*
|
||||||
|
* @param int $lastModified The last modified time
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
public function setLastModified($lastModified){
|
public function setLastModified($lastModified){
|
||||||
$this->lastModified = $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(){
|
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);
|
header('Content-Type: ' . $this->contentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** {@inheritdoc} */
|
||||||
public function display(){
|
public function display(){
|
||||||
if ($this->lastModified) {
|
if ($this->lastModified) {
|
||||||
header('Last-Modified: ' . gmdate('D, d M Y H:i:s ', $this->lastModified) . 'GMT');
|
header('Last-Modified: ' . gmdate('D, d M Y H:i:s ', $this->lastModified) . 'GMT');
|
||||||
@@ -45,12 +107,18 @@ abstract class FormatAbstract implements FormatInterface {
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @param array $items {@inheritdoc}
|
||||||
|
*/
|
||||||
public function setItems(array $items){
|
public function setItems(array $items){
|
||||||
$this->items = array_map(array($this, 'array_trim'), $items);
|
$this->items = $items;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** {@inheritdoc} */
|
||||||
public function getItems(){
|
public function getItems(){
|
||||||
if(!is_array($this->items))
|
if(!is_array($this->items))
|
||||||
throw new \LogicException('Feed the ' . get_class($this) . ' with "setItems" method before !');
|
throw new \LogicException('Feed the ' . get_class($this) . ' with "setItems" method before !');
|
||||||
@@ -59,10 +127,10 @@ abstract class FormatAbstract implements FormatInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Define common informations can be required by formats and set default value for unknown values
|
* {@inheritdoc}
|
||||||
* @param array $extraInfos array with know informations (there isn't merge !!!)
|
*
|
||||||
* @return this
|
* @param array $extraInfos {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function setExtraInfos(array $extraInfos = array()){
|
public function setExtraInfos(array $extraInfos = array()){
|
||||||
foreach(array('name', 'uri', 'icon') as $infoName) {
|
foreach(array('name', 'uri', 'icon') as $infoName) {
|
||||||
if(!isset($extraInfos[$infoName])) {
|
if(!isset($extraInfos[$infoName])) {
|
||||||
@@ -75,10 +143,7 @@ abstract class FormatAbstract implements FormatInterface {
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** {@inheritdoc} */
|
||||||
* Return extra infos
|
|
||||||
* @return array See "setExtraInfos" detail method to know what extra are disponibles
|
|
||||||
*/
|
|
||||||
public function getExtraInfos(){
|
public function getExtraInfos(){
|
||||||
if(is_null($this->extraInfos)) { // No extra info ?
|
if(is_null($this->extraInfos)) { // No extra info ?
|
||||||
$this->setExtraInfos(); // Define with default value
|
$this->setExtraInfos(); // Define with default value
|
||||||
@@ -88,12 +153,17 @@ abstract class FormatAbstract implements FormatInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sanitized html while leaving it functionnal.
|
* Sanitize HTML while leaving it functional.
|
||||||
* The aim is to keep html as-is (with clickable hyperlinks)
|
*
|
||||||
* while reducing annoying and potentially dangerous things.
|
* Keeps HTML as-is (with clickable hyperlinks) while reducing annoying and
|
||||||
* Yes, I know sanitizing HTML 100% is an impossible task.
|
* potentially dangerous things.
|
||||||
* Maybe we'll switch to http://htmlpurifier.org/
|
*
|
||||||
* or http://www.bioinformatics.org/phplabware/internal_utilities/htmLawed/index.php
|
* @param string $html The HTML content
|
||||||
|
* @return string The sanitized HTML content
|
||||||
|
*
|
||||||
|
* @todo This belongs into `html.php`
|
||||||
|
* @todo Maybe switch to http://htmlpurifier.org/
|
||||||
|
* @todo Maybe switch to http://www.bioinformatics.org/phplabware/internal_utilities/htmLawed/index.php
|
||||||
*/
|
*/
|
||||||
protected function sanitizeHtml($html)
|
protected function sanitizeHtml($html)
|
||||||
{
|
{
|
||||||
@@ -104,6 +174,17 @@ abstract class FormatAbstract implements FormatInterface {
|
|||||||
return $html;
|
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){
|
protected function array_trim($elements){
|
||||||
foreach($elements as $key => $value) {
|
foreach($elements as $key => $value) {
|
||||||
if(is_string($value))
|
if(is_string($value))
|
||||||
|
@@ -1,11 +1,83 @@
|
|||||||
<?php
|
<?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 {
|
interface FormatInterface {
|
||||||
|
/**
|
||||||
|
* Generate a string representation of the current data
|
||||||
|
*
|
||||||
|
* @return string The string representation
|
||||||
|
*/
|
||||||
public function stringify();
|
public function stringify();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the current data to the user
|
||||||
|
*
|
||||||
|
* @return self The format object
|
||||||
|
*/
|
||||||
public function display();
|
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);
|
public function setItems(array $bridges);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return items
|
||||||
|
*
|
||||||
|
* @throws \LogicException if the items are not set
|
||||||
|
* @return array The items
|
||||||
|
*/
|
||||||
public function getItems();
|
public function getItems();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set extra information
|
||||||
|
*
|
||||||
|
* @param array $infos Extra information
|
||||||
|
* @return self The format object
|
||||||
|
*/
|
||||||
public function setExtraInfos(array $infos);
|
public function setExtraInfos(array $infos);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return extra information
|
||||||
|
*
|
||||||
|
* @return array Extra information
|
||||||
|
*/
|
||||||
public function getExtraInfos();
|
public function getExtraInfos();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set charset
|
||||||
|
*
|
||||||
|
* @param string $charset The charset
|
||||||
|
* @return self The format object
|
||||||
|
*/
|
||||||
public function setCharset($charset);
|
public function setCharset($charset);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return current charset
|
||||||
|
*
|
||||||
|
* @return string The charset
|
||||||
|
*/
|
||||||
public function getCharset();
|
public function getCharset();
|
||||||
}
|
}
|
||||||
|
@@ -1,10 +1,35 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* Implements a validator for bridge parameters
|
* 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
|
||||||
*/
|
*/
|
||||||
class ParameterValidator {
|
class ParameterValidator {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds the list of invalid parameters
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
private $invalid = 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){
|
private function addInvalidParameter($name, $reason){
|
||||||
$this->invalid[] = array(
|
$this->invalid[] = array(
|
||||||
'name' => $name,
|
'name' => $name,
|
||||||
@@ -13,13 +38,23 @@ class ParameterValidator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an array of invalid parameters, where each element is an
|
* Return list of invalid parameters.
|
||||||
* array of 'name' and 'reason'.
|
*
|
||||||
|
* Each element is an array of 'name' and 'reason'.
|
||||||
|
*
|
||||||
|
* @return array List of invalid parameters
|
||||||
*/
|
*/
|
||||||
public function getInvalidParameters() {
|
public function getInvalidParameters() {
|
||||||
return $this->invalid;
|
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){
|
private function validateTextValue($value, $pattern = null){
|
||||||
if(!is_null($pattern)) {
|
if(!is_null($pattern)) {
|
||||||
$filteredValue = filter_var($value,
|
$filteredValue = filter_var($value,
|
||||||
@@ -38,6 +73,12 @@ class ParameterValidator {
|
|||||||
return $filteredValue;
|
return $filteredValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate value for a number input
|
||||||
|
*
|
||||||
|
* @param int $value The value of a number input
|
||||||
|
* @return int|null The filtered value or null if the value is invalid
|
||||||
|
*/
|
||||||
private function validateNumberValue($value){
|
private function validateNumberValue($value){
|
||||||
$filteredValue = filter_var($value, FILTER_VALIDATE_INT);
|
$filteredValue = filter_var($value, FILTER_VALIDATE_INT);
|
||||||
|
|
||||||
@@ -47,10 +88,23 @@ class ParameterValidator {
|
|||||||
return $filteredValue;
|
return $filteredValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate value for a checkbox
|
||||||
|
*
|
||||||
|
* @param bool $value The value of a checkbox
|
||||||
|
* @return bool The filtered value
|
||||||
|
*/
|
||||||
private function validateCheckboxValue($value){
|
private function validateCheckboxValue($value){
|
||||||
return filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
|
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){
|
private function validateListValue($value, $expectedValues){
|
||||||
$filteredValue = filter_var($value);
|
$filteredValue = filter_var($value);
|
||||||
|
|
||||||
@@ -69,9 +123,11 @@ class ParameterValidator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if all required parameters are supplied by the user
|
* Check if all required parameters are satisfied
|
||||||
* @param $data An array of parameters provided by the user
|
*
|
||||||
* @param $parameters An array of bridge parameters
|
* @param array $data (ref) A list of input values
|
||||||
|
* @param array $parameters The bridge parameters
|
||||||
|
* @return bool True if all parameters are satisfied
|
||||||
*/
|
*/
|
||||||
public function validateData(&$data, $parameters){
|
public function validateData(&$data, $parameters){
|
||||||
|
|
||||||
@@ -122,11 +178,11 @@ class ParameterValidator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the name of the context matching the provided inputs
|
* Get the name of the context matching the provided inputs
|
||||||
*
|
*
|
||||||
* @param array $data Associative array of user data
|
* @param array $data Associative array of user data
|
||||||
* @param array $parameters Array of bridge parameters
|
* @param array $parameters Array of bridge parameters
|
||||||
* @return mixed Returns the context name or null if no match was found
|
* @return string|null Returns the context name or null if no match was found
|
||||||
*/
|
*/
|
||||||
public function getQueriedContext($data, $parameters){
|
public function getQueriedContext($data, $parameters){
|
||||||
$queriedContexts = array();
|
$queriedContexts = array();
|
||||||
|
@@ -1,69 +0,0 @@
|
|||||||
<?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();
|
|
||||||
|
|
||||||
*/
|
|
299
lib/contents.php
299
lib/contents.php
@@ -1,79 +1,211 @@
|
|||||||
<?php
|
<?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()){
|
function getContents($url, $header = array(), $opts = array()){
|
||||||
debugMessage('Reading contents from "' . $url . '"');
|
Debug::log('Reading contents from "' . $url . '"');
|
||||||
|
|
||||||
$ch = curl_init($url);
|
// Initialize cache
|
||||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
$cache = Cache::create('FileCache');
|
||||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
$cache->setPath(PATH_CACHE . 'server/');
|
||||||
|
$cache->purgeCache(86400); // 24 hours (forced)
|
||||||
|
|
||||||
if(is_array($header) && count($header) !== 0) {
|
$params = [$url];
|
||||||
|
$cache->setParameters($params);
|
||||||
|
|
||||||
debugMessage('Setting headers: ' . json_encode($header));
|
// Use file_get_contents if in CLI mode with no root certificates defined
|
||||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
|
if(php_sapi_name() === 'cli' && empty(ini_get('curl.cainfo'))) {
|
||||||
|
$data = @file_get_contents($url);
|
||||||
|
|
||||||
}
|
if($data === false) {
|
||||||
|
$errorCode = 500;
|
||||||
curl_setopt($ch, CURLOPT_USERAGENT, ini_get('user_agent'));
|
} else {
|
||||||
curl_setopt($ch, CURLOPT_ENCODING, '');
|
$errorCode = 200;
|
||||||
curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
|
|
||||||
|
|
||||||
if(is_array($opts) && count($opts) !== 0) {
|
|
||||||
|
|
||||||
debugMessage('Setting options: ' . json_encode($opts));
|
|
||||||
|
|
||||||
foreach($opts as $key => $value) {
|
|
||||||
curl_setopt($ch, $key, $value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$curlError = '';
|
||||||
|
$curlErrno = '';
|
||||||
|
$headerSize = 0;
|
||||||
|
$finalHeader = array();
|
||||||
|
} else {
|
||||||
|
$ch = curl_init($url);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||||
|
|
||||||
|
if(is_array($header) && count($header) !== 0) {
|
||||||
|
|
||||||
|
Debug::log('Setting headers: ' . json_encode($header));
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
curl_setopt($ch, CURLOPT_USERAGENT, ini_get('user_agent'));
|
||||||
|
curl_setopt($ch, CURLOPT_ENCODING, '');
|
||||||
|
curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
|
||||||
|
|
||||||
|
if(is_array($opts) && count($opts) !== 0) {
|
||||||
|
|
||||||
|
Debug::log('Setting options: ' . json_encode($opts));
|
||||||
|
|
||||||
|
foreach($opts as $key => $value) {
|
||||||
|
curl_setopt($ch, $key, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if(defined('PROXY_URL') && !defined('NOPROXY')) {
|
||||||
|
|
||||||
|
Debug::log('Setting proxy url: ' . PROXY_URL);
|
||||||
|
curl_setopt($ch, CURLOPT_PROXY, PROXY_URL);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
$errorCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
|
||||||
|
$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 . ')');
|
||||||
|
|
||||||
|
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
|
||||||
|
$header = substr($data, 0, $headerSize);
|
||||||
|
|
||||||
|
Debug::log('Response header: ' . $header);
|
||||||
|
|
||||||
|
$headers = parseResponseHeader($header);
|
||||||
|
$finalHeader = end($headers);
|
||||||
|
|
||||||
|
curl_close($ch);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(defined('PROXY_URL') && !defined('NOPROXY')) {
|
switch($errorCode) {
|
||||||
|
case 200: // Contents received
|
||||||
debugMessage('Setting proxy url: ' . PROXY_URL);
|
Debug::log('New contents received');
|
||||||
curl_setopt($ch, CURLOPT_PROXY, PROXY_URL);
|
$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);
|
||||||
// We always want the response header as part of the data!
|
if(array_key_exists('cache-control', $finalHeader)) {
|
||||||
curl_setopt($ch, CURLOPT_HEADER, true);
|
Debug::log('Server responded with "Cache-Control" header');
|
||||||
|
$directives = explode(',', $finalHeader['cache-control']);
|
||||||
$data = curl_exec($ch);
|
$directives = array_map('trim', $directives);
|
||||||
$curlError = curl_error($ch);
|
if(in_array('no-cache', $directives)
|
||||||
$curlErrno = curl_errno($ch);
|
|| in_array('no-store', $directives)) { // Skip caching
|
||||||
|
Debug::log('Skip server side caching');
|
||||||
if($data === false)
|
return $data;
|
||||||
debugMessage('Cant\'t download ' . $url . ' cUrl error: ' . $curlError . ' (' . $curlErrno . ')');
|
}
|
||||||
|
}
|
||||||
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
|
Debug::log('Store response to cache');
|
||||||
$errorCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
$cache->saveData($data);
|
||||||
$header = substr($data, 0, $headerSize);
|
return $data;
|
||||||
|
case 304: // Not modified, use cached data
|
||||||
debugMessage('Response header: ' . $header);
|
Debug::log('Contents not modified on host, returning cached data');
|
||||||
|
return $cache->loadData();
|
||||||
$headers = parseResponseHeader($header);
|
default:
|
||||||
$finalHeader = end($headers);
|
if(array_key_exists('Server', $finalHeader) && strpos($finalHeader['Server'], 'cloudflare') !== false) {
|
||||||
|
|
||||||
if($errorCode !== 200) {
|
|
||||||
|
|
||||||
if(array_key_exists('Server', $finalHeader) && strpos($finalHeader['Server'], 'cloudflare') !== false) {
|
|
||||||
returnServerError(<<< EOD
|
returnServerError(<<< EOD
|
||||||
The server responded with a Cloudflare challenge, which is not supported by RSS-Bridge!
|
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!
|
If this error persists longer than a week, please consider opening an issue on GitHub!
|
||||||
EOD
|
EOD
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
returnError(<<<EOD
|
$lastError = error_get_last();
|
||||||
|
if($lastError !== null)
|
||||||
|
$lastError = $lastError['message'];
|
||||||
|
returnError(<<<EOD
|
||||||
The requested resource cannot be found!
|
The requested resource cannot be found!
|
||||||
Please make sure your input parameters are correct!
|
Please make sure your input parameters are correct!
|
||||||
|
cUrl error: $curlError ($curlErrno)
|
||||||
|
PHP error: $lastError
|
||||||
EOD
|
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,
|
function getSimpleHTMLDOM($url,
|
||||||
$header = array(),
|
$header = array(),
|
||||||
$opts = array(),
|
$opts = array(),
|
||||||
@@ -94,10 +226,34 @@ $defaultSpanText = DEFAULT_SPAN_TEXT){
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maintain locally cached versions of pages to avoid multiple downloads.
|
* Gets contents from the Internet as simplhtmldom object. Contents are cached
|
||||||
* @param url url to cache
|
* and re-used for subsequent calls until the cache duration elapsed.
|
||||||
* @param duration duration of the cache file in seconds (default: 24h/86400s)
|
*
|
||||||
* @return content of the file as string
|
* _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.
|
||||||
*/
|
*/
|
||||||
function getSimpleHTMLDOMCached($url,
|
function getSimpleHTMLDOMCached($url,
|
||||||
$duration = 86400,
|
$duration = 86400,
|
||||||
@@ -109,11 +265,11 @@ $target_charset = DEFAULT_TARGET_CHARSET,
|
|||||||
$stripRN = true,
|
$stripRN = true,
|
||||||
$defaultBRText = DEFAULT_BR_TEXT,
|
$defaultBRText = DEFAULT_BR_TEXT,
|
||||||
$defaultSpanText = DEFAULT_SPAN_TEXT){
|
$defaultSpanText = DEFAULT_SPAN_TEXT){
|
||||||
debugMessage('Caching url ' . $url . ', duration ' . $duration);
|
Debug::log('Caching url ' . $url . ', duration ' . $duration);
|
||||||
|
|
||||||
// Initialize cache
|
// Initialize cache
|
||||||
$cache = Cache::create('FileCache');
|
$cache = Cache::create('FileCache');
|
||||||
$cache->setPath(CACHE_DIR . '/pages');
|
$cache->setPath(PATH_CACHE . 'pages/');
|
||||||
$cache->purgeCache(86400); // 24 hours (forced)
|
$cache->purgeCache(86400); // 24 hours (forced)
|
||||||
|
|
||||||
$params = [$url];
|
$params = [$url];
|
||||||
@@ -123,7 +279,7 @@ $defaultSpanText = DEFAULT_SPAN_TEXT){
|
|||||||
$time = $cache->getTime();
|
$time = $cache->getTime();
|
||||||
if($time !== false
|
if($time !== false
|
||||||
&& (time() - $duration < $time)
|
&& (time() - $duration < $time)
|
||||||
&& (!defined('DEBUG') || DEBUG !== true)) { // Contents within duration
|
&& Debug::isEnabled()) { // Contents within duration
|
||||||
$content = $cache->loadData();
|
$content = $cache->loadData();
|
||||||
} else { // Content not within duration
|
} else { // Content not within duration
|
||||||
$content = getContents($url, $header, $opts);
|
$content = getContents($url, $header, $opts);
|
||||||
@@ -142,9 +298,12 @@ $defaultSpanText = DEFAULT_SPAN_TEXT){
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the provided response header into an associative array
|
* Parses the cURL response header into an associative array
|
||||||
*
|
*
|
||||||
* Based on https://stackoverflow.com/a/18682872
|
* 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) {
|
function parseResponseHeader($header) {
|
||||||
|
|
||||||
@@ -177,10 +336,18 @@ function parseResponseHeader($header) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine MIME type from URL/Path file extension
|
* Determines the MIME type from a 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
|
* _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`).
|
||||||
|
*
|
||||||
* Based on https://stackoverflow.com/a/1147952
|
* 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) {
|
function getMimeType($url) {
|
||||||
static $mime = null;
|
static $mime = null;
|
||||||
|
@@ -1,28 +1,43 @@
|
|||||||
<?php
|
<?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){
|
function returnError($message, $code){
|
||||||
throw new \HttpException($message, $code);
|
throw new \Exception($message, $code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns HTTP Error 400 (Bad Request) when called.
|
||||||
|
*
|
||||||
|
* @param string $message The error message
|
||||||
|
*/
|
||||||
function returnClientError($message){
|
function returnClientError($message){
|
||||||
returnError($message, 400);
|
returnError($message, 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns HTTP Error 500 (Internal Server Error) when called.
|
||||||
|
*
|
||||||
|
* @param string $message The error message
|
||||||
|
*/
|
||||||
function returnServerError($message){
|
function returnServerError($message){
|
||||||
returnError($message, 500);
|
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);
|
|
||||||
}
|
|
||||||
|
141
lib/html.php
141
lib/html.php
@@ -1,18 +1,55 @@
|
|||||||
<?php
|
<?php
|
||||||
function sanitize($textToSanitize,
|
/**
|
||||||
$removedTags = array('script', 'iframe', 'input', 'form'),
|
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
|
||||||
$keptAttributes = array('title', 'href', 'src'),
|
* Atom feeds for websites that don't have one.
|
||||||
$keptText = array()){
|
*
|
||||||
$htmlContent = str_get_html($textToSanitize);
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
foreach($htmlContent->find('*[!b38fd2b1fe7f4747d6b1c1254ccd055e]') as $element) {
|
||||||
if(in_array($element->tag, $keptText)) {
|
if(in_array($element->tag, $text_to_keep)) {
|
||||||
$element->outertext = $element->plaintext;
|
$element->outertext = $element->plaintext;
|
||||||
} elseif(in_array($element->tag, $removedTags)) {
|
} elseif(in_array($element->tag, $tags_to_remove)) {
|
||||||
$element->outertext = '';
|
$element->outertext = '';
|
||||||
} else {
|
} else {
|
||||||
foreach($element->getAllAttributes() as $attributeName => $attribute) {
|
foreach($element->getAllAttributes() as $attributeName => $attribute) {
|
||||||
if(!in_array($attributeName, $keptAttributes))
|
if(!in_array($attributeName, $attributes_to_keep))
|
||||||
$element->removeAttribute($attributeName);
|
$element->removeAttribute($attributeName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -21,11 +58,48 @@ $keptText = array()){
|
|||||||
return $htmlContent;
|
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) {
|
function backgroundToImg($htmlContent) {
|
||||||
|
|
||||||
$regex = '/background-image[ ]{0,}:[ ]{0,}url\([\'"]{0,}(.*?)[\'"]{0,}\)/';
|
$regex = '/background-image[ ]{0,}:[ ]{0,}url\([\'"]{0,}(.*?)[\'"]{0,}\)/';
|
||||||
$htmlContent = str_get_html($htmlContent);
|
$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) {
|
foreach($htmlContent->find('*[!b38fd2b1fe7f4747d6b1c1254ccd055e]') as $element) {
|
||||||
|
|
||||||
if(preg_match($regex, $element->style, $matches) > 0) {
|
if(preg_match($regex, $element->style, $matches) > 0) {
|
||||||
@@ -42,9 +116,14 @@ function backgroundToImg($htmlContent) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert relative links in HTML into absolute links
|
* Convert relative links in HTML into absolute links
|
||||||
* @param $content HTML content to fix. Supports HTML objects or string objects
|
*
|
||||||
* @param $server full URL to the page containing relative links
|
* This function is based on `php-urljoin`.
|
||||||
* @return content with fixed URLs, as HTML object or string depending on input type
|
*
|
||||||
|
* @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.
|
||||||
*/
|
*/
|
||||||
function defaultLinkTo($content, $server){
|
function defaultLinkTo($content, $server){
|
||||||
$string_convert = false;
|
$string_convert = false;
|
||||||
@@ -70,10 +149,12 @@ function defaultLinkTo($content, $server){
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract the first part of a string matching the specified start and end delimiters
|
* Extract the first part of a string matching the specified start and end delimiters
|
||||||
* @param $string input string, e.g. '<div>Post author: John Doe</div>'
|
*
|
||||||
* @param $start start delimiter, e.g. 'author: '
|
* @param string $string Input string, e.g. `<div>Post author: John Doe</div>`
|
||||||
* @param $end end delimiter, e.g. '<'
|
* @param string $start Start delimiter, e.g. `author: `
|
||||||
* @return extracted string, e.g. 'John Doe', or false if the delimiters were not found.
|
* @param string $end End delimiter, e.g. `<`
|
||||||
|
* @return string|bool Extracted string, e.g. `John Doe`, or false if the
|
||||||
|
* delimiters were not found.
|
||||||
*/
|
*/
|
||||||
function extractFromDelimiters($string, $start, $end) {
|
function extractFromDelimiters($string, $start, $end) {
|
||||||
if (strpos($string, $start) !== false) {
|
if (strpos($string, $start) !== false) {
|
||||||
@@ -85,10 +166,11 @@ function extractFromDelimiters($string, $start, $end) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove one or more part(s) of a string using a start and end delmiters
|
* Remove one or more part(s) of a string using a start and end delmiters
|
||||||
* @param $string input string, e.g. 'foo<script>superscript()</script>bar'
|
*
|
||||||
* @param $start start delimiter, e.g. '<script'
|
* @param string $string Input string, e.g. `foo<script>superscript()</script>bar`
|
||||||
* @param $end end delimiter, e.g. '</script>'
|
* @param string $start Start delimiter, e.g. `<script`
|
||||||
* @return cleaned string, e.g. 'foobar'
|
* @param string $end End delimiter, e.g. `</script>`
|
||||||
|
* @return string Cleaned string, e.g. `foobar`
|
||||||
*/
|
*/
|
||||||
function stripWithDelimiters($string, $start, $end) {
|
function stripWithDelimiters($string, $start, $end) {
|
||||||
while(strpos($string, $start) !== false) {
|
while(strpos($string, $start) !== false) {
|
||||||
@@ -101,10 +183,13 @@ function stripWithDelimiters($string, $start, $end) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove HTML sections containing one or more sections using the same HTML tag
|
* Remove HTML sections containing one or more sections using the same HTML tag
|
||||||
* @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 string $string Input string, e.g. `foo<div class="ads"><div>ads</div>ads</div>bar`
|
||||||
* @param $tag_start start of the HTML tag to remove, e.g. '<div class="ads">'
|
* @param string $tag_name Name of the HTML tag, e.g. `div`
|
||||||
* @return cleaned string, e.g. 'foobar'
|
* @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.
|
||||||
*/
|
*/
|
||||||
function stripRecursiveHTMLSection($string, $tag_name, $tag_start){
|
function stripRecursiveHTMLSection($string, $tag_name, $tag_start){
|
||||||
$open_tag = '<' . $tag_name;
|
$open_tag = '<' . $tag_name;
|
||||||
@@ -131,9 +216,13 @@ function stripRecursiveHTMLSection($string, $tag_name, $tag_start){
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert Markdown tags into HTML tags. Only a subset of the Markdown syntax is implemented.
|
* Convert Markdown into HTML. Only a subset of the Markdown syntax is implemented.
|
||||||
* @param $string input string in Markdown format
|
*
|
||||||
* @return output string in HTML format
|
* @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
|
||||||
*/
|
*/
|
||||||
function markdownToHtml($string) {
|
function markdownToHtml($string) {
|
||||||
|
|
||||||
|
80
lib/rssbridge.php
Normal file
80
lib/rssbridge.php
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
<?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 . 'FeedItem.php';
|
||||||
|
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());
|
||||||
|
}
|
47
phpcompatibility.xml
Normal file
47
phpcompatibility.xml
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<?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>
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user