mirror of
https://github.com/RSS-Bridge/rss-bridge.git
synced 2025-08-16 05:24:08 +02:00
Compare commits
88 Commits
2018-11-10
...
2018-12-11
Author | SHA1 | Date | |
---|---|---|---|
|
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 |
14
.gitattributes
vendored
14
.gitattributes
vendored
@@ -20,3 +20,17 @@
|
||||
*.PDF diff=astextplain
|
||||
*.rtf diff=astextplain
|
||||
*.RTF diff=astextplain
|
||||
|
||||
# Ignore files in git archive (i.e. GitHub release builds)
|
||||
Dockerfile export-ignore
|
||||
.travis.yml export-ignore
|
||||
.github/ export-ignore
|
||||
.gitattributes export-ignore
|
||||
.gitignore export-ignore
|
||||
.dockerignore export-ignore
|
||||
scalingo.json export-ignore
|
||||
phpunit.xml export-ignore
|
||||
phpcs.xml export-ignore
|
||||
phpcompatibility.xml export-ignore
|
||||
tests/ export-ignore
|
||||
cache/.gitkeep export-ignore
|
2
.github/CONTRIBUTING.md
vendored
2
.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)
|
||||
* [Do not use final statements inside final classes](https://github.com/RSS-Bridge/rss-bridge/wiki/Classes#do-not-use-final-statements-inside-final-classes)
|
||||
* [Do not override methods to call their parent](https://github.com/RSS-Bridge/rss-bridge/wiki/Classes#do-not-override-methods-to-call-their-parent)
|
||||
* [abstract and final declarations MUST precede the visibility declaration](https://github.com/RSS-Bridge/rss-bridge/wiki/Classes#abstract-and-final-declarations-must-precede-the-visibility-declaration)
|
||||
* [static declaration MUST come after the visibility declaration](https://github.com/RSS-Bridge/rss-bridge/wiki/Classes#static-declaration-must-come-after-the-visibility-declaration)
|
||||
* [Casting](https://github.com/RSS-Bridge/rss-bridge/wiki/Casting)
|
||||
* [Do not add spaces when casting](https://github.com/RSS-Bridge/rss-bridge/wiki/Casting#do-not-add-spaces-when-casting)
|
||||
|
@@ -1,5 +1,4 @@
|
||||
dist: trusty
|
||||
sudo: false
|
||||
language: php
|
||||
|
||||
install:
|
||||
@@ -17,10 +16,10 @@ install:
|
||||
script:
|
||||
- phpenv rehash
|
||||
# Run PHP_CodeSniffer on all versions
|
||||
- ~/.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;
|
||||
# Check PHP compatibility for the lowest supported version
|
||||
- if [[ $TRAVIS_PHP_VERSION == "5.6" ]]; then
|
||||
~/.composer/vendor/bin/phpcs . --standard=phpcompatibility.xml --warning-severity=0 --extensions=php -p;
|
||||
~/.config/composer/vendor/bin/phpcs . --standard=phpcompatibility.xml --warning-severity=0 --extensions=php -p;
|
||||
fi
|
||||
# Run unit tests (stable)
|
||||
- if [[ $TRAVIS_PHP_VERSION == "7.0" ]]; then
|
||||
@@ -30,7 +29,7 @@ script:
|
||||
# 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/;
|
||||
~/.composer/vendor/bin/phpcs . --standard=PHPCompatibility --warning-severity=0 --extensions=php -p --runtime-set testVersion 5.6-;
|
||||
~/.config/composer/vendor/bin/phpcs . --standard=PHPCompatibility --warning-severity=0 --extensions=php -p --runtime-set testVersion 5.6-;
|
||||
fi
|
||||
|
||||
matrix:
|
||||
|
172
README.md
172
README.md
@@ -110,88 +110,96 @@ Use this script to generate the list automatically (using the GitHub API):
|
||||
https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
|
||||
-->
|
||||
|
||||
* [16mhz](https://api.github.com/users/16mhz)
|
||||
* [Ahiles3005](https://api.github.com/users/Ahiles3005)
|
||||
* [Albirew](https://api.github.com/users/Albirew)
|
||||
* [AmauryCarrade](https://api.github.com/users/AmauryCarrade)
|
||||
* [ArthurHoaro](https://api.github.com/users/ArthurHoaro)
|
||||
* [Astalaseven](https://api.github.com/users/Astalaseven)
|
||||
* [Astyan-42](https://api.github.com/users/Astyan-42)
|
||||
* [Daiyousei](https://api.github.com/users/Daiyousei)
|
||||
* [Djuuu](https://api.github.com/users/Djuuu)
|
||||
* [Draeli](https://api.github.com/users/Draeli)
|
||||
* [EtienneM](https://api.github.com/users/EtienneM)
|
||||
* [Frenzie](https://api.github.com/users/Frenzie)
|
||||
* [Ginko-Aloe](https://api.github.com/users/Ginko-Aloe)
|
||||
* [Glandos](https://api.github.com/users/Glandos)
|
||||
* [GregThib](https://api.github.com/users/GregThib)
|
||||
* [Grummfy](https://api.github.com/users/Grummfy)
|
||||
* [JackNUMBER](https://api.github.com/users/JackNUMBER)
|
||||
* [JeremyRand](https://api.github.com/users/JeremyRand)
|
||||
* [Jocker666z](https://api.github.com/users/Jocker666z)
|
||||
* [LogMANOriginal](https://api.github.com/users/LogMANOriginal)
|
||||
* [MonsieurPoutounours](https://api.github.com/users/MonsieurPoutounours)
|
||||
* [ORelio](https://api.github.com/users/ORelio)
|
||||
* [PaulVayssiere](https://api.github.com/users/PaulVayssiere)
|
||||
* [Piranhaplant](https://api.github.com/users/Piranhaplant)
|
||||
* [Riduidel](https://api.github.com/users/Riduidel)
|
||||
* [Strubbl](https://api.github.com/users/Strubbl)
|
||||
* [TheRadialActive](https://api.github.com/users/TheRadialActive)
|
||||
* [TwizzyDizzy](https://api.github.com/users/TwizzyDizzy)
|
||||
* [WalterBarrett](https://api.github.com/users/WalterBarrett)
|
||||
* [ZeNairolf](https://api.github.com/users/ZeNairolf)
|
||||
* [adamchainz](https://api.github.com/users/adamchainz)
|
||||
* [aledeg](https://api.github.com/users/aledeg)
|
||||
* [alexAubin](https://api.github.com/users/alexAubin)
|
||||
* [az5he6ch](https://api.github.com/users/az5he6ch)
|
||||
* [b1nj](https://api.github.com/users/b1nj)
|
||||
* [benasse](https://api.github.com/users/benasse)
|
||||
* [captn3m0](https://api.github.com/users/captn3m0)
|
||||
* [chemel](https://api.github.com/users/chemel)
|
||||
* [ckiw](https://api.github.com/users/ckiw)
|
||||
* [cnlpete](https://api.github.com/users/cnlpete)
|
||||
* [corenting](https://api.github.com/users/corenting)
|
||||
* [da2x](https://api.github.com/users/da2x)
|
||||
* [eMerzh](https://api.github.com/users/eMerzh)
|
||||
* [em92](https://api.github.com/users/em92)
|
||||
* [griffaurel](https://api.github.com/users/griffaurel)
|
||||
* [hunhejj](https://api.github.com/users/hunhejj)
|
||||
* [j0k3r](https://api.github.com/users/j0k3r)
|
||||
* [jdigilio](https://api.github.com/users/jdigilio)
|
||||
* [kranack](https://api.github.com/users/kranack)
|
||||
* [kraoc](https://api.github.com/users/kraoc)
|
||||
* [laBecasse](https://api.github.com/users/laBecasse)
|
||||
* [lagaisse](https://api.github.com/users/lagaisse)
|
||||
* [lalannev](https://api.github.com/users/lalannev)
|
||||
* [ldidry](https://api.github.com/users/ldidry)
|
||||
* [m0zes](https://api.github.com/users/m0zes)
|
||||
* [matthewseal](https://api.github.com/users/matthewseal)
|
||||
* [mcbyte-it](https://api.github.com/users/mcbyte-it)
|
||||
* [mdemoss](https://api.github.com/users/mdemoss)
|
||||
* [melangue](https://api.github.com/users/melangue)
|
||||
* [metaMMA](https://api.github.com/users/metaMMA)
|
||||
* [mickael-bertrand](https://api.github.com/users/mickael-bertrand)
|
||||
* [mitsukarenai](https://api.github.com/users/mitsukarenai)
|
||||
* [mro](https://api.github.com/users/mro)
|
||||
* [mxmehl](https://api.github.com/users/mxmehl)
|
||||
* [nel50n](https://api.github.com/users/nel50n)
|
||||
* [niawag](https://api.github.com/users/niawag)
|
||||
* [pellaeon](https://api.github.com/users/pellaeon)
|
||||
* [pit-fgfjiudghdf](https://api.github.com/users/pit-fgfjiudghdf)
|
||||
* [pitchoule](https://api.github.com/users/pitchoule)
|
||||
* [pmaziere](https://api.github.com/users/pmaziere)
|
||||
* [prysme01](https://api.github.com/users/prysme01)
|
||||
* [quentinus95](https://api.github.com/users/quentinus95)
|
||||
* [qwertygc](https://api.github.com/users/qwertygc)
|
||||
* [regisenguehard](https://api.github.com/users/regisenguehard)
|
||||
* [rogerdc](https://api.github.com/users/rogerdc)
|
||||
* [sebsauvage](https://api.github.com/users/sebsauvage)
|
||||
* [sublimz](https://api.github.com/users/sublimz)
|
||||
* [sysadminstory](https://api.github.com/users/sysadminstory)
|
||||
* [tameroski](https://api.github.com/users/tameroski)
|
||||
* [teromene](https://api.github.com/users/teromene)
|
||||
* [triatic](https://api.github.com/users/triatic)
|
||||
* [wtuuju](https://api.github.com/users/wtuuju)
|
||||
* [16mhz](https://github.com/16mhz)
|
||||
* [Ahiles3005](https://github.com/Ahiles3005)
|
||||
* [Albirew](https://github.com/Albirew)
|
||||
* [AmauryCarrade](https://github.com/AmauryCarrade)
|
||||
* [AntoineTurmel](https://github.com/AntoineTurmel)
|
||||
* [ArthurHoaro](https://github.com/ArthurHoaro)
|
||||
* [Astalaseven](https://github.com/Astalaseven)
|
||||
* [Astyan-42](https://github.com/Astyan-42)
|
||||
* [Daiyousei](https://github.com/Daiyousei)
|
||||
* [Djuuu](https://github.com/Djuuu)
|
||||
* [Draeli](https://github.com/Draeli)
|
||||
* [EtienneM](https://github.com/EtienneM)
|
||||
* [Frenzie](https://github.com/Frenzie)
|
||||
* [Ginko-Aloe](https://github.com/Ginko-Aloe)
|
||||
* [Glandos](https://github.com/Glandos)
|
||||
* [GregThib](https://github.com/GregThib)
|
||||
* [Grummfy](https://github.com/Grummfy)
|
||||
* [JackNUMBER](https://github.com/JackNUMBER)
|
||||
* [JeremyRand](https://github.com/JeremyRand)
|
||||
* [Jocker666z](https://github.com/Jocker666z)
|
||||
* [LogMANOriginal](https://github.com/LogMANOriginal)
|
||||
* [MonsieurPoutounours](https://github.com/MonsieurPoutounours)
|
||||
* [Nono-m0le](https://github.com/Nono-m0le)
|
||||
* [ORelio](https://github.com/ORelio)
|
||||
* [PaulVayssiere](https://github.com/PaulVayssiere)
|
||||
* [Piranhaplant](https://github.com/Piranhaplant)
|
||||
* [Riduidel](https://github.com/Riduidel)
|
||||
* [Roliga](https://github.com/Roliga)
|
||||
* [Strubbl](https://github.com/Strubbl)
|
||||
* [TheRadialActive](https://github.com/TheRadialActive)
|
||||
* [TwizzyDizzy](https://github.com/TwizzyDizzy)
|
||||
* [WalterBarrett](https://github.com/WalterBarrett)
|
||||
* [ZeNairolf](https://github.com/ZeNairolf)
|
||||
* [adamchainz](https://github.com/adamchainz)
|
||||
* [aledeg](https://github.com/aledeg)
|
||||
* [alexAubin](https://github.com/alexAubin)
|
||||
* [az5he6ch](https://github.com/az5he6ch)
|
||||
* [b1nj](https://github.com/b1nj)
|
||||
* [benasse](https://github.com/benasse)
|
||||
* [captn3m0](https://github.com/captn3m0)
|
||||
* [chemel](https://github.com/chemel)
|
||||
* [ckiw](https://github.com/ckiw)
|
||||
* [cnlpete](https://github.com/cnlpete)
|
||||
* [corenting](https://github.com/corenting)
|
||||
* [couraudt](https://github.com/couraudt)
|
||||
* [da2x](https://github.com/da2x)
|
||||
* [disk0x](https://github.com/disk0x)
|
||||
* [eMerzh](https://github.com/eMerzh)
|
||||
* [em92](https://github.com/em92)
|
||||
* [fluffy-critter](https://github.com/fluffy-critter)
|
||||
* [griffaurel](https://github.com/griffaurel)
|
||||
* [hunhejj](https://github.com/hunhejj)
|
||||
* [j0k3r](https://github.com/j0k3r)
|
||||
* [jdigilio](https://github.com/jdigilio)
|
||||
* [kranack](https://github.com/kranack)
|
||||
* [kraoc](https://github.com/kraoc)
|
||||
* [laBecasse](https://github.com/laBecasse)
|
||||
* [lagaisse](https://github.com/lagaisse)
|
||||
* [lalannev](https://github.com/lalannev)
|
||||
* [ldidry](https://github.com/ldidry)
|
||||
* [m0zes](https://github.com/m0zes)
|
||||
* [matthewseal](https://github.com/matthewseal)
|
||||
* [mcbyte-it](https://github.com/mcbyte-it)
|
||||
* [mdemoss](https://github.com/mdemoss)
|
||||
* [melangue](https://github.com/melangue)
|
||||
* [metaMMA](https://github.com/metaMMA)
|
||||
* [mickael-bertrand](https://github.com/mickael-bertrand)
|
||||
* [mitsukarenai](https://github.com/mitsukarenai)
|
||||
* [mr-flibble](https://github.com/mr-flibble)
|
||||
* [mro](https://github.com/mro)
|
||||
* [mxmehl](https://github.com/mxmehl)
|
||||
* [nel50n](https://github.com/nel50n)
|
||||
* [niawag](https://github.com/niawag)
|
||||
* [pellaeon](https://github.com/pellaeon)
|
||||
* [pit-fgfjiudghdf](https://github.com/pit-fgfjiudghdf)
|
||||
* [pitchoule](https://github.com/pitchoule)
|
||||
* [pmaziere](https://github.com/pmaziere)
|
||||
* [prysme01](https://github.com/prysme01)
|
||||
* [quentinus95](https://github.com/quentinus95)
|
||||
* [qwertygc](https://github.com/qwertygc)
|
||||
* [regisenguehard](https://github.com/regisenguehard)
|
||||
* [rogerdc](https://github.com/rogerdc)
|
||||
* [sebsauvage](https://github.com/sebsauvage)
|
||||
* [sublimz](https://github.com/sublimz)
|
||||
* [sysadminstory](https://github.com/sysadminstory)
|
||||
* [tameroski](https://github.com/tameroski)
|
||||
* [teromene](https://github.com/teromene)
|
||||
* [triatic](https://github.com/triatic)
|
||||
* [wtuuju](https://github.com/wtuuju)
|
||||
* [yardenac](https://github.com/yardenac)
|
||||
|
||||
Licenses
|
||||
===
|
||||
|
@@ -113,7 +113,33 @@ class CrewbayBridge extends BridgeAbstract {
|
||||
|
||||
foreach ($annonces as $annonce) {
|
||||
$detail = $annonce->find('.btn--profile', 0);
|
||||
$htmlDetail = getSimpleHTMLDOMCached($detail->getAttribute('data-modal-href'));
|
||||
$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();
|
||||
|
||||
@@ -134,22 +160,24 @@ class CrewbayBridge extends BridgeAbstract {
|
||||
$images = $annonce->find('.avatar img');
|
||||
$item['enclosures'] = array(end($images)->getAttribute('src'));
|
||||
|
||||
if ($this->getInput('type') == 'boats') {
|
||||
$fields = array('job', 'boat', 'skipper');
|
||||
} else {
|
||||
$fields = array('profile', 'positions', 'info', 'qualifications' , 'skills', 'references');
|
||||
}
|
||||
$content = $htmlDetail->find('.viewer-intro--info', 0)->innertext;
|
||||
|
||||
$content = '';
|
||||
foreach ($fields as $field) {
|
||||
$info = $htmlDetail->find('.profile--modal-body .info-' . $field, 0);
|
||||
if ($info) {
|
||||
$content .= $htmlDetail->find('.profile--modal-body .info-' . $field, 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;
|
||||
}
|
||||
}
|
||||
|
||||
$item['content'] = $content;
|
||||
|
||||
if (!empty($this->getInput('keyword'))) {
|
||||
$keyword = strtolower($this->getInput('keyword'));
|
||||
if (strpos(strtolower($item['title']), $keyword) === false) {
|
||||
@@ -159,28 +187,16 @@ class CrewbayBridge extends BridgeAbstract {
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
$positions = explode("\r\n", trim($htmlDetail->find('.info-positions .value', 0)->plaintext));
|
||||
}
|
||||
$item['content'] = $content;
|
||||
|
||||
$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;
|
||||
}
|
||||
$tags = $htmlDetail->find('li.viewer-tags--tag');
|
||||
foreach ($tags as $tag) {
|
||||
if (!isset($item['categories'])) {
|
||||
$item['categories'] = array();
|
||||
}
|
||||
|
||||
if (!$found) {
|
||||
continue;
|
||||
$text = trim($tag->plaintext);
|
||||
if (!in_array($text, $item['categories'])) {
|
||||
$item['categories'][] = $text;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -103,7 +103,7 @@ EOD;
|
||||
$timestamp = 0;
|
||||
|
||||
$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
|
||||
$imagecleaned = preg_replace_callback('/<i [^>]* style="[^"]*url\(\'(.*?)\'\).*?><\/i>/m', function ($matches) {
|
||||
|
@@ -659,14 +659,8 @@ EOD;
|
||||
$date = 0;
|
||||
}
|
||||
|
||||
// Build title from username and content
|
||||
$title = $author;
|
||||
|
||||
if(strlen($title) > 24)
|
||||
$title = substr($title, 0, strpos(wordwrap($title, 24), "\n")) . '...';
|
||||
|
||||
$title = $title . ' | ' . strip_tags($content);
|
||||
|
||||
// Build title from content
|
||||
$title = strip_tags($post->find('.userContent', 0)->innertext);
|
||||
if(strlen($title) > 64)
|
||||
$title = substr($title, 0, strpos(wordwrap($title, 64), "\n")) . '...';
|
||||
|
||||
@@ -677,10 +671,10 @@ EOD;
|
||||
}
|
||||
|
||||
//Build and add final item
|
||||
$item['uri'] = htmlspecialchars_decode($uri);
|
||||
$item['content'] = htmlspecialchars_decode($content);
|
||||
$item['title'] = $title;
|
||||
$item['author'] = $author;
|
||||
$item['uri'] = htmlspecialchars_decode($uri, ENT_QUOTES);
|
||||
$item['content'] = htmlspecialchars_decode($content, ENT_QUOTES);
|
||||
$item['title'] = htmlspecialchars_decode($title, ENT_QUOTES);
|
||||
$item['author'] = htmlspecialchars_decode($author, ENT_QUOTES);
|
||||
$item['timestamp'] = $date;
|
||||
|
||||
if(strpos($item['content'], '<img') === false) {
|
||||
|
@@ -94,7 +94,7 @@ class FilterBridge extends FeedExpander {
|
||||
}
|
||||
try{
|
||||
$this->collectExpandableDatas($this->getURI());
|
||||
} catch (HttpException $e) {
|
||||
} catch (Exception $e) {
|
||||
$this->collectExpandableDatas($this->getURI());
|
||||
}
|
||||
}
|
||||
|
@@ -37,10 +37,9 @@ class GithubIssueBridge extends BridgeAbstract {
|
||||
$name = $this->getInput('u') . '/' . $this->getInput('p');
|
||||
switch($this->queriedContext) {
|
||||
case 'Project Issues':
|
||||
$prefix = static::NAME . 's for ';
|
||||
if($this->getInput('c')) {
|
||||
$prefix = static::NAME . 's comments for ';
|
||||
} else {
|
||||
$prefix = static::NAME . 's for ';
|
||||
}
|
||||
$name = $prefix . $name;
|
||||
break;
|
||||
@@ -53,8 +52,9 @@ class GithubIssueBridge extends BridgeAbstract {
|
||||
}
|
||||
|
||||
public function getURI(){
|
||||
if(!is_null($this->getInput('u')) && !is_null($this->getInput('p'))) {
|
||||
$uri = static::URI . $this->getInput('u') . '/' . $this->getInput('p') . '/issues';
|
||||
if(null !== $this->getInput('u') && null !== $this->getInput('p')) {
|
||||
$uri = static::URI . $this->getInput('u') . '/'
|
||||
. $this->getInput('p') . '/issues';
|
||||
if($this->queriedContext === 'Issue comments') {
|
||||
$uri .= '/' . $this->getInput('i');
|
||||
} elseif($this->getInput('c')) {
|
||||
@@ -66,54 +66,54 @@ class GithubIssueBridge extends BridgeAbstract {
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
protected function extractIssueComment($issueNbr, $title, $comment){
|
||||
$class = $comment->getAttribute('class');
|
||||
$classes = explode(' ', $class);
|
||||
$event = false;
|
||||
if(in_array('discussion-item', $classes)) {
|
||||
$event = true;
|
||||
}
|
||||
|
||||
$author = 'unknown';
|
||||
if($comment->find('.author', 0)) {
|
||||
$author = $comment->find('.author', 0)->plaintext;
|
||||
}
|
||||
|
||||
$uri = static::URI . $this->getInput('u') . '/' . $this->getInput('p') . '/issues/' . $issueNbr;
|
||||
|
||||
protected function extractIssueEvent($issueNbr, $title, $comment){
|
||||
$comment = $comment->firstChild();
|
||||
if(!$event) {
|
||||
$comment = $comment->nextSibling();
|
||||
}
|
||||
$uri = static::URI . $this->getInput('u') . '/' . $this->getInput('p')
|
||||
. '/issues/' . $issueNbr . '#' . $comment->getAttribute('id');
|
||||
|
||||
if($event) {
|
||||
$title .= ' / ' . substr($class, strpos($class, 'discussion-item-') + strlen('discussion-item-'));
|
||||
if(!$comment->hasAttribute('id')) {
|
||||
$items = array();
|
||||
$timestamp = strtotime($comment->find('relative-time', 0)->getAttribute('datetime'));
|
||||
$content = $comment->innertext;
|
||||
while($comment = $comment->nextSibling()) {
|
||||
$item = array();
|
||||
$item['author'] = $author;
|
||||
$item['title'] = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
|
||||
$item['timestamp'] = $timestamp;
|
||||
$item['content'] = $content . '<p>' . $comment->children(1)->innertext . '</p>';
|
||||
$item['uri'] = $uri . '#' . $comment->children(1)->getAttribute('id');
|
||||
$items[] = $item;
|
||||
}
|
||||
return $items;
|
||||
$author = $comment->find('.author', 0)->plaintext;
|
||||
|
||||
$title .= ' / ' . trim($comment->plaintext);
|
||||
|
||||
$content = $title;
|
||||
if (null !== $comment->nextSibling()) {
|
||||
$content = $comment->nextSibling()->innertext;
|
||||
if ($comment->nextSibling()->nodeName() === 'span') {
|
||||
$content = $comment->nextSibling()->nextSibling()->innertext;
|
||||
}
|
||||
$content = $comment->parent()->innertext;
|
||||
} else {
|
||||
$title .= ' / ' . trim($comment->firstChild()->plaintext);
|
||||
$content = '<pre>' . $comment->find('.comment-body', 0)->innertext . '</pre>';
|
||||
}
|
||||
|
||||
$item = array();
|
||||
$item['author'] = $author;
|
||||
$item['uri'] = $uri . '#' . $comment->getAttribute('id');
|
||||
$item['uri'] = $uri;
|
||||
$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;
|
||||
return $item;
|
||||
}
|
||||
@@ -121,17 +121,29 @@ class GithubIssueBridge extends BridgeAbstract {
|
||||
protected function extractIssueComments($issue){
|
||||
$items = array();
|
||||
$title = $issue->find('.gh-header-title', 0)->plaintext;
|
||||
$issueNbr = trim(substr($issue->find('.gh-header-number', 0)->plaintext, 1));
|
||||
$issueNbr = trim(
|
||||
substr($issue->find('.gh-header-number', 0)->plaintext, 1)
|
||||
);
|
||||
$comments = $issue->find('.js-discussion', 0);
|
||||
foreach($comments->children() as $comment) {
|
||||
if (!$comment->hasChildNodes()) {
|
||||
continue;
|
||||
}
|
||||
$comment = $comment->firstChild();
|
||||
$classes = explode(' ', $comment->getAttribute('class'));
|
||||
if(in_array('discussion-item', $classes)
|
||||
|| in_array('timeline-comment-wrapper', $classes)) {
|
||||
if (in_array('timeline-comment-wrapper', $classes)) {
|
||||
$item = $this->extractIssueComment($issueNbr, $title, $comment);
|
||||
if(array_keys($item) !== range(0, count($item) - 1)) {
|
||||
$item = array($item);
|
||||
$items[] = $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;
|
||||
@@ -139,7 +151,9 @@ class GithubIssueBridge extends BridgeAbstract {
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('No results for Github Issue ' . $this->getURI());
|
||||
or returnServerError(
|
||||
'No results for Github Issue ' . $this->getURI()
|
||||
);
|
||||
|
||||
switch($this->queriedContext) {
|
||||
case 'Issue comments':
|
||||
@@ -148,31 +162,40 @@ class GithubIssueBridge extends BridgeAbstract {
|
||||
case 'Project Issues':
|
||||
foreach($html->find('.js-active-navigation-container .js-navigation-item') as $issue) {
|
||||
$info = $issue->find('.opened-by', 0);
|
||||
$issueNbr = substr(trim($info->plaintext), 1, strpos(trim($info->plaintext), ' '));
|
||||
$issueNbr = substr(
|
||||
trim($info->plaintext), 1, strpos(trim($info->plaintext), ' ')
|
||||
);
|
||||
|
||||
$item = array();
|
||||
$item['content'] = '';
|
||||
|
||||
if($this->getInput('c')) {
|
||||
$uri = static::URI . $this->getInput('u') . '/' . $this->getInput('p') . '/issues/' . $issueNbr;
|
||||
$uri = static::URI . $this->getInput('u')
|
||||
. '/' . $this->getInput('p') . '/issues/' . $issueNbr;
|
||||
$issue = getSimpleHTMLDOMCached($uri, static::CACHE_TIMEOUT);
|
||||
if($issue) {
|
||||
$this->items = array_merge($this->items, $this->extractIssueComments($issue));
|
||||
$this->items = array_merge(
|
||||
$this->items,
|
||||
$this->extractIssueComments($issue)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
$item['content'] = 'Can not extract comments from ' . $uri;
|
||||
}
|
||||
|
||||
$item['author'] = $info->find('a', 0)->plaintext;
|
||||
$item['timestamp'] = strtotime($info->find('relative-time', 0)->getAttribute('datetime'));
|
||||
$item['timestamp'] = strtotime(
|
||||
$info->find('relative-time', 0)->getAttribute('datetime')
|
||||
);
|
||||
$item['title'] = html_entity_decode(
|
||||
$issue->find('.js-navigation-open', 0)->plaintext,
|
||||
ENT_QUOTES,
|
||||
'UTF-8'
|
||||
);
|
||||
$comments = $issue->find('.col-5', 0)->plaintext;
|
||||
$comments = trim($issue->find('.col-5', 0)->plaintext);
|
||||
$item['content'] .= "\n" . 'Comments: ' . ($comments ? $comments : '0');
|
||||
$item['uri'] = self::URI . $issue->find('.js-navigation-open', 0)->getAttribute('href');
|
||||
$item['uri'] = self::URI
|
||||
. $issue->find('.js-navigation-open', 0)->getAttribute('href');
|
||||
$this->items[] = $item;
|
||||
}
|
||||
break;
|
||||
@@ -180,7 +203,11 @@ class GithubIssueBridge extends BridgeAbstract {
|
||||
|
||||
array_walk($this->items, function(&$item){
|
||||
$item['content'] = preg_replace('/\s+/', ' ', $item['content']);
|
||||
$item['content'] = str_replace('href="/', 'href="' . static::URI, $item['content']);
|
||||
$item['content'] = str_replace(
|
||||
'href="/',
|
||||
'href="' . static::URI,
|
||||
$item['content']
|
||||
);
|
||||
$item['content'] = str_replace(
|
||||
'href="#',
|
||||
'href="' . substr($item['uri'], 0, strpos($item['uri'], '#') + 1),
|
||||
|
0
bridges/GlassdoorBridge.php
Executable file → Normal file
0
bridges/GlassdoorBridge.php
Executable file → Normal file
@@ -132,7 +132,7 @@ class JustETFBridge extends BridgeAbstract {
|
||||
|
||||
date_time_set($df, 0, 0);
|
||||
|
||||
// debugMessage(date_format($df, 'U'));
|
||||
// Debug::log(date_format($df, 'U'));
|
||||
|
||||
return date_format($df, 'U');
|
||||
}
|
||||
@@ -210,7 +210,7 @@ class JustETFBridge extends BridgeAbstract {
|
||||
$element = $article->find('div.subheadline', 0)
|
||||
or returnServerError('Date not found!');
|
||||
|
||||
// debugMessage($element->plaintext);
|
||||
// Debug::log($element->plaintext);
|
||||
|
||||
$date = trim(explode('|', $element->plaintext)[0]);
|
||||
|
||||
@@ -223,7 +223,7 @@ class JustETFBridge extends BridgeAbstract {
|
||||
|
||||
$element->find('a', 0)->onclick = '';
|
||||
|
||||
// debugMessage($element->innertext);
|
||||
// Debug::log($element->innertext);
|
||||
|
||||
return $element->innertext;
|
||||
}
|
||||
@@ -288,7 +288,7 @@ class JustETFBridge extends BridgeAbstract {
|
||||
$element = $html->find('div.infobox div.vallabel', 0)
|
||||
or returnServerError('Date not found!');
|
||||
|
||||
// debugMessage($element->plaintext);
|
||||
// Debug::log($element->plaintext);
|
||||
|
||||
$date = trim(explode("\r\n", $element->plaintext)[1]);
|
||||
|
||||
|
28
bridges/MozillaSecurity.php
Normal file
28
bridges/MozillaSecurity.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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -53,7 +53,7 @@ class PixivBridge extends BridgeAbstract {
|
||||
|
||||
$url = str_replace('_master1200', '', $url);
|
||||
$url = str_replace('c/240x240/img-master/', 'img-original/', $url);
|
||||
$path = PATH_CACHE . '/pixiv_img';
|
||||
$path = PATH_CACHE . 'pixiv_img/';
|
||||
|
||||
if(!is_dir($path))
|
||||
mkdir($path, 0755, true);
|
||||
|
@@ -34,13 +34,13 @@ class SoundCloudBridge extends BridgeAbstract {
|
||||
|
||||
for($i = 0; $i < 10; $i++) {
|
||||
$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['content'] = '<audio src="'
|
||||
. $tracks[$i]->uri
|
||||
$item['timestamp'] = strtotime($tracks[$i]->created_at);
|
||||
$item['content'] = $tracks[$i]->description;
|
||||
$item['enclosures'] = array($tracks[$i]->uri
|
||||
. '/stream?client_id='
|
||||
. self::CLIENT_ID
|
||||
. '">';
|
||||
. self::CLIENT_ID);
|
||||
|
||||
$item['id'] = self::URI
|
||||
. urlencode($this->getInput('u'))
|
||||
|
@@ -66,6 +66,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(){
|
||||
switch($this->queriedContext) {
|
||||
case 'By keyword or hashtag':
|
||||
@@ -144,9 +179,9 @@ class TwitterBridge extends BridgeAbstract {
|
||||
|
||||
$item = array();
|
||||
// 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)
|
||||
$item['fullname'] = $tweet->getAttribute('data-name');
|
||||
$item['fullname'] = htmlspecialchars_decode($tweet->getAttribute('data-name'), ENT_QUOTES);
|
||||
// get author
|
||||
$item['author'] = $item['fullname'] . ' (@' . $item['username'] . ')';
|
||||
// get avatar link
|
||||
@@ -158,7 +193,8 @@ class TwitterBridge extends BridgeAbstract {
|
||||
// extract tweet timestamp
|
||||
$item['timestamp'] = $tweet->find('span.js-short-timestamp', 0)->getAttribute('data-time');
|
||||
// generate the title
|
||||
$item['title'] = strip_tags($this->fixAnchorSpacing($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) {
|
||||
case 'By list':
|
||||
@@ -258,16 +294,17 @@ EOD;
|
||||
}
|
||||
|
||||
$item['content'] = <<<EOD
|
||||
{$item['content']}
|
||||
<hr>
|
||||
<div style="display: inline-block; vertical-align: top;">
|
||||
<blockquote>{$cleanedQuotedTweet}</blockquote>
|
||||
</div>
|
||||
<div style="display: block; vertical-align: top;">
|
||||
<blockquote>{$quotedImage_html}</blockquote>
|
||||
</div>
|
||||
<hr>
|
||||
{$item['content']}
|
||||
EOD;
|
||||
}
|
||||
$item['content'] = htmlspecialchars_decode($item['content'], ENT_QUOTES);
|
||||
|
||||
// put out
|
||||
$this->items[] = $item;
|
||||
|
@@ -93,7 +93,7 @@ class WordPressBridge extends FeedExpander {
|
||||
}
|
||||
try{
|
||||
$this->collectExpandableDatas($this->getURI() . '/feed/atom/');
|
||||
} catch (HttpException $e) {
|
||||
} catch (Exception $e) {
|
||||
$this->collectExpandableDatas($this->getURI() . '/?feed=atom');
|
||||
}
|
||||
|
||||
|
@@ -74,10 +74,10 @@ class WordPressPluginUpdateBridge extends BridgeAbstract {
|
||||
}
|
||||
|
||||
private function getCachedDate($url){
|
||||
debugMessage('getting pubdate from url ' . $url . '');
|
||||
Debug::log('getting pubdate from url ' . $url . '');
|
||||
// Initialize cache
|
||||
$cache = Cache::create('FileCache');
|
||||
$cache->setPath(PATH_CACHE . '/pages');
|
||||
$cache->setPath(PATH_CACHE . 'pages/');
|
||||
$params = [$url];
|
||||
$cache->setParameters($params);
|
||||
// Get cachefile timestamp
|
||||
|
@@ -453,7 +453,7 @@ class XenForoBridge extends BridgeAbstract {
|
||||
|
||||
}
|
||||
|
||||
// debugMessage(date_format($df, 'U'));
|
||||
// Debug::log(date_format($df, 'U'));
|
||||
|
||||
return date_format($df, 'U');
|
||||
|
||||
|
@@ -115,6 +115,7 @@ class YGGTorrentBridge extends BridgeAbstract {
|
||||
$item = array();
|
||||
$item['timestamp'] = $row->find('.hidden', 1)->plaintext;
|
||||
$item['title'] = $row->find('a', 1)->plaintext;
|
||||
$item['uri'] = $row->find('a', 1)->href;
|
||||
$torrentData = $this->collectTorrentData($row->find('a', 1)->href);
|
||||
$item['author'] = $torrentData['author'];
|
||||
$item['content'] = $torrentData['content'];
|
||||
|
@@ -1,8 +1,15 @@
|
||||
<?php
|
||||
class ZoneTelechargementBridge extends BridgeAbstract {
|
||||
const NAME = 'Zone Telechargement';
|
||||
const URI = 'https://www.zone-telechargement1.org/';
|
||||
const DESCRIPTION = 'Suivi de série sur Zone Telechargement';
|
||||
|
||||
/* This bridge was initally done for the Website Zone Telechargement,
|
||||
* but the website changed it's name and URL.
|
||||
* Therefore, the class name and filename does not correspond to the
|
||||
* name of the bridge. This permits to keep the same RSS Feed URL.
|
||||
*/
|
||||
|
||||
const NAME = 'Annuaire Telechargement';
|
||||
const URI = 'https://www.annuaire-telechargement.com/';
|
||||
const DESCRIPTION = 'Suivi de série sur Annuaire Telechargement';
|
||||
const MAINTAINER = 'sysadminstory';
|
||||
const PARAMETERS = 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',
|
||||
'type' => 'text',
|
||||
'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'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
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(){
|
||||
|
124
index.php
124
index.php
@@ -1,35 +1,5 @@
|
||||
<?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';
|
||||
|
||||
// Specify path for whitelist file
|
||||
define('WHITELIST_FILE', __DIR__ . '/whitelist.txt');
|
||||
require_once __DIR__ . '/lib/rssbridge.php';
|
||||
|
||||
Configuration::verifyInstallation();
|
||||
Configuration::loadConfiguration();
|
||||
@@ -66,7 +36,7 @@ $whitelist_default = array(
|
||||
'DansTonChatBridge',
|
||||
'DuckDuckGoBridge',
|
||||
'FacebookBridge',
|
||||
'FlickrExploreBridge',
|
||||
'FlickrBridge',
|
||||
'GooglePlusPostBridge',
|
||||
'GoogleSearchBridge',
|
||||
'IdenticaBridge',
|
||||
@@ -80,26 +50,7 @@ $whitelist_default = array(
|
||||
|
||||
try {
|
||||
|
||||
Bridge::setDir(PATH_LIB_BRIDGES);
|
||||
Format::setDir(PATH_LIB_FORMATS);
|
||||
Cache::setDir(PATH_LIB_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);
|
||||
}
|
||||
Bridge::setWhitelist($whitelist_default);
|
||||
|
||||
$showInactive = filter_input(INPUT_GET, 'show_inactive', FILTER_VALIDATE_BOOLEAN);
|
||||
$action = array_key_exists('action', $params) ? $params['action'] : null;
|
||||
@@ -112,7 +63,7 @@ try {
|
||||
$list->bridges = array();
|
||||
$list->total = 0;
|
||||
|
||||
foreach(Bridge::listBridges() as $bridgeName) {
|
||||
foreach(Bridge::getBridgeNames() as $bridgeName) {
|
||||
|
||||
$bridge = Bridge::create($bridgeName);
|
||||
|
||||
@@ -126,7 +77,7 @@ try {
|
||||
|
||||
}
|
||||
|
||||
$status = Bridge::isWhitelisted($whitelist_selection, strtolower($bridgeName)) ? 'active' : 'inactive';
|
||||
$status = Bridge::isWhitelisted($bridgeName) ? 'active' : 'inactive';
|
||||
|
||||
$list->bridges[$bridgeName] = array(
|
||||
'status' => $status,
|
||||
@@ -145,13 +96,44 @@ try {
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode($list, JSON_PRETTY_PRINT);
|
||||
|
||||
} elseif($action === 'display' && !empty($bridge)) {
|
||||
// DEPRECATED: 'nameBridge' scheme is replaced by 'name' in bridge parameter values
|
||||
// this is to keep compatibility until futher complete removal
|
||||
if(($pos = strpos($bridge, 'Bridge')) === (strlen($bridge) - strlen('Bridge'))) {
|
||||
$bridge = substr($bridge, 0, $pos);
|
||||
} elseif($action === 'detect') {
|
||||
|
||||
$targetURL = $params['url']
|
||||
or returnClientError('You must specify a url!');
|
||||
|
||||
$format = $params['format']
|
||||
or returnClientError('You must specify a format!');
|
||||
|
||||
foreach(Bridge::getBridgeNames() as $bridgeName) {
|
||||
|
||||
if(!Bridge::isWhitelisted($bridgeName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$bridge = Bridge::create($bridgeName);
|
||||
|
||||
if($bridge === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$bridgeParams = $bridge->detectParameters($targetURL);
|
||||
|
||||
if(is_null($bridgeParams)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$bridgeParams['bridge'] = $bridgeName;
|
||||
$bridgeParams['format'] = $format;
|
||||
|
||||
header('Location: ?action=display&' . http_build_query($bridgeParams), true, 301);
|
||||
die();
|
||||
|
||||
}
|
||||
|
||||
returnClientError('No bridge found for given URL: ' . $targetURL);
|
||||
|
||||
} elseif($action === 'display' && !empty($bridge)) {
|
||||
|
||||
$format = $params['format']
|
||||
or returnClientError('You must specify a format!');
|
||||
|
||||
@@ -162,8 +144,8 @@ try {
|
||||
}
|
||||
|
||||
// whitelist control
|
||||
if(!Bridge::isWhitelisted($whitelist_selection, strtolower($bridge))) {
|
||||
throw new \HttpException('This bridge is not whitelisted', 401);
|
||||
if(!Bridge::isWhitelisted($bridge)) {
|
||||
throw new \Exception('This bridge is not whitelisted', 401);
|
||||
die;
|
||||
}
|
||||
|
||||
@@ -180,7 +162,10 @@ try {
|
||||
if(array_key_exists('_cache_timeout', $params)) {
|
||||
|
||||
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);
|
||||
@@ -228,7 +213,7 @@ try {
|
||||
|
||||
if($mtime !== false
|
||||
&& (time() - $cache_timeout < $mtime)
|
||||
&& (!defined('DEBUG') || DEBUG !== true)) { // Load cached data
|
||||
&& !Debug::isEnabled()) { // Load cached data
|
||||
|
||||
// Send "Not Modified" response if client supports it
|
||||
// Implementation based on https://stackoverflow.com/a/10847262
|
||||
@@ -275,7 +260,8 @@ try {
|
||||
$item['title'] = 'Bridge returned error ' . $e->getCode() . '! (' . $params['_error_time'] . ')';
|
||||
}
|
||||
|
||||
$item['uri'] = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) . '?' . http_build_query($params);
|
||||
$item['uri'] = (isset($_SERVER['REQUEST_URI']) ? parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) : '')
|
||||
. '?' . http_build_query($params);
|
||||
$item['timestamp'] = time();
|
||||
$item['content'] = buildBridgeException($e, $bridge);
|
||||
|
||||
@@ -288,7 +274,8 @@ try {
|
||||
// Create "new" error message every 24 hours
|
||||
$params['_error_time'] = urlencode((int)(time() / 86400));
|
||||
|
||||
$item['uri'] = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) . '?' . http_build_query($params);
|
||||
$item['uri'] = (isset($_SERVER['REQUEST_URI']) ? parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) : '')
|
||||
. '?' . http_build_query($params);
|
||||
$item['title'] = 'Bridge returned error ' . $e->getCode() . '! (' . $params['_error_time'] . ')';
|
||||
$item['timestamp'] = time();
|
||||
$item['content'] = buildBridgeException($e, $bridge);
|
||||
@@ -321,13 +308,10 @@ try {
|
||||
die(buildTransformException($e, $bridge));
|
||||
}
|
||||
} else {
|
||||
echo BridgeList::create($whitelist_selection, $showInactive);
|
||||
echo BridgeList::create($showInactive);
|
||||
}
|
||||
} catch(HttpException $e) {
|
||||
} catch(\Exception $e) {
|
||||
error_log($e);
|
||||
header('Content-Type: text/plain', true, $e->getCode());
|
||||
die($e->getMessage());
|
||||
} catch(\Exception $e) {
|
||||
error_log($e);
|
||||
die($e->getMessage());
|
||||
}
|
||||
|
@@ -1,6 +1,56 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
|
||||
* Atom feeds for websites that don't have one.
|
||||
*
|
||||
* For the full license information, please view the UNLICENSE file distributed
|
||||
* with this source code.
|
||||
*
|
||||
* @package Core
|
||||
* @license http://unlicense.org/ UNLICENSE
|
||||
* @link https://github.com/rss-bridge/rss-bridge
|
||||
*/
|
||||
|
||||
/**
|
||||
* Authentication module for RSS-Bridge.
|
||||
*
|
||||
* This class implements an authentication module for RSS-Bridge, utilizing the
|
||||
* HTTP authentication capabilities of PHP.
|
||||
*
|
||||
* _Notice_: Authentication via HTTP does not prevent users from accessing files
|
||||
* on your server. If your server supports `.htaccess`, you should globally restrict
|
||||
* access to files instead.
|
||||
*
|
||||
* @link https://php.net/manual/en/features.http-auth.php HTTP authentication with PHP
|
||||
* @link https://httpd.apache.org/docs/2.4/howto/htaccess.html Apache HTTP Server
|
||||
* Tutorial: .htaccess files
|
||||
*
|
||||
* @todo Configuration parameters should be stored internally instead of accessing
|
||||
* the configuration class directly.
|
||||
* @todo Add functions to detect if a user is authenticated or not. This can be
|
||||
* utilized for limiting access to authorized users only.
|
||||
*/
|
||||
class Authentication {
|
||||
|
||||
/**
|
||||
* Throw an exception when trying to create a new instance of this class.
|
||||
* Use {@see Authentication::showPromptIfNeeded()} instead!
|
||||
*
|
||||
* @throws \LogicException if called.
|
||||
*/
|
||||
public function __construct(){
|
||||
throw new \LogicException('Use ' . __CLASS__ . '::showPromptIfNeeded()!');
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests the user for login credentials if necessary.
|
||||
*
|
||||
* Responds to an authentication request or returns the `WWW-Authenticate`
|
||||
* header if authentication is enabled in the configuration of RSS-Bridge
|
||||
* (`[authentication] enable = true`).
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function showPromptIfNeeded() {
|
||||
|
||||
if(Configuration::getConfig('authentication', 'enable') === true) {
|
||||
@@ -13,6 +63,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() {
|
||||
|
||||
if(isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])) {
|
||||
|
308
lib/Bridge.php
308
lib/Bridge.php
@@ -1,88 +1,296 @@
|
||||
<?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 {
|
||||
|
||||
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(){
|
||||
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
|
||||
* @param string $nameBridge Defined bridge name you want use
|
||||
* @return Bridge object dedicated
|
||||
*/
|
||||
static public function create($nameBridge){
|
||||
if(!preg_match('@^[A-Z][a-zA-Z0-9-]*$@', $nameBridge)) {
|
||||
$message = <<<EOD
|
||||
'nameBridge' must start with one uppercase character followed or not by
|
||||
alphanumeric or dash characters!
|
||||
EOD;
|
||||
throw new \InvalidArgumentException($message);
|
||||
* Creates a new bridge object from the working directory.
|
||||
*
|
||||
* @throws \InvalidArgumentException if the requested bridge name is invalid.
|
||||
* @throws \Exception if the requested bridge doesn't exist in the working
|
||||
* directory.
|
||||
* @param string $name Name of the bridge object.
|
||||
* @return object|bool The bridge object or false if the class is not instantiable.
|
||||
*/
|
||||
public static function create($name){
|
||||
if(!self::isBridgeName($name)) {
|
||||
throw new \InvalidArgumentException('Bridge name invalid!');
|
||||
}
|
||||
|
||||
$nameBridge = $nameBridge . 'Bridge';
|
||||
$pathBridge = self::getDir() . $nameBridge . '.php';
|
||||
$name = self::sanitizeBridgeName($name) . 'Bridge';
|
||||
$filePath = self::getWorkingDir() . $name . '.php';
|
||||
|
||||
if(!file_exists($pathBridge)) {
|
||||
throw new \Exception('The bridge you looking for does not exist. It should be at path '
|
||||
. $pathBridge);
|
||||
if(!file_exists($filePath)) {
|
||||
throw new \Exception('Bridge file ' . $filePath . ' does not exist!');
|
||||
}
|
||||
|
||||
require_once $pathBridge;
|
||||
require_once $filePath;
|
||||
|
||||
if((new ReflectionClass($nameBridge))->isInstantiable()) {
|
||||
return new $nameBridge();
|
||||
if((new \ReflectionClass($name))->isInstantiable()) {
|
||||
return new $name();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static public function setDir($dirBridge){
|
||||
if(!is_string($dirBridge)) {
|
||||
throw new \InvalidArgumentException('Dir bridge must be a string.');
|
||||
/**
|
||||
* Sets the working directory.
|
||||
*
|
||||
* @param string $dir Path to the directory containing bridges.
|
||||
* @throws \LogicException if the provided path is not a valid string.
|
||||
* @throws \Exception if the provided path does not exist.
|
||||
* @throws \InvalidArgumentException if $dir is not a directory.
|
||||
* @return void
|
||||
*/
|
||||
public static function setWorkingDir($dir){
|
||||
self::$workingDir = null;
|
||||
|
||||
if(!is_string($dir)) {
|
||||
throw new \InvalidArgumentException('Working directory is not a valid string!');
|
||||
}
|
||||
|
||||
if(!file_exists($dirBridge)) {
|
||||
throw new \Exception('Dir bridge does not exist.');
|
||||
if(!file_exists($dir)) {
|
||||
throw new \Exception('Working directory does not exist!');
|
||||
}
|
||||
|
||||
self::$dirBridge = $dirBridge;
|
||||
}
|
||||
|
||||
static public function getDir(){
|
||||
if(is_null(self::$dirBridge)) {
|
||||
throw new \LogicException(__CLASS__ . ' class need to know bridge path !');
|
||||
if(!is_dir($dir)) {
|
||||
throw new \InvalidArgumentException('Working directory is not a directory!');
|
||||
}
|
||||
|
||||
return self::$dirBridge;
|
||||
self::$workingDir = realpath($dir) . '/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists the available bridges.
|
||||
* @return array List of the bridges
|
||||
*/
|
||||
static public function listBridges(){
|
||||
$listBridge = array();
|
||||
$dirFiles = scandir(self::getDir());
|
||||
* Returns the working directory.
|
||||
* The working directory must be specified with {@see Bridge::setWorkingDir()}!
|
||||
*
|
||||
* @throws \LogicException if the working directory is not set.
|
||||
* @return string The current working directory.
|
||||
*/
|
||||
public static function getWorkingDir(){
|
||||
if(is_null(self::$workingDir)) {
|
||||
throw new \LogicException('Working directory is not set!');
|
||||
}
|
||||
|
||||
if($dirFiles !== false) {
|
||||
foreach($dirFiles as $fileName) {
|
||||
if(preg_match('@^([^.]+)Bridge\.php$@U', $fileName, $out)) {
|
||||
$listBridge[] = $out[1];
|
||||
return self::$workingDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the provided name is a valid bridge name.
|
||||
*
|
||||
* A valid bridge name starts with a capital letter ([A-Z]), followed by
|
||||
* zero or more alphanumeric characters or hyphen ([A-Za-z0-9-]).
|
||||
*
|
||||
* @param string $name The bridge name.
|
||||
* @return bool true if the name is a valid bridge name, false otherwise.
|
||||
*/
|
||||
public static function isBridgeName($name){
|
||||
return is_string($name) && preg_match('/^[A-Z][a-zA-Z0-9-]*$/', $name) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of bridge names from the working directory.
|
||||
*
|
||||
* The list is cached internally to allow for successive calls.
|
||||
*
|
||||
* @return array List of bridge names
|
||||
*/
|
||||
public static function getBridgeNames(){
|
||||
|
||||
static $bridgeNames = array(); // Initialized on first call
|
||||
|
||||
if(empty($bridgeNames)) {
|
||||
$files = scandir(self::getWorkingDir());
|
||||
|
||||
if($files !== false) {
|
||||
foreach($files as $file) {
|
||||
if(preg_match('/^([^.]+)Bridge\.php$/U', $file, $out)) {
|
||||
$bridgeNames[] = $out[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $listBridge;
|
||||
return $bridgeNames;
|
||||
}
|
||||
|
||||
static public function isWhitelisted($whitelist, $name){
|
||||
return in_array($name, $whitelist)
|
||||
|| in_array($name . '.php', $whitelist)
|
||||
|| in_array($name . 'bridge', $whitelist) // DEPRECATED
|
||||
|| in_array($name . 'bridge.php', $whitelist) // DEPRECATED
|
||||
|| (count($whitelist) === 1 && trim($whitelist[0]) === '*');
|
||||
/**
|
||||
* Checks if a bridge is whitelisted.
|
||||
*
|
||||
* @param string $name Name of the bridge.
|
||||
* @return bool True if the bridge is whitelisted.
|
||||
*/
|
||||
public static function isWhitelisted($name){
|
||||
return in_array(self::sanitizeBridgeName($name), self::getWhitelist());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the whitelist.
|
||||
*
|
||||
* On first call this function reads the whitelist from {@see WHITELIST}.
|
||||
* * Each line in the file specifies one bridge on the whitelist.
|
||||
* * An empty file disables all bridges.
|
||||
* * If the file only only contains `*`, all bridges are whitelisted.
|
||||
*
|
||||
* Use {@see Bridge::setWhitelist()} to specify a default whitelist **before**
|
||||
* calling this function! The list is cached internally to allow for
|
||||
* successive calls. If {@see Bridge::setWhitelist()} gets called after this
|
||||
* function, the whitelist is **not** updated again!
|
||||
*
|
||||
* @return array Array of whitelisted bridges
|
||||
*/
|
||||
public static function getWhitelist() {
|
||||
|
||||
static $firstCall = true; // Initialized on first call
|
||||
|
||||
if($firstCall) {
|
||||
|
||||
// Create initial whitelist or load from disk
|
||||
if (!file_exists(WHITELIST) && !empty(self::$whitelist)) {
|
||||
file_put_contents(WHITELIST, implode("\n", self::$whitelist));
|
||||
} else {
|
||||
|
||||
$contents = trim(file_get_contents(WHITELIST));
|
||||
|
||||
if($contents === '*') { // Whitelist all bridges
|
||||
self::$whitelist = self::getBridgeNames();
|
||||
} else {
|
||||
self::$whitelist = array_map('self::sanitizeBridgeName', explode("\n", $contents));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return self::$whitelist;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the (default) whitelist.
|
||||
*
|
||||
* If this function is called **before** {@see Bridge::getWhitelist()}, the
|
||||
* provided whitelist will be replaced by a custom whitelist specified in
|
||||
* {@see WHITELIST} (if it exists).
|
||||
*
|
||||
* If this function is called **after** {@see Bridge::getWhitelist()}, the
|
||||
* provided whitelist is taken as is (not updated by the custom whitelist
|
||||
* again).
|
||||
*
|
||||
* @param array $default The whitelist as array of bridge names.
|
||||
* @return void
|
||||
*/
|
||||
public static function setWhitelist($default = array()) {
|
||||
self::$whitelist = array_map('self::sanitizeBridgeName', $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sanitized bridge name.
|
||||
*
|
||||
* The bridge name can be specified in various ways:
|
||||
* * The PHP file name (i.e. `GitHubIssueBridge.php`)
|
||||
* * The PHP file name without file extension (i.e. `GitHubIssueBridge`)
|
||||
* * The bridge name (i.e. `GitHubIssue`)
|
||||
*
|
||||
* Casing is ignored (i.e. `GITHUBISSUE` and `githubissue` are the same).
|
||||
*
|
||||
* A bridge file matching the given bridge name must exist in the working
|
||||
* directory!
|
||||
*
|
||||
* @param string $name The bridge name
|
||||
* @return string|null The sanitized bridge name if the provided name is
|
||||
* valid, null otherwise.
|
||||
*/
|
||||
protected static function sanitizeBridgeName($name) {
|
||||
|
||||
if(is_string($name)) {
|
||||
|
||||
// Trim trailing '.php' if exists
|
||||
if(preg_match('/(.+)(?:\.php)/', $name, $matches)) {
|
||||
$name = $matches[1];
|
||||
}
|
||||
|
||||
// Trim trailing 'Bridge' if exists
|
||||
if(preg_match('/(.+)(?:Bridge)/i', $name, $matches)) {
|
||||
$name = $matches[1];
|
||||
}
|
||||
|
||||
// The name is valid if a corresponding bridge file is found on disk
|
||||
if(in_array(strtolower($name), array_map('strtolower', self::getBridgeNames()))) {
|
||||
$index = array_search(strtolower($name), array_map('strtolower', self::getBridgeNames()));
|
||||
return self::getBridgeNames()[$index];
|
||||
}
|
||||
|
||||
Debug::log('Invalid bridge name specified: "' . $name . '"!');
|
||||
|
||||
}
|
||||
|
||||
return null; // Bad parameter
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -1,32 +1,112 @@
|
||||
<?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 {
|
||||
|
||||
/**
|
||||
* Name of the bridge
|
||||
*
|
||||
* Use {@see BridgeAbstract::getName()} to read this parameter
|
||||
*/
|
||||
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
|
||||
* @return mixed
|
||||
*/
|
||||
* URI to the site the bridge is intended to be used for.
|
||||
*
|
||||
* Use {@see BridgeAbstract::getURI()} to read this parameter
|
||||
*/
|
||||
const URI = '';
|
||||
|
||||
/**
|
||||
* A brief description of what the bridge can do
|
||||
*
|
||||
* Use {@see BridgeAbstract::getDescription()} to read this parameter
|
||||
*/
|
||||
const DESCRIPTION = 'No description provided';
|
||||
|
||||
/**
|
||||
* The name of the maintainer. Multiple maintainers can be separated by comma
|
||||
*
|
||||
* Use {@see BridgeAbstract::getMaintainer()} to read this parameter
|
||||
*/
|
||||
const MAINTAINER = 'No maintainer';
|
||||
|
||||
/**
|
||||
* The default cache timeout for the bridge
|
||||
*
|
||||
* Use {@see BridgeAbstract::getCacheTimeout()} to read this parameter
|
||||
*/
|
||||
const CACHE_TIMEOUT = 3600;
|
||||
|
||||
/**
|
||||
* Parameters for the bridge
|
||||
*
|
||||
* Use {@see BridgeAbstract::getParameters()} to read this parameter
|
||||
*/
|
||||
const PARAMETERS = array();
|
||||
|
||||
/**
|
||||
* Holds the list of items collected by the bridge
|
||||
*
|
||||
* Items must be collected by {@see BridgeInterface::collectData()}
|
||||
*
|
||||
* Use {@see BridgeAbstract::getItems()} to access items.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $items = array();
|
||||
|
||||
/**
|
||||
* Holds the list of input parameters used by the bridge
|
||||
*
|
||||
* Do not access this parameter directly!
|
||||
* Use {@see BridgeAbstract::setInputs()} and {@see BridgeAbstract::getInput()} instead!
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $inputs = array();
|
||||
|
||||
/**
|
||||
* Holds the name of the queried context
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $queriedContext = '';
|
||||
|
||||
/** {@inheritdoc} */
|
||||
public function getItems(){
|
||||
return $this->items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the input values for a given context. Existing values are
|
||||
* overwritten.
|
||||
* Sets the input values for a given context.
|
||||
*
|
||||
* @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){
|
||||
// Import and assign all inputs to their context
|
||||
@@ -103,9 +183,15 @@ abstract class BridgeAbstract implements BridgeInterface {
|
||||
}
|
||||
|
||||
/**
|
||||
* Defined datas with parameters depending choose bridge
|
||||
* @param array array with expected bridge paramters
|
||||
*/
|
||||
* Set inputs for the bridge
|
||||
*
|
||||
* Returns errors and aborts execution if the provided input parameters are
|
||||
* invalid.
|
||||
*
|
||||
* @param array List of input parameters. Each element in this list must
|
||||
* relate to an item in {@see BridgeAbstract::PARAMETERS}
|
||||
* @return void
|
||||
*/
|
||||
public function setDatas(array $inputs){
|
||||
|
||||
if(empty(static::PARAMETERS)) {
|
||||
@@ -148,7 +234,7 @@ abstract class BridgeAbstract implements BridgeInterface {
|
||||
* Returns the value for the provided input
|
||||
*
|
||||
* @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){
|
||||
if(!isset($this->inputs[$this->queriedContext][$input]['value'])) {
|
||||
@@ -157,32 +243,52 @@ abstract class BridgeAbstract implements BridgeInterface {
|
||||
return $this->inputs[$this->queriedContext][$input]['value'];
|
||||
}
|
||||
|
||||
/** {@inheritdoc} */
|
||||
public function getDescription(){
|
||||
return static::DESCRIPTION;
|
||||
}
|
||||
|
||||
/** {@inheritdoc} */
|
||||
public function getMaintainer(){
|
||||
return static::MAINTAINER;
|
||||
}
|
||||
|
||||
/** {@inheritdoc} */
|
||||
public function getName(){
|
||||
return static::NAME;
|
||||
}
|
||||
|
||||
/** {@inheritdoc} */
|
||||
public function getIcon(){
|
||||
return '';
|
||||
}
|
||||
|
||||
/** {@inheritdoc} */
|
||||
public function getParameters(){
|
||||
return static::PARAMETERS;
|
||||
}
|
||||
|
||||
/** {@inheritdoc} */
|
||||
public function getURI(){
|
||||
return static::URI;
|
||||
}
|
||||
|
||||
/** {@inheritdoc} */
|
||||
public function getCacheTimeout(){
|
||||
return static::CACHE_TIMEOUT;
|
||||
}
|
||||
|
||||
/** {@inheritdoc} */
|
||||
public function detectParameters($url){
|
||||
$regex = '/^(https?:\/\/)?(www\.)?(.+?)(\/)?$/';
|
||||
if(empty(static::PARAMETERS)
|
||||
&& preg_match($regex, $url, $urlMatches) > 0
|
||||
&& preg_match($regex, static::URI, $bridgeUriMatches) > 0
|
||||
&& $urlMatches[3] === $bridgeUriMatches[3]) {
|
||||
return array();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,6 +1,32 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
|
||||
* Atom feeds for websites that don't have one.
|
||||
*
|
||||
* For the full license information, please view the UNLICENSE file distributed
|
||||
* with this source code.
|
||||
*
|
||||
* @package Core
|
||||
* @license http://unlicense.org/ UNLICENSE
|
||||
* @link https://github.com/rss-bridge/rss-bridge
|
||||
*/
|
||||
|
||||
/**
|
||||
* A generator class for a single bridge card on the home page of RSS-Bridge.
|
||||
*
|
||||
* This class generates the HTML content for a single bridge card for the home
|
||||
* page of RSS-Bridge.
|
||||
*
|
||||
* @todo Return error if a caller creates an object of this class.
|
||||
*/
|
||||
final class BridgeCard {
|
||||
|
||||
/**
|
||||
* Build a HTML document string of buttons for each of the provided formats
|
||||
*
|
||||
* @param array $formats A list of format names
|
||||
* @return string The document string
|
||||
*/
|
||||
private static function buildFormatButtons($formats) {
|
||||
$buttons = '';
|
||||
|
||||
@@ -16,6 +42,13 @@ final class BridgeCard {
|
||||
return $buttons;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the form header for a bridge card
|
||||
*
|
||||
* @param string $bridgeName The bridge name
|
||||
* @param bool $isHttps If disabled, adds a warning to the form
|
||||
* @return string The form header
|
||||
*/
|
||||
private static function getFormHeader($bridgeName, $isHttps = false) {
|
||||
$form = <<<EOD
|
||||
<form method="GET" action="?">
|
||||
@@ -31,13 +64,24 @@ This bridge is not fetching its content through a secure connection</div>';
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the form body for a bridge
|
||||
*
|
||||
* @param string $bridgeName The bridge name
|
||||
* @param array $formats A list of supported formats
|
||||
* @param bool $isActive Indicates if a bridge is enabled or not
|
||||
* @param bool $isHttps Indicates if a bridge uses HTTPS or not
|
||||
* @param string $parameterName Sets the bridge context for the current form
|
||||
* @param array $parameters The bridge parameters
|
||||
* @return string The form body
|
||||
*/
|
||||
private static function getForm($bridgeName,
|
||||
$formats,
|
||||
$isActive = false,
|
||||
$isHttps = false,
|
||||
$parameterName = '',
|
||||
$parameters = array()) {
|
||||
$form = BridgeCard::getFormHeader($bridgeName, $isHttps);
|
||||
$form = self::getFormHeader($bridgeName, $isHttps);
|
||||
|
||||
if(count($parameters) > 0) {
|
||||
|
||||
@@ -65,13 +109,13 @@ This bridge is not fetching its content through a secure connection</div>';
|
||||
. PHP_EOL;
|
||||
|
||||
if(!isset($inputEntry['type']) || $inputEntry['type'] === 'text') {
|
||||
$form .= BridgeCard::getTextInput($inputEntry, $idArg, $id);
|
||||
$form .= self::getTextInput($inputEntry, $idArg, $id);
|
||||
} elseif($inputEntry['type'] === 'number') {
|
||||
$form .= BridgeCard::getNumberInput($inputEntry, $idArg, $id);
|
||||
$form .= self::getNumberInput($inputEntry, $idArg, $id);
|
||||
} else if($inputEntry['type'] === 'list') {
|
||||
$form .= BridgeCard::getListInput($inputEntry, $idArg, $id);
|
||||
$form .= self::getListInput($inputEntry, $idArg, $id);
|
||||
} elseif($inputEntry['type'] === 'checkbox') {
|
||||
$form .= BridgeCard::getCheckboxInput($inputEntry, $idArg, $id);
|
||||
$form .= self::getCheckboxInput($inputEntry, $idArg, $id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +124,7 @@ This bridge is not fetching its content through a secure connection</div>';
|
||||
}
|
||||
|
||||
if($isActive) {
|
||||
$form .= BridgeCard::buildFormatButtons($formats);
|
||||
$form .= self::buildFormatButtons($formats);
|
||||
} else {
|
||||
$form .= '<span style="font-weight: bold;">Inactive</span>';
|
||||
}
|
||||
@@ -88,6 +132,12 @@ This bridge is not fetching its content through a secure connection</div>';
|
||||
return $form . '</form>' . PHP_EOL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get input field attributes
|
||||
*
|
||||
* @param array $entry The current entry
|
||||
* @return string The input field attributes
|
||||
*/
|
||||
private static function getInputAttributes($entry) {
|
||||
$retVal = '';
|
||||
|
||||
@@ -103,9 +153,17 @@ This bridge is not fetching its content through a secure connection</div>';
|
||||
return $retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get text input
|
||||
*
|
||||
* @param array $entry The current entry
|
||||
* @param string $id The field ID
|
||||
* @param string $name The field name
|
||||
* @return string The text input field
|
||||
*/
|
||||
private static function getTextInput($entry, $id, $name) {
|
||||
return '<input '
|
||||
. BridgeCard::getInputAttributes($entry)
|
||||
. self::getInputAttributes($entry)
|
||||
. ' id="'
|
||||
. $id
|
||||
. '" type="text" value="'
|
||||
@@ -118,9 +176,17 @@ This bridge is not fetching its content through a secure connection</div>';
|
||||
. PHP_EOL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get number input
|
||||
*
|
||||
* @param array $entry The current entry
|
||||
* @param string $id The field ID
|
||||
* @param string $name The field name
|
||||
* @return string The number input field
|
||||
*/
|
||||
private static function getNumberInput($entry, $id, $name) {
|
||||
return '<input '
|
||||
. BridgeCard::getInputAttributes($entry)
|
||||
. self::getInputAttributes($entry)
|
||||
. ' id="'
|
||||
. $id
|
||||
. '" type="number" value="'
|
||||
@@ -133,9 +199,17 @@ This bridge is not fetching its content through a secure connection</div>';
|
||||
. PHP_EOL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list input
|
||||
*
|
||||
* @param array $entry The current entry
|
||||
* @param string $id The field ID
|
||||
* @param string $name The field name
|
||||
* @return string The list input field
|
||||
*/
|
||||
private static function getListInput($entry, $id, $name) {
|
||||
$list = '<select '
|
||||
. BridgeCard::getInputAttributes($entry)
|
||||
. self::getInputAttributes($entry)
|
||||
. ' id="'
|
||||
. $id
|
||||
. '" name="'
|
||||
@@ -185,9 +259,17 @@ This bridge is not fetching its content through a secure connection</div>';
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get checkbox input
|
||||
*
|
||||
* @param array $entry The current entry
|
||||
* @param string $id The field ID
|
||||
* @param string $name The field name
|
||||
* @return string The checkbox input field
|
||||
*/
|
||||
private static function getCheckboxInput($entry, $id, $name) {
|
||||
return '<input '
|
||||
. BridgeCard::getInputAttributes($entry)
|
||||
. self::getInputAttributes($entry)
|
||||
. ' id="'
|
||||
. $id
|
||||
. '" type="checkbox" name="'
|
||||
@@ -198,6 +280,14 @@ This bridge is not fetching its content through a secure connection</div>';
|
||||
. PHP_EOL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a single bridge card
|
||||
*
|
||||
* @param string $bridgeName The bridge name
|
||||
* @param array $formats A list of formats
|
||||
* @param bool $isActive Indicates if the bridge is active or not
|
||||
* @return string The bridge card
|
||||
*/
|
||||
static function displayBridgeCard($bridgeName, $formats, $isActive = true){
|
||||
|
||||
$bridge = Bridge::create($bridgeName);
|
||||
@@ -240,7 +330,7 @@ CARD;
|
||||
if(count($parameters) === 0
|
||||
|| count($parameters) === 1 && array_key_exists('global', $parameters)) {
|
||||
|
||||
$card .= BridgeCard::getForm($bridgeName, $formats, $isActive, $isHttps);
|
||||
$card .= self::getForm($bridgeName, $formats, $isActive, $isHttps);
|
||||
|
||||
} else {
|
||||
|
||||
@@ -254,7 +344,7 @@ CARD;
|
||||
if(!is_numeric($parameterName))
|
||||
$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,4 +1,57 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
|
||||
* Atom feeds for websites that don't have one.
|
||||
*
|
||||
* For the full license information, please view the UNLICENSE file distributed
|
||||
* with this source code.
|
||||
*
|
||||
* @package Core
|
||||
* @license http://unlicense.org/ UNLICENSE
|
||||
* @link https://github.com/rss-bridge/rss-bridge
|
||||
*/
|
||||
|
||||
/**
|
||||
* The bridge interface
|
||||
*
|
||||
* A bridge is a class that is responsible for collecting and transforming data
|
||||
* from one hosting provider into an internal representation of feed data, that
|
||||
* can later be transformed into different feed formats (see {@see FormatInterface}).
|
||||
*
|
||||
* For this purpose, all bridges need to perform three common operations:
|
||||
*
|
||||
* 1. Collect data from a remote site.
|
||||
* 2. Extract the required contents.
|
||||
* 3. Add the contents to the internal data structure.
|
||||
*
|
||||
* Bridges can optionally specify parameters to customize bridge behavior based
|
||||
* on user input. For example, a user could specify how many items to return in
|
||||
* the feed and where to get them.
|
||||
*
|
||||
* In order to present a bridge on the home page, and for the purpose of bridge
|
||||
* specific behaviour, additional information must be provided by the bridge:
|
||||
*
|
||||
* * **Name**
|
||||
* The name of the bridge that can be displayed to users.
|
||||
*
|
||||
* * **Description**
|
||||
* A brief description for the bridge that can be displayed to users.
|
||||
*
|
||||
* * **URI**
|
||||
* A link to the hosting provider.
|
||||
*
|
||||
* * **Maintainer**
|
||||
* The GitHub username of the bridge maintainer
|
||||
*
|
||||
* * **Parameters**
|
||||
* A list of parameters for customization
|
||||
*
|
||||
* * **Icon**
|
||||
* A link to the favicon of the hosting provider
|
||||
*
|
||||
* * **Cache timeout**
|
||||
* The default cache timeout for the bridge.
|
||||
*/
|
||||
interface BridgeInterface {
|
||||
|
||||
/**
|
||||
@@ -61,4 +114,12 @@ interface BridgeInterface {
|
||||
* @return int Cache timeout
|
||||
*/
|
||||
public function getCacheTimeout();
|
||||
|
||||
/**
|
||||
* Returns parameters from given URL or null if URL is not applicable
|
||||
*
|
||||
* @param string $url URL to extract parameters from
|
||||
* @return array|null List of bridge parameters or null if detection failed.
|
||||
*/
|
||||
public function detectParameters($url);
|
||||
}
|
||||
|
@@ -1,6 +1,31 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
|
||||
* Atom feeds for websites that don't have one.
|
||||
*
|
||||
* For the full license information, please view the UNLICENSE file distributed
|
||||
* with this source code.
|
||||
*
|
||||
* @package Core
|
||||
* @license http://unlicense.org/ UNLICENSE
|
||||
* @link https://github.com/rss-bridge/rss-bridge
|
||||
*/
|
||||
|
||||
/**
|
||||
* A generator class for the home page of RSS-Bridge.
|
||||
*
|
||||
* This class generates the HTML content for displaying all bridges on the home
|
||||
* page of RSS-Bridge.
|
||||
*
|
||||
* @todo Return error if a caller creates an object of this class.
|
||||
*/
|
||||
final class BridgeList {
|
||||
|
||||
/**
|
||||
* Get the document head
|
||||
*
|
||||
* @return string The document head
|
||||
*/
|
||||
private static function getHead() {
|
||||
return <<<EOD
|
||||
<head>
|
||||
@@ -22,20 +47,29 @@ final class BridgeList {
|
||||
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 = '';
|
||||
$totalActiveBridges = 0;
|
||||
$inactiveBridges = '';
|
||||
|
||||
$bridgeList = Bridge::listBridges();
|
||||
$formats = Format::searchInformation();
|
||||
$bridgeList = Bridge::getBridgeNames();
|
||||
$formats = Format::getFormatNames();
|
||||
|
||||
$totalBridges = count($bridgeList);
|
||||
|
||||
foreach($bridgeList as $bridgeName) {
|
||||
|
||||
if(Bridge::isWhitelisted($whitelist, strtolower($bridgeName))) {
|
||||
if(Bridge::isWhitelisted($bridgeName)) {
|
||||
|
||||
$body .= BridgeCard::displayBridgeCard($bridgeName, $formats);
|
||||
$totalActiveBridges++;
|
||||
@@ -54,19 +88,24 @@ EOD;
|
||||
return $body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the document header
|
||||
*
|
||||
* @return string The document header
|
||||
*/
|
||||
private static function getHeader() {
|
||||
$warning = '';
|
||||
|
||||
if(defined('DEBUG') && DEBUG === true) {
|
||||
if(defined('DEBUG_INSECURE') && DEBUG_INSECURE === true) {
|
||||
if(Debug::isEnabled()) {
|
||||
if(!Debug::isSecure()) {
|
||||
$warning .= <<<EOD
|
||||
<section class="critical-warning">Warning : Debug mode is active from any location,
|
||||
make sure only you can access RSS-Bridge.</section>
|
||||
<section class="critical-warning">Warning : Debug mode is active from any location,
|
||||
make sure only you can access RSS-Bridge.</section>
|
||||
EOD;
|
||||
} else {
|
||||
$warning .= <<<EOD
|
||||
<section class="warning">Warning : Debug mode is active from your IP address,
|
||||
your requests will bypass the cache.</section>
|
||||
<section class="warning">Warning : Debug mode is active from your IP address,
|
||||
your requests will bypass the cache.</section>
|
||||
EOD;
|
||||
}
|
||||
}
|
||||
@@ -80,6 +119,11 @@ EOD;
|
||||
EOD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the searchbar
|
||||
*
|
||||
* @return string The searchbar
|
||||
*/
|
||||
private static function getSearchbar() {
|
||||
$query = filter_input(INPUT_GET, 'q');
|
||||
|
||||
@@ -93,6 +137,16 @@ EOD;
|
||||
EOD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the document footer
|
||||
*
|
||||
* @param int $totalBridges The total number of bridges, shown in the footer
|
||||
* @param int $totalActiveBridges The total number of active bridges, shown
|
||||
* in the footer.
|
||||
* @param bool $showInactive Sets the 'Show active'/'Show inactive' text in
|
||||
* the footer.
|
||||
* @return string The document footer
|
||||
*/
|
||||
private static function getFooter($totalBridges, $totalActiveBridges, $showInactive) {
|
||||
$version = Configuration::getVersion();
|
||||
|
||||
@@ -131,7 +185,14 @@ 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;
|
||||
$totalActiveBridges = 0;
|
||||
@@ -141,7 +202,7 @@ EOD;
|
||||
. '<body onload="search()">'
|
||||
. BridgeList::getHeader()
|
||||
. BridgeList::getSearchbar()
|
||||
. BridgeList::getBridges($whitelist, $showInactive, $totalBridges, $totalActiveBridges)
|
||||
. BridgeList::getBridges($showInactive, $totalBridges, $totalActiveBridges)
|
||||
. BridgeList::getFooter($totalBridges, $totalActiveBridges, $showInactive)
|
||||
. '</body></html>';
|
||||
|
||||
|
137
lib/Cache.php
137
lib/Cache.php
@@ -1,53 +1,140 @@
|
||||
<?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 {
|
||||
|
||||
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(){
|
||||
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)) {
|
||||
throw new \InvalidArgumentException('Name cache must be at least one
|
||||
uppercase follow or not by alphanumeric or dash characters.');
|
||||
/**
|
||||
* Creates a new cache object from the working directory.
|
||||
*
|
||||
* @throws \InvalidArgumentException if the requested cache name is invalid.
|
||||
* @throws \Exception if the requested cache file doesn't exist in the
|
||||
* working directory.
|
||||
* @param string $name Name of the cache object.
|
||||
* @return object|bool The cache object or false if the class is not instantiable.
|
||||
*/
|
||||
public static function create($name){
|
||||
if(!self::isCacheName($name)) {
|
||||
throw new \InvalidArgumentException('Cache name invalid!');
|
||||
}
|
||||
|
||||
$pathCache = self::getDir() . $nameCache . '.php';
|
||||
$filePath = self::getWorkingDir() . $name . '.php';
|
||||
|
||||
if(!file_exists($pathCache)) {
|
||||
throw new \Exception('The cache you looking for does not exist.');
|
||||
if(!file_exists($filePath)) {
|
||||
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)) {
|
||||
throw new \InvalidArgumentException('Dir cache must be a string.');
|
||||
/**
|
||||
* Sets the working directory.
|
||||
*
|
||||
* @param string $dir Path to a directory containing cache classes
|
||||
* @throws \InvalidArgumentException if $dir is not a string.
|
||||
* @throws \Exception if the working directory doesn't exist.
|
||||
* @throws \InvalidArgumentException if $dir is not a directory.
|
||||
* @return void
|
||||
*/
|
||||
public static function setWorkingDir($dir){
|
||||
self::$workingDir = null;
|
||||
|
||||
if(!is_string($dir)) {
|
||||
throw new \InvalidArgumentException('Working directory is not a valid string!');
|
||||
}
|
||||
|
||||
if(!file_exists($dirCache)) {
|
||||
throw new \Exception('Dir cache does not exist.');
|
||||
if(!file_exists($dir)) {
|
||||
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;
|
||||
|
||||
if(is_null($dirCache)) {
|
||||
throw new \LogicException(__CLASS__ . ' class need to know cache path !');
|
||||
/**
|
||||
* Returns the working directory.
|
||||
* The working directory must be set with {@see Cache::setWorkingDir()}!
|
||||
*
|
||||
* @throws \LogicException if the working directory is not set.
|
||||
* @return string The current working directory.
|
||||
*/
|
||||
public static function getWorkingDir(){
|
||||
if(is_null(self::$workingDir)) {
|
||||
throw new \LogicException('Working directory is not set!');
|
||||
}
|
||||
|
||||
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,51 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
|
||||
* Atom feeds for websites that don't have one.
|
||||
*
|
||||
* For the full license information, please view the UNLICENSE file distributed
|
||||
* with this source code.
|
||||
*
|
||||
* @package Core
|
||||
* @license http://unlicense.org/ UNLICENSE
|
||||
* @link https://github.com/rss-bridge/rss-bridge
|
||||
*/
|
||||
|
||||
/**
|
||||
* The cache interface
|
||||
*
|
||||
* @todo Add missing function to the interface
|
||||
* @todo Explain parameters and return values in more detail
|
||||
* @todo Return self more often (to allow call chaining)
|
||||
*/
|
||||
interface CacheInterface {
|
||||
|
||||
/**
|
||||
* Loads data from cache
|
||||
*
|
||||
* @return mixed The cache data
|
||||
*/
|
||||
public function loadData();
|
||||
|
||||
/**
|
||||
* Stores data to the cache
|
||||
*
|
||||
* @param mixed $datas The data to store
|
||||
* @return self The cache object
|
||||
*/
|
||||
public function saveData($datas);
|
||||
|
||||
/**
|
||||
* Returns the timestamp for the curent cache file
|
||||
*
|
||||
* @return int Timestamp
|
||||
*/
|
||||
public function getTime();
|
||||
|
||||
/**
|
||||
* Removes any data that is older than the specified duration from cache
|
||||
*
|
||||
* @param int $duration The cache duration in seconds
|
||||
*/
|
||||
public function purgeCache($duration);
|
||||
}
|
||||
|
@@ -1,10 +1,81 @@
|
||||
<?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 = '2018-11-10';
|
||||
/**
|
||||
* 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 = '2018-12-11';
|
||||
|
||||
/**
|
||||
* Holds the configuration data.
|
||||
*
|
||||
* Do not access this property directly!
|
||||
* Use {@see Configuration::getConfig()} instead.
|
||||
*
|
||||
* @var array|null
|
||||
*/
|
||||
private static $config = null;
|
||||
|
||||
/**
|
||||
* Throw an exception when trying to create a new instance of this class.
|
||||
*
|
||||
* @throws \LogicException if called.
|
||||
*/
|
||||
public function __construct(){
|
||||
throw new \LogicException('Can\'t create object of this class!');
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the current installation of RSS-Bridge and PHP.
|
||||
*
|
||||
* Returns an error message and aborts execution if the installation does
|
||||
* not satisfy the requirements of RSS-Bridge.
|
||||
*
|
||||
* **Requirements**
|
||||
* - PHP 5.6.0 or higher
|
||||
* - `openssl` extension
|
||||
* - `libxml` extension
|
||||
* - `mbstring` extension
|
||||
* - `simplexml` extension
|
||||
* - `curl` extension
|
||||
* - `json` extension
|
||||
* - The cache folder specified by {@see PATH_CACHE} requires write permission
|
||||
* - The whitelist file specified by {@see WHITELIST} requires write permission
|
||||
*
|
||||
* @link http://php.net/supported-versions.php PHP Supported Versions
|
||||
* @link http://php.net/manual/en/book.openssl.php OpenSSL
|
||||
* @link http://php.net/manual/en/book.libxml.php libxml
|
||||
* @link http://php.net/manual/en/book.mbstring.php Multibyte String (mbstring)
|
||||
* @link http://php.net/manual/en/book.simplexml.php SimpleXML
|
||||
* @link http://php.net/manual/en/book.curl.php Client URL Library (curl)
|
||||
* @link http://php.net/manual/en/book.json.php JavaScript Object Notation (json)
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function verifyInstallation() {
|
||||
|
||||
// Check PHP version
|
||||
@@ -34,24 +105,50 @@ class Configuration {
|
||||
if(!is_writable(PATH_CACHE))
|
||||
die('RSS-Bridge does not have write permissions for ' . PATH_CACHE . '!');
|
||||
|
||||
// Check whitelist file permissions (only in DEBUG mode)
|
||||
if(!file_exists(WHITELIST_FILE) && !is_writable(dirname(WHITELIST_FILE)))
|
||||
die('RSS-Bridge does not have write permissions for ' . WHITELIST_FILE . '!');
|
||||
// Check whitelist file permissions
|
||||
if(!file_exists(WHITELIST) && !is_writable(dirname(WHITELIST)))
|
||||
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() {
|
||||
|
||||
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!');
|
||||
|
||||
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)
|
||||
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
|
||||
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) {
|
||||
// Skip unknown sections and keys
|
||||
if(array_key_exists($header, Configuration::$config) && array_key_exists($key, Configuration::$config[$header])) {
|
||||
@@ -64,22 +161,27 @@ class Configuration {
|
||||
if(!is_string(self::getConfig('proxy', 'url')))
|
||||
die('Parameter [proxy] => "url" is not a valid string! Please check "config.ini.php"!');
|
||||
|
||||
if(!empty(self::getConfig('proxy', 'url')))
|
||||
if(!empty(self::getConfig('proxy', 'url'))) {
|
||||
/** URL of the proxy server */
|
||||
define('PROXY_URL', self::getConfig('proxy', 'url'));
|
||||
}
|
||||
|
||||
if(!is_bool(self::getConfig('proxy', 'by_bridge')))
|
||||
die('Parameter [proxy] => "by_bridge" is not a valid Boolean! Please check "config.ini.php"!');
|
||||
|
||||
/** True if proxy usage can be enabled selectively for each bridge */
|
||||
define('PROXY_BYBRIDGE', self::getConfig('proxy', 'by_bridge'));
|
||||
|
||||
if(!is_string(self::getConfig('proxy', 'name')))
|
||||
die('Parameter [proxy] => "name" is not a valid string! Please check "config.ini.php"!');
|
||||
|
||||
/** Name of the proxy server */
|
||||
define('PROXY_NAME', self::getConfig('proxy', 'name'));
|
||||
|
||||
if(!is_bool(self::getConfig('cache', 'custom_timeout')))
|
||||
die('Parameter [cache] => "custom_timeout" is not a valid Boolean! Please check "config.ini.php"!');
|
||||
|
||||
/** True if the cache timeout can be specified by the user */
|
||||
define('CUSTOM_CACHE_TIMEOUT', self::getConfig('cache', 'custom_timeout'));
|
||||
|
||||
if(!is_bool(self::getConfig('authentication', 'enable')))
|
||||
@@ -91,21 +193,41 @@ class Configuration {
|
||||
if(!is_string(self::getConfig('authentication', 'password')))
|
||||
die('Parameter [authentication] => "password" is not a valid string! Please check "config.ini.php"!');
|
||||
|
||||
if(!empty(self::getConfig('admin', 'email'))
|
||||
&& !filter_var(self::getConfig('admin', 'email'), FILTER_VALIDATE_EMAIL))
|
||||
die('Parameter [admin] => "email" is not a valid email address! Please check "config.ini.php"!');
|
||||
|
||||
}
|
||||
|
||||
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])) {
|
||||
return self::$config[$category][$key];
|
||||
if(array_key_exists($section, self::$config) && array_key_exists($key, self::$config[$section])) {
|
||||
return self::$config[$section][$key];
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current version string of RSS-Bridge.
|
||||
*
|
||||
* This function returns the contents of {@see Configuration::$VERSION} for
|
||||
* regular installations and the git branch name and commit id for instances
|
||||
* running in a git environment.
|
||||
*
|
||||
* @return string The version string.
|
||||
*/
|
||||
public static function getVersion() {
|
||||
|
||||
$headFile = '.git/HEAD';
|
||||
$headFile = PATH_ROOT . '.git/HEAD';
|
||||
|
||||
// '@' is used to mute open_basedir warning
|
||||
if(@is_readable($headFile)) {
|
||||
|
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
|
||||
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
|
||||
* on the information provided
|
||||
*
|
||||
* @param $title string Sets the title of the issue
|
||||
* @param $body string Sets the body of the issue (GitHub markdown applies)
|
||||
* @param $labels mixed (optional) Specifies labels to add to the issue
|
||||
* @param $maintainer string (optional) Specifies the maintainer for the issue.
|
||||
* @param string $title string Sets the title of the issue
|
||||
* @param string $body string Sets the body of the issue (GitHub markdown applies)
|
||||
* @param string $labels mixed (optional) Specifies labels to add to the issue
|
||||
* @param string $maintainer string (optional) Specifies the maintainer for the issue.
|
||||
* The maintainer only applies if part of the development team!
|
||||
* @return string Returns a qualified URL to a new issue with populated conent.
|
||||
* Returns null if title or body is null or empty
|
||||
* @return string|null A qualified URL to a new issue with populated conent or null.
|
||||
*
|
||||
* @todo This function belongs inside a class
|
||||
*/
|
||||
function buildGitHubIssueQuery($title, $body, $labels = null, $maintainer = null){
|
||||
if(!isset($title) || !isset($body) || empty($title) || empty($body)) {
|
||||
@@ -49,10 +60,11 @@ function buildGitHubIssueQuery($title, $body, $labels = null, $maintainer = null
|
||||
/**
|
||||
* Returns the exception message as HTML string
|
||||
*
|
||||
* @param $e Exception The exception to show
|
||||
* @param $bridge object The bridge object
|
||||
* @return string Returns the exception as HTML string. Returns null if the
|
||||
* provided parameter are invalid
|
||||
* @param object $e Exception The exception to show
|
||||
* @param object $bridge object The bridge object
|
||||
* @return string|null Returns the exception as HTML string or null.
|
||||
*
|
||||
* @todo This function belongs inside a class
|
||||
*/
|
||||
function buildBridgeException($e, $bridge){
|
||||
if(( !($e instanceof \Exception) && !($e instanceof \Error)) || !($bridge instanceof \BridgeInterface)) {
|
||||
@@ -65,7 +77,7 @@ function buildBridgeException($e, $bridge){
|
||||
$body = 'Error message: `'
|
||||
. $e->getMessage()
|
||||
. "`\nQuery string: `"
|
||||
. $_SERVER['QUERY_STRING']
|
||||
. (isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '')
|
||||
. "`\nVersion: `"
|
||||
. Configuration::getVersion()
|
||||
. '`';
|
||||
@@ -87,10 +99,11 @@ EOD;
|
||||
/**
|
||||
* Returns the exception message as HTML string
|
||||
*
|
||||
* @param $e Exception The exception to show
|
||||
* @param $bridge object The bridge object
|
||||
* @return string Returns the exception as HTML string. Returns null if the
|
||||
* provided parameter are invalid
|
||||
* @param object $e Exception The exception to show
|
||||
* @param object $bridge object The bridge object
|
||||
* @return string|null Returns the exception as HTML string or null.
|
||||
*
|
||||
* @todo This function belongs inside a class
|
||||
*/
|
||||
function buildTransformException($e, $bridge){
|
||||
if(( !($e instanceof \Exception) && !($e instanceof \Error)) || !($bridge instanceof \BridgeInterface)) {
|
||||
@@ -103,7 +116,8 @@ function buildTransformException($e, $bridge){
|
||||
$body = 'Error message: `'
|
||||
. $e->getMessage()
|
||||
. "`\nQuery string: `"
|
||||
. $_SERVER['QUERY_STRING'] . '`';
|
||||
. (isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '')
|
||||
. '`';
|
||||
|
||||
$link = buildGitHubIssueQuery($title, $body, 'bug report', $bridge->getMaintainer());
|
||||
$header = buildHeader($e, $bridge);
|
||||
@@ -114,6 +128,15 @@ function buildTransformException($e, $bridge){
|
||||
return buildPage($title, $header, $section);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new HTML header with data from a exception an a bridge
|
||||
*
|
||||
* @param object $e The exception object
|
||||
* @param object $bridge The bridge object
|
||||
* @return string The HTML header
|
||||
*
|
||||
* @todo This function belongs inside a class
|
||||
*/
|
||||
function buildHeader($e, $bridge){
|
||||
return <<<EOD
|
||||
<header>
|
||||
@@ -124,6 +147,17 @@ function buildHeader($e, $bridge){
|
||||
EOD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new HTML section
|
||||
*
|
||||
* @param object $e The exception object
|
||||
* @param object $bridge The bridge object
|
||||
* @param string $message The message to display
|
||||
* @param string $link The link to include in the anchor
|
||||
* @return string The HTML section
|
||||
*
|
||||
* @todo This function belongs inside a class
|
||||
*/
|
||||
function buildSection($e, $bridge, $message, $link){
|
||||
return <<<EOD
|
||||
<section>
|
||||
@@ -142,6 +176,16 @@ function buildSection($e, $bridge, $message, $link){
|
||||
EOD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new HTML page
|
||||
*
|
||||
* @param string $title The HTML title
|
||||
* @param string $header The HTML header
|
||||
* @param string $section The HTML section
|
||||
* @return string The HTML page
|
||||
*
|
||||
* @todo This function belongs inside a class
|
||||
*/
|
||||
function buildPage($title, $header, $section){
|
||||
return <<<EOD
|
||||
<!DOCTYPE html>
|
||||
|
@@ -1,17 +1,86 @@
|
||||
<?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 {
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Holds the feed type during internal operations.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $feedType;
|
||||
|
||||
/**
|
||||
* Collects data from an existing feed.
|
||||
*
|
||||
* Children should call this function in {@see BridgeInterface::collectData()}
|
||||
* to extract a feed.
|
||||
*
|
||||
* @param string $url URL to the feed.
|
||||
* @param int $maxItems Maximum number of items to collect from the feed
|
||||
* (`-1`: no limit).
|
||||
* @return self
|
||||
*/
|
||||
public function collectExpandableDatas($url, $maxItems = -1){
|
||||
if(empty($url)) {
|
||||
returnServerError('There is no $url for this RSS expander');
|
||||
}
|
||||
|
||||
debugMessage('Loading from ' . $url);
|
||||
Debug::log('Loading from ' . $url);
|
||||
|
||||
/* Notice we do not use cache here on purpose:
|
||||
* we want a fresh view of the RSS stream each time
|
||||
@@ -20,34 +89,49 @@ abstract class FeedExpander extends BridgeAbstract {
|
||||
or returnServerError('Could not request ' . $url);
|
||||
$rssContent = simplexml_load_string(trim($content));
|
||||
|
||||
debugMessage('Detecting feed format/version');
|
||||
Debug::log('Detecting feed format/version');
|
||||
switch(true) {
|
||||
case isset($rssContent->item[0]):
|
||||
debugMessage('Detected RSS 1.0 format');
|
||||
$this->feedType = 'RSS_1_0';
|
||||
Debug::log('Detected RSS 1.0 format');
|
||||
$this->feedType = self::FEED_TYPE_RSS_1_0;
|
||||
break;
|
||||
case isset($rssContent->channel[0]):
|
||||
debugMessage('Detected RSS 0.9x or 2.0 format');
|
||||
$this->feedType = 'RSS_2_0';
|
||||
Debug::log('Detected RSS 0.9x or 2.0 format');
|
||||
$this->feedType = self::FEED_TYPE_RSS_2_0;
|
||||
break;
|
||||
case isset($rssContent->entry[0]):
|
||||
debugMessage('Detected ATOM format');
|
||||
$this->feedType = 'ATOM_1_0';
|
||||
Debug::log('Detected ATOM format');
|
||||
$this->feedType = self::FEED_TYPE_ATOM_1_0;
|
||||
break;
|
||||
default:
|
||||
debugMessage('Unknown feed format/version');
|
||||
Debug::log('Unknown feed format/version');
|
||||
returnServerError('The feed format is unknown!');
|
||||
break;
|
||||
}
|
||||
|
||||
debugMessage('Calling function "collect_' . $this->feedType . '_data"');
|
||||
Debug::log('Calling function "collect_' . $this->feedType . '_data"');
|
||||
$this->{'collect_' . $this->feedType . '_data'}($rssContent, $maxItems);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect data from a RSS 1.0 compatible feed
|
||||
*
|
||||
* @link http://web.resource.org/rss/1.0/spec RDF Site Summary (RSS) 1.0
|
||||
*
|
||||
* @param string $rssContent The RSS content
|
||||
* @param int $maxItems Maximum number of items to collect from the feed
|
||||
* (`-1`: no limit).
|
||||
* @return void
|
||||
*
|
||||
* @todo Instead of passing $maxItems to all functions, just add all items
|
||||
* and remove excessive items later.
|
||||
*/
|
||||
protected function collect_RSS_1_0_data($rssContent, $maxItems){
|
||||
$this->load_RSS_2_0_feed_data($rssContent->channel[0]);
|
||||
foreach($rssContent->item as $item) {
|
||||
debugMessage('parsing item ' . var_export($item, true));
|
||||
Debug::log('parsing item ' . var_export($item, true));
|
||||
$tmp_item = $this->parseItem($item);
|
||||
if (!empty($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){
|
||||
$rssContent = $rssContent->channel[0];
|
||||
debugMessage('RSS content is ===========\n'
|
||||
Debug::log('RSS content is ===========\n'
|
||||
. var_export($rssContent, true)
|
||||
. '===========');
|
||||
|
||||
$this->load_RSS_2_0_feed_data($rssContent);
|
||||
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);
|
||||
if (!empty($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){
|
||||
$this->load_ATOM_feed_data($content);
|
||||
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);
|
||||
if (!empty($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){
|
||||
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){
|
||||
$this->name = trim((string)$rssContent->title);
|
||||
$this->title = trim((string)$rssContent->title);
|
||||
$this->uri = trim((string)$rssContent->link);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load Atom feed data into RSS-Bridge
|
||||
*
|
||||
* @param object $content The Atom content
|
||||
* @return void
|
||||
*/
|
||||
protected function load_ATOM_feed_data($content){
|
||||
$this->name = (string)$content->title;
|
||||
$this->title = (string)$content->title;
|
||||
|
||||
// Find best link (only one, or first of 'alternate')
|
||||
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){
|
||||
// Some ATOM entries also contain RSS 2.0 fields
|
||||
$item = $this->parseRSS_2_0_Item($feedItem);
|
||||
@@ -139,6 +278,16 @@ abstract class FeedExpander extends BridgeAbstract {
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the contents of a single RSS 0.91 feed item into a RSS-Bridge item
|
||||
* for further transformation.
|
||||
*
|
||||
* @param object $feedItem A single feed item
|
||||
* @return object The RSS-Bridge item
|
||||
*
|
||||
* @todo To reduce confusion, the RSS-Bridge item should maybe have a class
|
||||
* of its own?
|
||||
*/
|
||||
protected function parseRSS_0_9_1_Item($feedItem){
|
||||
$item = array();
|
||||
if(isset($feedItem->link)) $item['uri'] = (string)$feedItem->link;
|
||||
@@ -150,6 +299,16 @@ abstract class FeedExpander extends BridgeAbstract {
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the contents of a single RSS 1.0 feed item into a RSS-Bridge item
|
||||
* for further transformation.
|
||||
*
|
||||
* @param object $feedItem A single feed item
|
||||
* @return object The RSS-Bridge item
|
||||
*
|
||||
* @todo To reduce confusion, the RSS-Bridge item should maybe have a class
|
||||
* of its own?
|
||||
*/
|
||||
protected function parseRSS_1_0_Item($feedItem){
|
||||
// 1.0 adds optional elements around the 0.91 standard
|
||||
$item = $this->parseRSS_0_9_1_Item($feedItem);
|
||||
@@ -164,6 +323,16 @@ abstract class FeedExpander extends BridgeAbstract {
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the contents of a single RSS 2.0 feed item into a RSS-Bridge item
|
||||
* for further transformation.
|
||||
*
|
||||
* @param object $feedItem A single feed item
|
||||
* @return object The RSS-Bridge item
|
||||
*
|
||||
* @todo To reduce confusion, the RSS-Bridge item should maybe have a class
|
||||
* of its own?
|
||||
*/
|
||||
protected function parseRSS_2_0_Item($feedItem){
|
||||
// Primary data is compatible to 0.91 with some additional data
|
||||
$item = $this->parseRSS_0_9_1_Item($feedItem);
|
||||
@@ -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
|
||||
* @param $item the input rss item
|
||||
* @return a RSS-Bridge Item, with (hopefully) the whole content)
|
||||
* Parse the contents of a single feed item, depending on the current feed
|
||||
* type, into a RSS-Bridge item.
|
||||
*
|
||||
* @param object $item The current feed item
|
||||
* @return object A RSS-Bridge item, with (hopefully) the whole content
|
||||
*/
|
||||
protected function parseItem($item){
|
||||
switch($this->feedType) {
|
||||
case 'RSS_1_0':
|
||||
case self::FEED_TYPE_RSS_1_0:
|
||||
return $this->parseRSS_1_0_Item($item);
|
||||
break;
|
||||
case 'RSS_2_0':
|
||||
case self::FEED_TYPE_RSS_2_0:
|
||||
return $this->parseRSS_2_0_Item($item);
|
||||
break;
|
||||
case 'ATOM_1_0':
|
||||
case self::FEED_TYPE_ATOM_1_0:
|
||||
return $this->parseATOMItem($item);
|
||||
break;
|
||||
default: returnClientError('Unknown version ' . $this->getInput('version') . '!');
|
||||
}
|
||||
}
|
||||
|
||||
/** {@inheritdoc} */
|
||||
public function getURI(){
|
||||
return !empty($this->uri) ? $this->uri : parent::getURI();
|
||||
}
|
||||
|
||||
/** {@inheritdoc} */
|
||||
public function getName(){
|
||||
return !empty($this->name) ? $this->name : parent::getName();
|
||||
return !empty($this->title) ? $this->title : parent::getName();
|
||||
}
|
||||
|
||||
/** {@inheritdoc} */
|
||||
public function getIcon(){
|
||||
return !empty($this->icon) ? $this->icon : parent::getIcon();
|
||||
}
|
||||
|
179
lib/Format.php
179
lib/Format.php
@@ -1,73 +1,166 @@
|
||||
<?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 {
|
||||
|
||||
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(){
|
||||
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)) {
|
||||
throw new \InvalidArgumentException('Name format must be at least
|
||||
one uppercase follow or not by alphabetic characters.');
|
||||
/**
|
||||
* Creates a new format object from the working directory.
|
||||
*
|
||||
* @throws \InvalidArgumentException if the requested format name is invalid.
|
||||
* @throws \Exception if the requested format file doesn't exist in the
|
||||
* working directory.
|
||||
* @param string $name Name of the format object.
|
||||
* @return object|bool The format object or false if the class is not instantiable.
|
||||
*/
|
||||
public static function create($name){
|
||||
if(!self::isFormatName($name)) {
|
||||
throw new \InvalidArgumentException('Format name invalid!');
|
||||
}
|
||||
|
||||
$nameFormat = $nameFormat . 'Format';
|
||||
$pathFormat = self::getDir() . $nameFormat . '.php';
|
||||
$name = $name . 'Format';
|
||||
$pathFormat = self::getWorkingDir() . $name . '.php';
|
||||
|
||||
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;
|
||||
|
||||
return new $nameFormat();
|
||||
}
|
||||
|
||||
static public function setDir($dirFormat){
|
||||
if(!is_string($dirFormat)) {
|
||||
throw new \InvalidArgumentException('Dir format must be a string.');
|
||||
if((new \ReflectionClass($name))->isInstantiable()) {
|
||||
return new $name();
|
||||
}
|
||||
|
||||
if(!file_exists($dirFormat)) {
|
||||
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;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read format dir and catch informations about each format depending annotation
|
||||
* @return array Informations about each format
|
||||
*/
|
||||
static public function searchInformation(){
|
||||
$pathDirFormat = self::getDir();
|
||||
* Sets the working directory.
|
||||
*
|
||||
* @param string $dir Path to a directory containing cache classes
|
||||
* @throws \InvalidArgumentException if $dir is not a string.
|
||||
* @throws \Exception if the working directory doesn't exist.
|
||||
* @throws \InvalidArgumentException if $dir is not a directory.
|
||||
* @return void
|
||||
*/
|
||||
public static function setWorkingDir($dir){
|
||||
self::$workingDir = null;
|
||||
|
||||
$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($dirFiles !== false) {
|
||||
foreach($dirFiles as $fileName) {
|
||||
if(preg_match('@^([^.]+)Format\.php$@U', $fileName, $out)) { // Is PHP file ?
|
||||
$listFormat[] = $out[1];
|
||||
if(!is_dir($dir)) {
|
||||
throw new \InvalidArgumentException('Working directory is not a directory!');
|
||||
}
|
||||
|
||||
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
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
/** The default charset (UTF-8) */
|
||||
const DEFAULT_CHARSET = 'UTF-8';
|
||||
|
||||
protected
|
||||
$contentType,
|
||||
$charset,
|
||||
$items,
|
||||
$lastModified,
|
||||
$extraInfos;
|
||||
/** @var string|null $contentType The content type */
|
||||
protected $contentType = null;
|
||||
|
||||
/** @var string $charset The charset */
|
||||
protected $charset;
|
||||
|
||||
/** @var array $items The items */
|
||||
protected $items;
|
||||
|
||||
/**
|
||||
* @var int $lastModified A timestamp to indicate the last modified time of
|
||||
* the output data.
|
||||
*/
|
||||
protected $lastModified;
|
||||
|
||||
/** @var array $extraInfos The extra infos */
|
||||
protected $extraInfos;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param string $charset {@inheritdoc}
|
||||
*/
|
||||
public function setCharset($charset){
|
||||
$this->charset = $charset;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** {@inheritdoc} */
|
||||
public function getCharset(){
|
||||
$charset = $this->charset;
|
||||
|
||||
return is_null($charset) ? static::DEFAULT_CHARSET : $charset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the content type
|
||||
*
|
||||
* @param string $contentType The content type
|
||||
* @return self The format object
|
||||
*/
|
||||
protected function setContentType($contentType){
|
||||
$this->contentType = $contentType;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the last modified time
|
||||
*
|
||||
* @param int $lastModified The last modified time
|
||||
* @return void
|
||||
*/
|
||||
public function setLastModified($lastModified){
|
||||
$this->lastModified = $lastModified;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send header with the currently specified content type
|
||||
*
|
||||
* @throws \LogicException if the content type is not set
|
||||
* @throws \LogicException if the content type is not a string
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function callContentType(){
|
||||
if(empty($this->contentType))
|
||||
throw new \LogicException('Content-Type is not set!');
|
||||
|
||||
if(!is_string($this->contentType))
|
||||
throw new \LogicException('Content-Type must be a string!');
|
||||
|
||||
header('Content-Type: ' . $this->contentType);
|
||||
}
|
||||
|
||||
/** {@inheritdoc} */
|
||||
public function display(){
|
||||
if ($this->lastModified) {
|
||||
header('Last-Modified: ' . gmdate('D, d M Y H:i:s ', $this->lastModified) . 'GMT');
|
||||
@@ -45,12 +107,18 @@ abstract class FormatAbstract implements FormatInterface {
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param array $items {@inheritdoc}
|
||||
*/
|
||||
public function setItems(array $items){
|
||||
$this->items = array_map(array($this, 'array_trim'), $items);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** {@inheritdoc} */
|
||||
public function getItems(){
|
||||
if(!is_array($this->items))
|
||||
throw new \LogicException('Feed the ' . get_class($this) . ' with "setItems" method before !');
|
||||
@@ -59,10 +127,10 @@ abstract class FormatAbstract implements FormatInterface {
|
||||
}
|
||||
|
||||
/**
|
||||
* Define common informations can be required by formats and set default value for unknown values
|
||||
* @param array $extraInfos array with know informations (there isn't merge !!!)
|
||||
* @return this
|
||||
*/
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param array $extraInfos {@inheritdoc}
|
||||
*/
|
||||
public function setExtraInfos(array $extraInfos = array()){
|
||||
foreach(array('name', 'uri', 'icon') as $infoName) {
|
||||
if(!isset($extraInfos[$infoName])) {
|
||||
@@ -75,10 +143,7 @@ abstract class FormatAbstract implements FormatInterface {
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return extra infos
|
||||
* @return array See "setExtraInfos" detail method to know what extra are disponibles
|
||||
*/
|
||||
/** {@inheritdoc} */
|
||||
public function getExtraInfos(){
|
||||
if(is_null($this->extraInfos)) { // No extra info ?
|
||||
$this->setExtraInfos(); // Define with default value
|
||||
@@ -88,12 +153,17 @@ abstract class FormatAbstract implements FormatInterface {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitized html while leaving it functionnal.
|
||||
* The aim is to keep html as-is (with clickable hyperlinks)
|
||||
* while reducing annoying and potentially dangerous things.
|
||||
* Yes, I know sanitizing HTML 100% is an impossible task.
|
||||
* Maybe we'll switch to http://htmlpurifier.org/
|
||||
* or http://www.bioinformatics.org/phplabware/internal_utilities/htmLawed/index.php
|
||||
* Sanitize HTML while leaving it functional.
|
||||
*
|
||||
* Keeps HTML as-is (with clickable hyperlinks) while reducing annoying and
|
||||
* potentially dangerous things.
|
||||
*
|
||||
* @param string $html The HTML content
|
||||
* @return string The sanitized HTML content
|
||||
*
|
||||
* @todo This belongs into `html.php`
|
||||
* @todo Maybe switch to http://htmlpurifier.org/
|
||||
* @todo Maybe switch to http://www.bioinformatics.org/phplabware/internal_utilities/htmLawed/index.php
|
||||
*/
|
||||
protected function sanitizeHtml($html)
|
||||
{
|
||||
@@ -104,6 +174,17 @@ abstract class FormatAbstract implements FormatInterface {
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trim each element of an array
|
||||
*
|
||||
* This function applies `trim()` to all elements in the array, if the element
|
||||
* is a valid string.
|
||||
*
|
||||
* @param array $elements The array to trim
|
||||
* @return array The trimmed array
|
||||
*
|
||||
* @todo This is a utility function that doesn't belong here, find a new home.
|
||||
*/
|
||||
protected function array_trim($elements){
|
||||
foreach($elements as $key => $value) {
|
||||
if(is_string($value))
|
||||
|
@@ -1,11 +1,84 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
|
||||
* Atom feeds for websites that don't have one.
|
||||
*
|
||||
* For the full license information, please view the UNLICENSE file distributed
|
||||
* with this source code.
|
||||
*
|
||||
* @package Core
|
||||
* @license http://unlicense.org/ UNLICENSE
|
||||
* @link https://github.com/rss-bridge/rss-bridge
|
||||
*/
|
||||
|
||||
/**
|
||||
* The format interface
|
||||
*
|
||||
* @todo Add missing function to the interface
|
||||
* @todo Explain parameters and return values in more detail
|
||||
* @todo Return self more often (to allow call chaining)
|
||||
*/
|
||||
interface FormatInterface {
|
||||
|
||||
/**
|
||||
* Generate a string representation of the current data
|
||||
*
|
||||
* @return string The string representation
|
||||
*/
|
||||
public function stringify();
|
||||
|
||||
/**
|
||||
* Display the current data to the user
|
||||
*
|
||||
* @return self The format object
|
||||
*/
|
||||
public function display();
|
||||
|
||||
/**
|
||||
* Set items
|
||||
*
|
||||
* @param array $bridges The items
|
||||
* @return self The format object
|
||||
*
|
||||
* @todo Rename parameter `$bridges` to `$items`
|
||||
*/
|
||||
public function setItems(array $bridges);
|
||||
|
||||
/**
|
||||
* Return items
|
||||
*
|
||||
* @throws \LogicException if the items are not set
|
||||
* @return array The items
|
||||
*/
|
||||
public function getItems();
|
||||
|
||||
/**
|
||||
* Set extra information
|
||||
*
|
||||
* @param array $infos Extra information
|
||||
* @return self The format object
|
||||
*/
|
||||
public function setExtraInfos(array $infos);
|
||||
|
||||
/**
|
||||
* Return extra information
|
||||
*
|
||||
* @return array Extra information
|
||||
*/
|
||||
public function getExtraInfos();
|
||||
|
||||
/**
|
||||
* Set charset
|
||||
*
|
||||
* @param string $charset The charset
|
||||
* @return self The format object
|
||||
*/
|
||||
public function setCharset($charset);
|
||||
|
||||
/**
|
||||
* Return current charset
|
||||
*
|
||||
* @return string The charset
|
||||
*/
|
||||
public function getCharset();
|
||||
}
|
||||
|
@@ -1,10 +1,35 @@
|
||||
<?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 {
|
||||
|
||||
/**
|
||||
* Holds the list of invalid parameters
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $invalid = array();
|
||||
|
||||
/**
|
||||
* Add item to list of invalid parameters
|
||||
*
|
||||
* @param string $name The name of the parameter
|
||||
* @param string $reason The reason for that parameter being invalid
|
||||
* @return void
|
||||
*/
|
||||
private function addInvalidParameter($name, $reason){
|
||||
$this->invalid[] = array(
|
||||
'name' => $name,
|
||||
@@ -13,13 +38,23 @@ class ParameterValidator {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of invalid parameters, where each element is an
|
||||
* array of 'name' and 'reason'.
|
||||
* Return list of invalid parameters.
|
||||
*
|
||||
* Each element is an array of 'name' and 'reason'.
|
||||
*
|
||||
* @return array List of invalid parameters
|
||||
*/
|
||||
public function getInvalidParameters() {
|
||||
return $this->invalid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate value for a text input
|
||||
*
|
||||
* @param string $value The value of a text input
|
||||
* @param string|null $pattern (optional) A regex pattern
|
||||
* @return string|null The filtered value or null if the value is invalid
|
||||
*/
|
||||
private function validateTextValue($value, $pattern = null){
|
||||
if(!is_null($pattern)) {
|
||||
$filteredValue = filter_var($value,
|
||||
@@ -38,6 +73,12 @@ class ParameterValidator {
|
||||
return $filteredValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate value for a number input
|
||||
*
|
||||
* @param int $value The value of a number input
|
||||
* @return int|null The filtered value or null if the value is invalid
|
||||
*/
|
||||
private function validateNumberValue($value){
|
||||
$filteredValue = filter_var($value, FILTER_VALIDATE_INT);
|
||||
|
||||
@@ -47,10 +88,23 @@ class ParameterValidator {
|
||||
return $filteredValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate value for a checkbox
|
||||
*
|
||||
* @param bool $value The value of a checkbox
|
||||
* @return bool The filtered value
|
||||
*/
|
||||
private function validateCheckboxValue($value){
|
||||
return filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate value for a list
|
||||
*
|
||||
* @param string $value The value of a list
|
||||
* @param array $expectedValues A list of expected values
|
||||
* @return string|null The filtered value or null if the value is invalid
|
||||
*/
|
||||
private function validateListValue($value, $expectedValues){
|
||||
$filteredValue = filter_var($value);
|
||||
|
||||
@@ -69,9 +123,11 @@ class ParameterValidator {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if all required parameters are supplied by the user
|
||||
* @param $data An array of parameters provided by the user
|
||||
* @param $parameters An array of bridge parameters
|
||||
* Check if all required parameters are satisfied
|
||||
*
|
||||
* @param array $data (ref) A list of input values
|
||||
* @param array $parameters The bridge parameters
|
||||
* @return bool True if all parameters are satisfied
|
||||
*/
|
||||
public function validateData(&$data, $parameters){
|
||||
|
||||
@@ -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 $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){
|
||||
$queriedContexts = array();
|
||||
|
@@ -1,37 +0,0 @@
|
||||
<?php
|
||||
|
||||
define('PATH_LIB', __DIR__ . '/../lib/'); // Path to core library
|
||||
define('PATH_LIB_VENDOR', __DIR__ . '/../vendor/'); // Path to vendor library
|
||||
define('PATH_LIB_BRIDGES', __DIR__ . '/../bridges/'); // Path to bridges library
|
||||
define('PATH_LIB_FORMATS', __DIR__ . '/../formats/'); // Path to formats library
|
||||
define('PATH_LIB_CACHES', __DIR__ . '/../caches/'); // Path to caches library
|
||||
define('PATH_CACHE', __DIR__ . '/../cache'); // Path to cache folder
|
||||
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 . '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';
|
198
lib/contents.php
198
lib/contents.php
@@ -1,6 +1,56 @@
|
||||
<?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()){
|
||||
debugMessage('Reading contents from "' . $url . '"');
|
||||
Debug::log('Reading contents from "' . $url . '"');
|
||||
|
||||
// Initialize cache
|
||||
$cache = Cache::create('FileCache');
|
||||
$cache->setPath(PATH_CACHE . 'server/');
|
||||
$cache->purgeCache(86400); // 24 hours (forced)
|
||||
|
||||
$params = [$url];
|
||||
$cache->setParameters($params);
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
@@ -8,7 +58,7 @@ function getContents($url, $header = array(), $opts = array()){
|
||||
|
||||
if(is_array($header) && count($header) !== 0) {
|
||||
|
||||
debugMessage('Setting headers: ' . json_encode($header));
|
||||
Debug::log('Setting headers: ' . json_encode($header));
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
|
||||
|
||||
}
|
||||
@@ -19,7 +69,7 @@ function getContents($url, $header = array(), $opts = array()){
|
||||
|
||||
if(is_array($opts) && count($opts) !== 0) {
|
||||
|
||||
debugMessage('Setting options: ' . json_encode($opts));
|
||||
Debug::log('Setting options: ' . json_encode($opts));
|
||||
|
||||
foreach($opts as $key => $value) {
|
||||
curl_setopt($ch, $key, $value);
|
||||
@@ -29,7 +79,7 @@ function getContents($url, $header = array(), $opts = array()){
|
||||
|
||||
if(defined('PROXY_URL') && !defined('NOPROXY')) {
|
||||
|
||||
debugMessage('Setting proxy url: ' . PROXY_URL);
|
||||
Debug::log('Setting proxy url: ' . PROXY_URL);
|
||||
curl_setopt($ch, CURLOPT_PROXY, PROXY_URL);
|
||||
|
||||
}
|
||||
@@ -37,43 +87,104 @@ function getContents($url, $header = array(), $opts = array()){
|
||||
// We always want the response header as part of the data!
|
||||
curl_setopt($ch, CURLOPT_HEADER, true);
|
||||
|
||||
// Build "If-Modified-Since" header
|
||||
if(!Debug::isEnabled() && $time = $cache->getTime()) { // Skip if cache file doesn't exist
|
||||
Debug::log('Adding If-Modified-Since');
|
||||
curl_setopt($ch, CURLOPT_TIMEVALUE, $time);
|
||||
curl_setopt($ch, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE);
|
||||
}
|
||||
|
||||
// Enables logging for the outgoing header
|
||||
curl_setopt($ch, CURLINFO_HEADER_OUT, true);
|
||||
|
||||
$data = curl_exec($ch);
|
||||
$curlError = curl_error($ch);
|
||||
$curlErrno = curl_errno($ch);
|
||||
$curlInfo = curl_getinfo($ch);
|
||||
|
||||
Debug::log('Outgoing header: ' . json_encode($curlInfo));
|
||||
|
||||
if($data === false)
|
||||
debugMessage('Cant\'t download ' . $url . ' cUrl error: ' . $curlError . ' (' . $curlErrno . ')');
|
||||
Debug::log('Cant\'t download ' . $url . ' cUrl error: ' . $curlError . ' (' . $curlErrno . ')');
|
||||
|
||||
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
|
||||
$errorCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$header = substr($data, 0, $headerSize);
|
||||
|
||||
debugMessage('Response header: ' . $header);
|
||||
Debug::log('Response header: ' . $header);
|
||||
|
||||
$headers = parseResponseHeader($header);
|
||||
$finalHeader = end($headers);
|
||||
|
||||
if($errorCode !== 200) {
|
||||
curl_close($ch);
|
||||
|
||||
if(array_key_exists('Server', $finalHeader) && strpos($finalHeader['Server'], 'cloudflare') !== false) {
|
||||
switch($errorCode) {
|
||||
case 200: // Contents received
|
||||
Debug::log('New contents received');
|
||||
$data = substr($data, $headerSize);
|
||||
// Disable caching if the server responds with "Cache-Control: no-cache"
|
||||
// or "Cache-Control: no-store"
|
||||
$finalHeader = array_change_key_case($finalHeader, CASE_LOWER);
|
||||
if(array_key_exists('cache-control', $finalHeader)) {
|
||||
Debug::log('Server responded with "Cache-Control" header');
|
||||
$directives = explode(',', $finalHeader['cache-control']);
|
||||
$directives = array_map('trim', $directives);
|
||||
if(in_array('no-cache', $directives)
|
||||
|| in_array('no-store', $directives)) { // Skip caching
|
||||
Debug::log('Skip server side caching');
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
Debug::log('Store response to cache');
|
||||
$cache->saveData($data);
|
||||
return $data;
|
||||
case 304: // Not modified, use cached data
|
||||
Debug::log('Contents not modified on host, returning cached data');
|
||||
return $cache->loadData();
|
||||
default:
|
||||
if(array_key_exists('Server', $finalHeader) && strpos($finalHeader['Server'], 'cloudflare') !== false) {
|
||||
returnServerError(<<< EOD
|
||||
The server responded with a Cloudflare challenge, which is not supported by RSS-Bridge!
|
||||
If this error persists longer than a week, please consider opening an issue on GitHub!
|
||||
EOD
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
returnError(<<<EOD
|
||||
returnError(<<<EOD
|
||||
The requested resource cannot be found!
|
||||
Please make sure your input parameters are correct!
|
||||
cUrl error: $curlError ($curlErrno)
|
||||
EOD
|
||||
, $errorCode);
|
||||
, $errorCode);
|
||||
}
|
||||
|
||||
curl_close($ch);
|
||||
return substr($data, $headerSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets contents from the Internet as simplhtmldom object.
|
||||
*
|
||||
* @param string $url The URL.
|
||||
* @param array $header (optional) A list of cURL header.
|
||||
* For more information follow the links below.
|
||||
* * https://php.net/manual/en/function.curl-setopt.php
|
||||
* * https://curl.haxx.se/libcurl/c/CURLOPT_HTTPHEADER.html
|
||||
* @param array $opts (optional) A list of cURL options as associative array in
|
||||
* the format `$opts[$option] = $value;`, where `$option` is any `CURLOPT_XXX`
|
||||
* option and `$value` the corresponding value.
|
||||
*
|
||||
* For more information see http://php.net/manual/en/function.curl-setopt.php
|
||||
* @param bool $lowercase Force all selectors to lowercase.
|
||||
* @param bool $forceTagsClosed Forcefully close tags in malformed HTML.
|
||||
*
|
||||
* _Remarks_: Forcefully closing tags is great for malformed HTML, but it can
|
||||
* lead to parsing errors.
|
||||
* @param string $target_charset Defines the target charset.
|
||||
* @param bool $stripRN Replace all occurrences of `"\r"` and `"\n"` by `" "`.
|
||||
* @param string $defaultBRText Specifies the replacement text for `<br>` tags
|
||||
* when returning plaintext.
|
||||
* @param string $defaultSpanText Specifies the replacement text for `<span />`
|
||||
* tags when returning plaintext.
|
||||
* @return string Contents as simplehtmldom object.
|
||||
*/
|
||||
function getSimpleHTMLDOM($url,
|
||||
$header = array(),
|
||||
$opts = array(),
|
||||
@@ -94,10 +205,34 @@ $defaultSpanText = DEFAULT_SPAN_TEXT){
|
||||
}
|
||||
|
||||
/**
|
||||
* Maintain locally cached versions of pages to avoid multiple downloads.
|
||||
* @param url url to cache
|
||||
* @param duration duration of the cache file in seconds (default: 24h/86400s)
|
||||
* @return content of the file as string
|
||||
* Gets contents from the Internet as simplhtmldom object. Contents are cached
|
||||
* and re-used for subsequent calls until the cache duration elapsed.
|
||||
*
|
||||
* _Notice_: Cached contents are forcefully removed after 24 hours (86400 seconds).
|
||||
*
|
||||
* @param string $url The URL.
|
||||
* @param int $duration Cache duration in seconds.
|
||||
* @param array $header (optional) A list of cURL header.
|
||||
* For more information follow the links below.
|
||||
* * https://php.net/manual/en/function.curl-setopt.php
|
||||
* * https://curl.haxx.se/libcurl/c/CURLOPT_HTTPHEADER.html
|
||||
* @param array $opts (optional) A list of cURL options as associative array in
|
||||
* the format `$opts[$option] = $value;`, where `$option` is any `CURLOPT_XXX`
|
||||
* option and `$value` the corresponding value.
|
||||
*
|
||||
* For more information see http://php.net/manual/en/function.curl-setopt.php
|
||||
* @param bool $lowercase Force all selectors to lowercase.
|
||||
* @param bool $forceTagsClosed Forcefully close tags in malformed HTML.
|
||||
*
|
||||
* _Remarks_: Forcefully closing tags is great for malformed HTML, but it can
|
||||
* lead to parsing errors.
|
||||
* @param string $target_charset Defines the target charset.
|
||||
* @param bool $stripRN Replace all occurrences of `"\r"` and `"\n"` by `" "`.
|
||||
* @param string $defaultBRText Specifies the replacement text for `<br>` tags
|
||||
* when returning plaintext.
|
||||
* @param string $defaultSpanText Specifies the replacement text for `<span />`
|
||||
* tags when returning plaintext.
|
||||
* @return string Contents as simplehtmldom object.
|
||||
*/
|
||||
function getSimpleHTMLDOMCached($url,
|
||||
$duration = 86400,
|
||||
@@ -109,11 +244,11 @@ $target_charset = DEFAULT_TARGET_CHARSET,
|
||||
$stripRN = true,
|
||||
$defaultBRText = DEFAULT_BR_TEXT,
|
||||
$defaultSpanText = DEFAULT_SPAN_TEXT){
|
||||
debugMessage('Caching url ' . $url . ', duration ' . $duration);
|
||||
Debug::log('Caching url ' . $url . ', duration ' . $duration);
|
||||
|
||||
// Initialize cache
|
||||
$cache = Cache::create('FileCache');
|
||||
$cache->setPath(PATH_CACHE . '/pages');
|
||||
$cache->setPath(PATH_CACHE . 'pages/');
|
||||
$cache->purgeCache(86400); // 24 hours (forced)
|
||||
|
||||
$params = [$url];
|
||||
@@ -123,7 +258,7 @@ $defaultSpanText = DEFAULT_SPAN_TEXT){
|
||||
$time = $cache->getTime();
|
||||
if($time !== false
|
||||
&& (time() - $duration < $time)
|
||||
&& (!defined('DEBUG') || DEBUG !== true)) { // Contents within duration
|
||||
&& Debug::isEnabled()) { // Contents within duration
|
||||
$content = $cache->loadData();
|
||||
} else { // Content not within duration
|
||||
$content = getContents($url, $header, $opts);
|
||||
@@ -142,9 +277,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
|
||||
*
|
||||
* @param string $header The cURL response header.
|
||||
* @return array An associative array of response headers.
|
||||
*/
|
||||
function parseResponseHeader($header) {
|
||||
|
||||
@@ -177,10 +315,18 @@ function parseResponseHeader($header) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine MIME type from URL/Path file extension
|
||||
* Remark: Built-in functions mime_content_type or fileinfo requires fetching remote content
|
||||
* Remark: A bridge can hint for a MIME type by appending #.ext to a URL, e.g. #.image
|
||||
* Determines the MIME type from a URL/Path file extension.
|
||||
*
|
||||
* _Remarks_:
|
||||
*
|
||||
* * The built-in functions `mime_content_type` and `fileinfo` require fetching
|
||||
* remote contents.
|
||||
* * A caller can hint for a MIME type by appending `#.ext` to the URL (i.e. `#.image`).
|
||||
*
|
||||
* Based on https://stackoverflow.com/a/1147952
|
||||
*
|
||||
* @param string $url The URL or path to the file.
|
||||
* @return string The MIME type of the file.
|
||||
*/
|
||||
function getMimeType($url) {
|
||||
static $mime = null;
|
||||
|
@@ -1,28 +1,43 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
|
||||
* Atom feeds for websites that don't have one.
|
||||
*
|
||||
* For the full license information, please view the UNLICENSE file distributed
|
||||
* with this source code.
|
||||
*
|
||||
* @package Core
|
||||
* @license http://unlicense.org/ UNLICENSE
|
||||
* @link https://github.com/rss-bridge/rss-bridge
|
||||
*/
|
||||
|
||||
/**
|
||||
* Throws an exception when called.
|
||||
*
|
||||
* @throws \Exception when called
|
||||
* @param string $message The error message
|
||||
* @param int $code The HTTP error code
|
||||
* @link https://en.wikipedia.org/wiki/List_of_HTTP_status_codes List of HTTP
|
||||
* status codes
|
||||
*/
|
||||
function returnError($message, $code){
|
||||
throw new \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){
|
||||
returnError($message, 400);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns HTTP Error 500 (Internal Server Error) when called.
|
||||
*
|
||||
* @param string $message The error message
|
||||
*/
|
||||
function returnServerError($message){
|
||||
returnError($message, 500);
|
||||
}
|
||||
|
||||
function debugMessage($text){
|
||||
if(!file_exists('DEBUG')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3);
|
||||
$calling = $backtrace[2];
|
||||
$message = $calling['file'] . ':'
|
||||
. $calling['line'] . ' class '
|
||||
. (isset($calling['class']) ? $calling['class'] : '<no-class>') . '->'
|
||||
. $calling['function'] . ' - '
|
||||
. $text;
|
||||
|
||||
error_log($message);
|
||||
}
|
||||
|
141
lib/html.php
141
lib/html.php
@@ -1,18 +1,55 @@
|
||||
<?php
|
||||
function sanitize($textToSanitize,
|
||||
$removedTags = array('script', 'iframe', 'input', 'form'),
|
||||
$keptAttributes = array('title', 'href', 'src'),
|
||||
$keptText = array()){
|
||||
$htmlContent = str_get_html($textToSanitize);
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
/**
|
||||
* Removes unwanted tags from a given HTML text.
|
||||
*
|
||||
* @param string $html The HTML text to sanitize.
|
||||
* @param array $tags_to_remove A list of tags to remove from the DOM.
|
||||
* @param array $attributes_to_keep A list of attributes to keep on tags (other
|
||||
* attributes are removed).
|
||||
* @param array $text_to_keep A list of tags where the innertext replaces the tag
|
||||
* (i.e. `<p>Hello World!</p>` becomes `Hello World!`).
|
||||
* @return object A simplehtmldom object of the remaining contents.
|
||||
*
|
||||
* @todo Check if this implementation is still necessary, because simplehtmldom
|
||||
* already removes some of the tags (search for `remove_noise` in simple_html_dom.php).
|
||||
*/
|
||||
function sanitize($html,
|
||||
$tags_to_remove = array('script', 'iframe', 'input', 'form'),
|
||||
$attributes_to_keep = array('title', 'href', 'src'),
|
||||
$text_to_keep = array()){
|
||||
$htmlContent = str_get_html($html);
|
||||
|
||||
/*
|
||||
* Notice: simple_html_dom currently doesn't support "->find(*)", which is a
|
||||
* known issue: https://sourceforge.net/p/simplehtmldom/bugs/157/
|
||||
*
|
||||
* A solution to this is to find all nodes WITHOUT a specific attribute. If
|
||||
* the attribute is very unlikely to appear in the DOM, this is essentially
|
||||
* returning all nodes.
|
||||
*
|
||||
* "*[!b38fd2b1fe7f4747d6b1c1254ccd055e]" is doing exactly that. The attrib
|
||||
* "b38fd2b1fe7f4747d6b1c1254ccd055e" is very unlikely to appear in any DOM.
|
||||
*/
|
||||
foreach($htmlContent->find('*[!b38fd2b1fe7f4747d6b1c1254ccd055e]') as $element) {
|
||||
if(in_array($element->tag, $keptText)) {
|
||||
if(in_array($element->tag, $text_to_keep)) {
|
||||
$element->outertext = $element->plaintext;
|
||||
} elseif(in_array($element->tag, $removedTags)) {
|
||||
} elseif(in_array($element->tag, $tags_to_remove)) {
|
||||
$element->outertext = '';
|
||||
} else {
|
||||
foreach($element->getAllAttributes() as $attributeName => $attribute) {
|
||||
if(!in_array($attributeName, $keptAttributes))
|
||||
if(!in_array($attributeName, $attributes_to_keep))
|
||||
$element->removeAttribute($attributeName);
|
||||
}
|
||||
}
|
||||
@@ -21,11 +58,48 @@ $keptText = array()){
|
||||
return $htmlContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace background by image
|
||||
*
|
||||
* Replaces tags with styles of `backgroud-image` by `<img />` tags.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* ```HTML
|
||||
* <html>
|
||||
* <body style="background-image: url('bgimage.jpg');">
|
||||
* <h1>Hello world!</h1>
|
||||
* </body>
|
||||
* </html>
|
||||
* ```
|
||||
*
|
||||
* results in this output:
|
||||
*
|
||||
* ```HTML
|
||||
* <html>
|
||||
* <img style="display:block;" src="bgimage.jpg" />
|
||||
* </html>
|
||||
* ```
|
||||
*
|
||||
* @param string $htmlContent The HTML content
|
||||
* @return string The HTML content with all ocurrences replaced
|
||||
*/
|
||||
function backgroundToImg($htmlContent) {
|
||||
|
||||
$regex = '/background-image[ ]{0,}:[ ]{0,}url\([\'"]{0,}(.*?)[\'"]{0,}\)/';
|
||||
$htmlContent = str_get_html($htmlContent);
|
||||
|
||||
/*
|
||||
* Notice: simple_html_dom currently doesn't support "->find(*)", which is a
|
||||
* known issue: https://sourceforge.net/p/simplehtmldom/bugs/157/
|
||||
*
|
||||
* A solution to this is to find all nodes WITHOUT a specific attribute. If
|
||||
* the attribute is very unlikely to appear in the DOM, this is essentially
|
||||
* returning all nodes.
|
||||
*
|
||||
* "*[!b38fd2b1fe7f4747d6b1c1254ccd055e]" is doing exactly that. The attrib
|
||||
* "b38fd2b1fe7f4747d6b1c1254ccd055e" is very unlikely to appear in any DOM.
|
||||
*/
|
||||
foreach($htmlContent->find('*[!b38fd2b1fe7f4747d6b1c1254ccd055e]') as $element) {
|
||||
|
||||
if(preg_match($regex, $element->style, $matches) > 0) {
|
||||
@@ -42,9 +116,14 @@ function backgroundToImg($htmlContent) {
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @return content with fixed URLs, as HTML object or string depending on input type
|
||||
*
|
||||
* This function is based on `php-urljoin`.
|
||||
*
|
||||
* @link https://github.com/plaidfluff/php-urljoin php-urljoin
|
||||
*
|
||||
* @param string|object $content The HTML content. Supports HTML objects or string objects
|
||||
* @param string $server Fully qualified URL to the page containing relative links
|
||||
* @return object Content with fixed URLs.
|
||||
*/
|
||||
function defaultLinkTo($content, $server){
|
||||
$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
|
||||
* @param $string input string, e.g. '<div>Post author: John Doe</div>'
|
||||
* @param $start start delimiter, e.g. 'author: '
|
||||
* @param $end end delimiter, e.g. '<'
|
||||
* @return extracted string, e.g. 'John Doe', or false if the delimiters were not found.
|
||||
*
|
||||
* @param string $string Input string, e.g. `<div>Post author: John Doe</div>`
|
||||
* @param string $start Start delimiter, e.g. `author: `
|
||||
* @param string $end End delimiter, e.g. `<`
|
||||
* @return string|bool Extracted string, e.g. `John Doe`, or false if the
|
||||
* delimiters were not found.
|
||||
*/
|
||||
function extractFromDelimiters($string, $start, $end) {
|
||||
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
|
||||
* @param $string input string, e.g. 'foo<script>superscript()</script>bar'
|
||||
* @param $start start delimiter, e.g. '<script'
|
||||
* @param $end end delimiter, e.g. '</script>'
|
||||
* @return cleaned string, e.g. 'foobar'
|
||||
*
|
||||
* @param string $string Input string, e.g. `foo<script>superscript()</script>bar`
|
||||
* @param string $start Start delimiter, e.g. `<script`
|
||||
* @param string $end End delimiter, e.g. `</script>`
|
||||
* @return string Cleaned string, e.g. `foobar`
|
||||
*/
|
||||
function stripWithDelimiters($string, $start, $end) {
|
||||
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
|
||||
* @param $string input string, e.g. 'foo<div class="ads"><div>ads</div>ads</div>bar'
|
||||
* @param $tag_name name of the HTML tag, e.g. 'div'
|
||||
* @param $tag_start start of the HTML tag to remove, e.g. '<div class="ads">'
|
||||
* @return cleaned string, e.g. 'foobar'
|
||||
*
|
||||
* @param string $string Input string, e.g. `foo<div class="ads"><div>ads</div>ads</div>bar`
|
||||
* @param string $tag_name Name of the HTML tag, e.g. `div`
|
||||
* @param string $tag_start Start of the HTML tag to remove, e.g. `<div class="ads">`
|
||||
* @return string Cleaned String, e.g. `foobar`
|
||||
*
|
||||
* @todo This function needs more documentation to make it maintainable.
|
||||
*/
|
||||
function stripRecursiveHTMLSection($string, $tag_name, $tag_start){
|
||||
$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.
|
||||
* @param $string input string in Markdown format
|
||||
* @return output string in HTML format
|
||||
* Convert Markdown into HTML. Only a subset of the Markdown syntax is implemented.
|
||||
*
|
||||
* @link https://daringfireball.net/projects/markdown/ Markdown
|
||||
* @link https://github.github.com/gfm/ GitHub Flavored Markdown Spec
|
||||
*
|
||||
* @param string $string Input string in Markdown format
|
||||
* @return string output string in HTML format
|
||||
*/
|
||||
function markdownToHtml($string) {
|
||||
|
||||
|
79
lib/rssbridge.php
Normal file
79
lib/rssbridge.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
|
||||
* Atom feeds for websites that don't have one.
|
||||
*
|
||||
* For the full license information, please view the UNLICENSE file distributed
|
||||
* with this source code.
|
||||
*
|
||||
* @package Core
|
||||
* @license http://unlicense.org/ UNLICENSE
|
||||
* @link https://github.com/rss-bridge/rss-bridge
|
||||
*/
|
||||
|
||||
/** Path to the root folder of RSS-Bridge (where index.php is located) */
|
||||
define('PATH_ROOT', __DIR__ . '/../');
|
||||
|
||||
/** Path to the core library */
|
||||
define('PATH_LIB', __DIR__ . '/../lib/'); // Path to core library
|
||||
|
||||
/** Path to the vendor library */
|
||||
define('PATH_LIB_VENDOR', __DIR__ . '/../vendor/');
|
||||
|
||||
/** Path to the bridges library */
|
||||
define('PATH_LIB_BRIDGES', __DIR__ . '/../bridges/');
|
||||
|
||||
/** Path to the formats library */
|
||||
define('PATH_LIB_FORMATS', __DIR__ . '/../formats/');
|
||||
|
||||
/** Path to the caches library */
|
||||
define('PATH_LIB_CACHES', __DIR__ . '/../caches/');
|
||||
|
||||
/** Path to the cache folder */
|
||||
define('PATH_CACHE', __DIR__ . '/../cache/');
|
||||
|
||||
/** Path to the whitelist file */
|
||||
define('WHITELIST', __DIR__ . '/../whitelist.txt');
|
||||
|
||||
/** URL to the RSS-Bridge repository */
|
||||
define('REPOSITORY', 'https://github.com/RSS-Bridge/rss-bridge/');
|
||||
|
||||
// Interfaces
|
||||
require_once PATH_LIB . 'BridgeInterface.php';
|
||||
require_once PATH_LIB . 'CacheInterface.php';
|
||||
require_once PATH_LIB . 'FormatInterface.php';
|
||||
|
||||
// Classes
|
||||
require_once PATH_LIB . 'Debug.php';
|
||||
require_once PATH_LIB . 'Exceptions.php';
|
||||
require_once PATH_LIB . 'Format.php';
|
||||
require_once PATH_LIB . 'FormatAbstract.php';
|
||||
require_once PATH_LIB . 'Bridge.php';
|
||||
require_once PATH_LIB . 'BridgeAbstract.php';
|
||||
require_once PATH_LIB . 'FeedExpander.php';
|
||||
require_once PATH_LIB . 'Cache.php';
|
||||
require_once PATH_LIB . 'Authentication.php';
|
||||
require_once PATH_LIB . 'Configuration.php';
|
||||
require_once PATH_LIB . 'BridgeCard.php';
|
||||
require_once PATH_LIB . 'BridgeList.php';
|
||||
require_once PATH_LIB . 'ParameterValidator.php';
|
||||
|
||||
// Functions
|
||||
require_once PATH_LIB . 'html.php';
|
||||
require_once PATH_LIB . 'error.php';
|
||||
require_once PATH_LIB . 'contents.php';
|
||||
|
||||
// Vendor
|
||||
require_once PATH_LIB_VENDOR . 'simplehtmldom/simple_html_dom.php';
|
||||
require_once PATH_LIB_VENDOR . 'php-urljoin/src/urljoin.php';
|
||||
|
||||
// Initialize static members
|
||||
try {
|
||||
Bridge::setWorkingDir(PATH_LIB_BRIDGES);
|
||||
Format::setWorkingDir(PATH_LIB_FORMATS);
|
||||
Cache::setWorkingDir(PATH_LIB_CACHES);
|
||||
} catch(Exception $e) {
|
||||
error_log($e);
|
||||
header('Content-type: text/plain', true, 500);
|
||||
die($e->getMessage());
|
||||
}
|
@@ -47,6 +47,9 @@
|
||||
<rule ref="PEAR.Functions.ValidDefaultValue"/>
|
||||
<!-- Use PascalCase for class names -->
|
||||
<rule ref="PEAR.NamingConventions.ValidClassName"/>
|
||||
<!-- abstract and final declarations MUST precede the visibility declaration -->
|
||||
<!-- static declaration MUST come after the visibility declaration -->
|
||||
<rule ref="PSR2.Methods.MethodDeclaration" />
|
||||
<!-- Use 'elseif' instead of 'else if' -->
|
||||
<rule ref="PSR2.ControlStructures.ElseIfDeclaration"/>
|
||||
<!-- Do not add spaces after opening or before closing bracket -->
|
||||
|
@@ -4,9 +4,7 @@ use PHPUnit\Framework\TestCase;
|
||||
use PHPUnit\Framework\TestResult;
|
||||
use PHPUnit\Framework\AssertionFailedError;
|
||||
|
||||
require_once(__DIR__ . '/../lib/RssBridge.php');
|
||||
|
||||
Bridge::setDir(PATH_LIB_BRIDGES);
|
||||
require_once __DIR__ . '/../lib/rssbridge.php';
|
||||
|
||||
/**
|
||||
* This class checks bridges for implementation details:
|
||||
@@ -143,7 +141,7 @@ final class BridgeImplementationTest extends TestCase {
|
||||
}
|
||||
|
||||
public function count() {
|
||||
return count(Bridge::listBridges());
|
||||
return count(Bridge::getBridgeNames());
|
||||
}
|
||||
|
||||
public function run(TestResult $result = null) {
|
||||
@@ -152,7 +150,7 @@ final class BridgeImplementationTest extends TestCase {
|
||||
$result = new TestResult;
|
||||
}
|
||||
|
||||
foreach (Bridge::listBridges() as $bridge) {
|
||||
foreach (Bridge::getBridgeNames() as $bridge) {
|
||||
|
||||
$bridge .= 'Bridge';
|
||||
|
||||
|
Reference in New Issue
Block a user