mirror of
https://github.com/RSS-Bridge/rss-bridge.git
synced 2025-08-17 22:02:09 +02:00
Compare commits
42 Commits
2018-10-15
...
revert-909
Author | SHA1 | Date | |
---|---|---|---|
|
db25a68072 | ||
|
8c97953211 | ||
|
d987ceec73 | ||
|
392e3ff6c7 | ||
|
e295dc5a79 | ||
|
b9f6bc8197 | ||
|
9c1c0f2974 | ||
|
65da157fff | ||
|
5fe943562a | ||
|
c58331f74d | ||
|
145a46ae1d | ||
|
1a7a7bad98 | ||
|
27d6a22675 | ||
|
b55ec51e0e | ||
|
07b4c72d5d | ||
|
2e6cbd1ce7 | ||
|
2ac2f3dc66 | ||
|
e2dfea2b77 | ||
|
c4896c7791 | ||
|
7621784598 | ||
|
1cfe939927 | ||
|
c56f7abc2a | ||
|
e3030cbbfd | ||
|
953c6e1022 | ||
|
dbd44f64dd | ||
|
89ca42da54 | ||
|
b4b5340b7e | ||
|
a508dddb36 | ||
|
cb488d9d8c | ||
|
9820ad5c0f | ||
|
ea2d54523d | ||
|
87d218296e | ||
|
afd5ef0f1d | ||
|
30bc5179c2 | ||
|
7596be65f2 | ||
|
16f0ee7104 | ||
|
e0323f06cd | ||
|
717b0bdd9c | ||
|
62d737efe2 | ||
|
6fce03daa7 | ||
|
7561c0685d | ||
|
f48eac854f |
@@ -1,47 +1,47 @@
|
||||
### Pull request policy
|
||||
Fix one issue per pull request.
|
||||
Squash commits before opening a pull request.
|
||||
Respect the coding style policy.
|
||||
Name your PR like the following :
|
||||
|
||||
* When correcting a single bridge, use `[BridgeName] Feature`.
|
||||
* When fixing a problem in a specific file, use `[FileName] Feature`.
|
||||
* When fixing a general problem, use `category : feature`.
|
||||
* [Fix one issue per pull request](https://github.com/RSS-Bridge/rss-bridge/wiki/Pull-request-policy#fix-one-issue-per-pull-request)
|
||||
* [Respect the coding style policy](https://github.com/RSS-Bridge/rss-bridge/wiki/Pull-request-policy#respect-the-coding-style-policy)
|
||||
* [Properly name your commits](https://github.com/RSS-Bridge/rss-bridge/wiki/Pull-request-policy#properly-name-your-commits)
|
||||
* When fixing a bridge (located in the `bridges` directory), write `[BridgeName] Feature` <br>(i.e. `[YoutubeBridge] Fix typo in video titles`).
|
||||
* When fixing other files, use `[FileName] Feature` <br>(i.e. `[index.php] Add multilingual support`).
|
||||
* When fixing a general problem that applies to multiple files, write `category: feature` <br>(i.e. `bridges: Fix various typos`).
|
||||
|
||||
Note that all pull-requests should pass the unit tests before they can be merged.
|
||||
Note that all pull-requests must pass all tests before they can be merged.
|
||||
|
||||
### Coding style
|
||||
|
||||
Use `camelCase` for variables and methods.
|
||||
Use `UPPERCASE` for constants.
|
||||
Use `PascalCase` for class names. When creating a bridge, your class and PHP file should be named `MyImplementationBridge`.
|
||||
Use tabs for indentation.
|
||||
Add an empty line at the end of your file.
|
||||
|
||||
Use `''` to encapsulate strings, including in arrays.
|
||||
Prefer lines shorter than 80 chars, no line longer than 120 chars.
|
||||
PHP constants should be in lower case (`true, false, null`...)
|
||||
|
||||
|
||||
* Add spaces between the logical operator and your expressions (not needed for the `!` operator).
|
||||
* Use `||` and `&&` instead of `or` and `and`.
|
||||
* Add space between your condition and the opening bracket/closing bracket.
|
||||
* Don't put a space between `if` and your bracket.
|
||||
* Use `elseif` instead of `else if`.
|
||||
* Add new lines in your conditions if they are containing more than one line.
|
||||
* Example :
|
||||
|
||||
```PHP
|
||||
if($a == true && $b) {
|
||||
print($a);
|
||||
} else if(!$b) {
|
||||
|
||||
$a = !$a;
|
||||
$b = $b >> $a;
|
||||
print($b);
|
||||
|
||||
} else {
|
||||
print($b);
|
||||
}
|
||||
```
|
||||
|
||||
* [Whitespace](https://github.com/RSS-Bridge/rss-bridge/wiki/Whitespace)
|
||||
* [Add a new line at the end of a file](https://github.com/RSS-Bridge/rss-bridge/wiki/Whitespace#add-a-new-line-at-the-end-of-a-file)
|
||||
* [Do not add a whitespace before a semicolon](https://github.com/RSS-Bridge/rss-bridge/wiki/Whitespace#add-a-new-line-at-the-end-of-a-file)
|
||||
* [Do not add whitespace at start or end of a file or end of a line](https://github.com/RSS-Bridge/rss-bridge/wiki/Whitespace#do-not-add-whitespace-at-start-or-end-of-a-file-or-end-of-a-line)
|
||||
* [Indentation](https://github.com/RSS-Bridge/rss-bridge/wiki/Indentation)
|
||||
* [Use tabs for indentation](https://github.com/RSS-Bridge/rss-bridge/wiki/Indentation#use-tabs-for-indentation)
|
||||
* [Maximum line length](https://github.com/RSS-Bridge/rss-bridge/wiki/Maximum-line-length)
|
||||
* [The maximum line length should not exceed 80 characters](https://github.com/RSS-Bridge/rss-bridge/wiki/Maximum-line-length#the-maximum-line-length-should-not-exceed-80-characters)
|
||||
* [Strings](https://github.com/RSS-Bridge/rss-bridge/wiki/Strings)
|
||||
* [Whenever possible use single quoted strings](https://github.com/RSS-Bridge/rss-bridge/wiki/Strings#whenever-possible-use-single-quote-strings)
|
||||
* [Add spaces around the concatenation operator](https://github.com/RSS-Bridge/rss-bridge/wiki/Strings#add-spaces-around-the-concatenation-operator)
|
||||
* [Use a single string instead of concatenating](https://github.com/RSS-Bridge/rss-bridge/wiki/Strings#use-a-single-string-instead-of-concatenating)
|
||||
* [Constants](https://github.com/RSS-Bridge/rss-bridge/wiki/Constants)
|
||||
* [Use UPPERCASE for constants](https://github.com/RSS-Bridge/rss-bridge/wiki/Constants#use-uppercase-for-constants)
|
||||
* [Keywords](https://github.com/RSS-Bridge/rss-bridge/wiki/Keywords)
|
||||
* [Use lowercase for `true`, `false` and `null`](https://github.com/RSS-Bridge/rss-bridge/wiki/Keywords#use-lowercase-for-true-false-and-null)
|
||||
* [Operators](https://github.com/RSS-Bridge/rss-bridge/wiki/Operators)
|
||||
* [Operators must have a space around them](https://github.com/RSS-Bridge/rss-bridge/wiki/Operators#operators-must-have-a-space-around-them)
|
||||
* [Functions](https://github.com/RSS-Bridge/rss-bridge/wiki/Functions)
|
||||
* [Parameters with default values must appear last in functions](https://github.com/RSS-Bridge/rss-bridge/wiki/Functions#parameters-with-default-values-must-appear-last-in-functions)
|
||||
* [Calling functions](https://github.com/RSS-Bridge/rss-bridge/wiki/Functions#calling-functions)
|
||||
* [Do not add spaces after opening or before closing bracket](https://github.com/RSS-Bridge/rss-bridge/wiki/Functions#do-not-add-spaces-after-opening-or-before-closing-bracket)
|
||||
* [Structures](https://github.com/RSS-Bridge/rss-bridge/wiki/Structures)
|
||||
* [Structures must always be formatted as multi-line blocks](https://github.com/RSS-Bridge/rss-bridge/wiki/Structures#structures-must-always-be-formatted-as-multi-line-blocks)
|
||||
* [If-Statement](https://github.com/RSS-Bridge/rss-bridge/wiki/if-Statement)
|
||||
* [Use `elseif` instead of `else if`](https://github.com/RSS-Bridge/rss-bridge/wiki/if-Statement#use-elseif-instead-of-else-if)
|
||||
* [Do not write empty statements](https://github.com/RSS-Bridge/rss-bridge/wiki/if-Statement#do-not-write-empty-statements)
|
||||
* [Do not write unconditional if-statements](https://github.com/RSS-Bridge/rss-bridge/wiki/if-Statement#do-not-write-unconditional-if-statements)
|
||||
* [Classes](https://github.com/RSS-Bridge/rss-bridge/wiki/Classes)
|
||||
* [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)
|
||||
* [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)
|
||||
|
@@ -4,6 +4,8 @@ rss-bridge
|
||||
|
||||
RSS-Bridge is a PHP project capable of generating RSS and Atom feeds for websites which don't have one. It can be used on webservers or as stand alone application in CLI mode.
|
||||
|
||||
**Important**: RSS-Bridge is __not__ a feed reader or feed aggregator, but a tool to generate feeds that are consumed by feed readers and feed aggregators. Find a list of feed aggregators on [Wikipedia](https://en.wikipedia.org/wiki/Comparison_of_feed_aggregators).
|
||||
|
||||
Supported sites/pages (examples)
|
||||
===
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
class AskfmBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'az5he6ch';
|
||||
const MAINTAINER = 'az5he6ch, logmanoriginal';
|
||||
const NAME = 'Ask.fm Answers';
|
||||
const URI = 'https://ask.fm/';
|
||||
const CACHE_TIMEOUT = 300; //5 min
|
||||
@@ -19,39 +19,39 @@ class AskfmBridge extends BridgeAbstract {
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Requested username can\'t be found.');
|
||||
|
||||
foreach($html->find('div.streamItem-answer') as $element) {
|
||||
$html = defaultLinkTo($html, self::URI);
|
||||
|
||||
foreach($html->find('article.streamItem-answer') as $element) {
|
||||
$item = array();
|
||||
$item['uri'] = self::URI . $element->find('a.streamItemsAge', 0)->href;
|
||||
$question = trim($element->find('h1.streamItemContent-question', 0)->innertext);
|
||||
$item['uri'] = $element->find('a.streamItem_meta', 0)->href;
|
||||
$question = trim($element->find('header.streamItem_header', 0)->innertext);
|
||||
|
||||
$item['title'] = trim(
|
||||
htmlspecialchars_decode($element->find('h1.streamItemContent-question', 0)->plaintext,
|
||||
htmlspecialchars_decode($element->find('header.streamItem_header', 0)->plaintext,
|
||||
ENT_QUOTES
|
||||
)
|
||||
);
|
||||
|
||||
$answer = trim($element->find('p.streamItemContent-answer', 0)->innertext);
|
||||
$item['timestamp'] = strtotime($element->find('time', 0)->datetime);
|
||||
|
||||
// Doesn't work, DOM parser doesn't seem to like data-hint, dunno why
|
||||
#$item['update'] = $element->find('a.streamitemsage',0)->data-hint;
|
||||
$answer = trim($element->find('div.streamItem_content', 0)->innertext);
|
||||
|
||||
// This probably should be cleaned up, especially for YouTube embeds
|
||||
$visual = $element->find('div.streamItemContent-visual', 0)->innertext;
|
||||
if($visual = $element->find('div.streamItem_visual', 0)) {
|
||||
$visual = $visual->innertext;
|
||||
}
|
||||
|
||||
// Fix tracking links, also doesn't work
|
||||
foreach($element->find('a') as $link) {
|
||||
if(strpos($link->href, 'l.ask.fm') !== false) {
|
||||
|
||||
// Too slow
|
||||
#$link->href = str_replace('#_=_', '', get_headers($link->href, 1)['Location']);
|
||||
|
||||
$link->href = $link->plaintext;
|
||||
}
|
||||
}
|
||||
|
||||
$content = '<p>' . $question . '</p><p>' . $answer . '</p><p>' . $visual . '</p>';
|
||||
// Fix relative links without breaking // scheme used by YouTube stuff
|
||||
$content = preg_replace('#href="\/(?!\/)#', 'href="' . self::URI, $content);
|
||||
$item['content'] = $content;
|
||||
$item['content'] = '<p>' . $question
|
||||
. '</p><p>' . $answer
|
||||
. '</p><p>' . $visual . '</p>';
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
@@ -66,7 +66,7 @@ class AskfmBridge extends BridgeAbstract {
|
||||
|
||||
public function getURI(){
|
||||
if(!is_null($this->getInput('u'))) {
|
||||
return self::URI . urlencode($this->getInput('u')) . '/answers/more?page=0';
|
||||
return self::URI . urlencode($this->getInput('u'));
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
|
@@ -19,6 +19,10 @@ class AutoJMBridge extends BridgeAbstract {
|
||||
);
|
||||
const CACHE_TIMEOUT = 3600;
|
||||
|
||||
public function getIcon() {
|
||||
return self::URI . 'assets/images/favicon.ico';
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$html = getSimpleHTMLDOM(self::URI . $this->getInput('url'))
|
||||
or returnServerError('Could not request AutoJM.');
|
||||
@@ -59,4 +63,3 @@ class AutoJMBridge extends BridgeAbstract {
|
||||
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
265
bridges/BAEBridge.php
Normal file
265
bridges/BAEBridge.php
Normal file
@@ -0,0 +1,265 @@
|
||||
<?php
|
||||
class BAEBridge extends BridgeAbstract {
|
||||
const MAINTAINER = 'couraudt';
|
||||
const NAME = 'Bourse Aux Equipiers Bridge';
|
||||
const URI = 'https://www.bourse-aux-equipiers.com';
|
||||
const DESCRIPTION = 'Returns the newest sailing offers.';
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'keyword' => array(
|
||||
'name' => 'Filtrer par mots clés',
|
||||
'title' => 'Entrez le mot clé à filtrer ici'
|
||||
),
|
||||
'type' => array(
|
||||
'name' => 'Type de recherche',
|
||||
'title' => 'Afficher seuleument un certain type d\'annonce',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Toutes les annonces' => false,
|
||||
'Les embarquements' => 'boat',
|
||||
'Les skippers' => 'skipper',
|
||||
'Les équipiers' => 'crew'
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData() {
|
||||
$url = $this->getURI();
|
||||
$html = getSimpleHTMLDOM($url) or returnClientError('No results for this query.');
|
||||
|
||||
$annonces = $html->find('main article');
|
||||
foreach ($annonces as $annonce) {
|
||||
$detail = $annonce->find('footer a', 0);
|
||||
|
||||
$htmlDetail = getSimpleHTMLDOMCached(parent::getURI() . $detail->href);
|
||||
if (!$htmlDetail)
|
||||
continue;
|
||||
|
||||
$item = array();
|
||||
|
||||
$item['title'] = $annonce->find('header h2', 0)->plaintext;
|
||||
$item['uri'] = parent::getURI() . $detail->href;
|
||||
|
||||
$content = $htmlDetail->find('article p', 0)->innertext;
|
||||
if (!empty($this->getInput('keyword'))) {
|
||||
$keyword = $this->remove_accents(strtolower($this->getInput('keyword')));
|
||||
$cleanTitle = $this->remove_accents(strtolower($item['title']));
|
||||
if (strpos($cleanTitle, $keyword) === false) {
|
||||
$cleanContent = $this->remove_accents(strtolower($content));
|
||||
if (strpos($cleanContent, $keyword) === false) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$content .= '<hr>';
|
||||
$content .= $htmlDetail->find('section', 0)->innertext;
|
||||
$content = str_replace('src="/', 'src="' . parent::getURI() . '/', $content);
|
||||
$content = str_replace('href="/', 'href="' . parent::getURI() . '/', $content);
|
||||
$item['content'] = $content;
|
||||
$image = $htmlDetail->find('#zoom', 0);
|
||||
if ($image) {
|
||||
$item['enclosures'] = array(parent::getURI() . $image->getAttribute('src'));
|
||||
}
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
$uri = parent::getURI();
|
||||
if (!empty($this->getInput('type'))) {
|
||||
if ($this->getInput('type') == 'boat') {
|
||||
$uri .= '/embarquements.html';
|
||||
} elseif ($this->getInput('type') == 'skipper') {
|
||||
$uri .= '/skippers.html';
|
||||
} else {
|
||||
$uri .= '/equipiers.html';
|
||||
}
|
||||
}
|
||||
|
||||
return $uri;
|
||||
}
|
||||
|
||||
private function remove_accents($string) {
|
||||
$chars = array(
|
||||
// Decompositions for Latin-1 Supplement
|
||||
'ª' => 'a', 'º' => 'o',
|
||||
'À' => 'A', 'Á' => 'A',
|
||||
'Â' => 'A', 'Ã' => 'A',
|
||||
'Ä' => 'A', 'Å' => 'A',
|
||||
'Æ' => 'AE', 'Ç' => 'C',
|
||||
'È' => 'E', 'É' => 'E',
|
||||
'Ê' => 'E', 'Ë' => 'E',
|
||||
'Ì' => 'I', 'Í' => 'I',
|
||||
'Î' => 'I', 'Ï' => 'I',
|
||||
'Ð' => 'D', 'Ñ' => 'N',
|
||||
'Ò' => 'O', 'Ó' => 'O',
|
||||
'Ô' => 'O', 'Õ' => 'O',
|
||||
'Ö' => 'O', 'Ù' => 'U',
|
||||
'Ú' => 'U', 'Û' => 'U',
|
||||
'Ü' => 'U', 'Ý' => 'Y',
|
||||
'Þ' => 'TH', 'ß' => 's',
|
||||
'à' => 'a', 'á' => 'a',
|
||||
'â' => 'a', 'ã' => 'a',
|
||||
'ä' => 'a', 'å' => 'a',
|
||||
'æ' => 'ae', 'ç' => 'c',
|
||||
'è' => 'e', 'é' => 'e',
|
||||
'ê' => 'e', 'ë' => 'e',
|
||||
'ì' => 'i', 'í' => 'i',
|
||||
'î' => 'i', 'ï' => 'i',
|
||||
'ð' => 'd', 'ñ' => 'n',
|
||||
'ò' => 'o', 'ó' => 'o',
|
||||
'ô' => 'o', 'õ' => 'o',
|
||||
'ö' => 'o', 'ø' => 'o',
|
||||
'ù' => 'u', 'ú' => 'u',
|
||||
'û' => 'u', 'ü' => 'u',
|
||||
'ý' => 'y', 'þ' => 'th',
|
||||
'ÿ' => 'y', 'Ø' => 'O',
|
||||
// Decompositions for Latin Extended-A
|
||||
'Ā' => 'A', 'ā' => 'a',
|
||||
'Ă' => 'A', 'ă' => 'a',
|
||||
'Ą' => 'A', 'ą' => 'a',
|
||||
'Ć' => 'C', 'ć' => 'c',
|
||||
'Ĉ' => 'C', 'ĉ' => 'c',
|
||||
'Ċ' => 'C', 'ċ' => 'c',
|
||||
'Č' => 'C', 'č' => 'c',
|
||||
'Ď' => 'D', 'ď' => 'd',
|
||||
'Đ' => 'D', 'đ' => 'd',
|
||||
'Ē' => 'E', 'ē' => 'e',
|
||||
'Ĕ' => 'E', 'ĕ' => 'e',
|
||||
'Ė' => 'E', 'ė' => 'e',
|
||||
'Ę' => 'E', 'ę' => 'e',
|
||||
'Ě' => 'E', 'ě' => 'e',
|
||||
'Ĝ' => 'G', 'ĝ' => 'g',
|
||||
'Ğ' => 'G', 'ğ' => 'g',
|
||||
'Ġ' => 'G', 'ġ' => 'g',
|
||||
'Ģ' => 'G', 'ģ' => 'g',
|
||||
'Ĥ' => 'H', 'ĥ' => 'h',
|
||||
'Ħ' => 'H', 'ħ' => 'h',
|
||||
'Ĩ' => 'I', 'ĩ' => 'i',
|
||||
'Ī' => 'I', 'ī' => 'i',
|
||||
'Ĭ' => 'I', 'ĭ' => 'i',
|
||||
'Į' => 'I', 'į' => 'i',
|
||||
'İ' => 'I', 'ı' => 'i',
|
||||
'IJ' => 'IJ', 'ij' => 'ij',
|
||||
'Ĵ' => 'J', 'ĵ' => 'j',
|
||||
'Ķ' => 'K', 'ķ' => 'k',
|
||||
'ĸ' => 'k', 'Ĺ' => 'L',
|
||||
'ĺ' => 'l', 'Ļ' => 'L',
|
||||
'ļ' => 'l', 'Ľ' => 'L',
|
||||
'ľ' => 'l', 'Ŀ' => 'L',
|
||||
'ŀ' => 'l', 'Ł' => 'L',
|
||||
'ł' => 'l', 'Ń' => 'N',
|
||||
'ń' => 'n', 'Ņ' => 'N',
|
||||
'ņ' => 'n', 'Ň' => 'N',
|
||||
'ň' => 'n', 'ʼn' => 'n',
|
||||
'Ŋ' => 'N', 'ŋ' => 'n',
|
||||
'Ō' => 'O', 'ō' => 'o',
|
||||
'Ŏ' => 'O', 'ŏ' => 'o',
|
||||
'Ő' => 'O', 'ő' => 'o',
|
||||
'Œ' => 'OE', 'œ' => 'oe',
|
||||
'Ŕ' => 'R', 'ŕ' => 'r',
|
||||
'Ŗ' => 'R', 'ŗ' => 'r',
|
||||
'Ř' => 'R', 'ř' => 'r',
|
||||
'Ś' => 'S', 'ś' => 's',
|
||||
'Ŝ' => 'S', 'ŝ' => 's',
|
||||
'Ş' => 'S', 'ş' => 's',
|
||||
'Š' => 'S', 'š' => 's',
|
||||
'Ţ' => 'T', 'ţ' => 't',
|
||||
'Ť' => 'T', 'ť' => 't',
|
||||
'Ŧ' => 'T', 'ŧ' => 't',
|
||||
'Ũ' => 'U', 'ũ' => 'u',
|
||||
'Ū' => 'U', 'ū' => 'u',
|
||||
'Ŭ' => 'U', 'ŭ' => 'u',
|
||||
'Ů' => 'U', 'ů' => 'u',
|
||||
'Ű' => 'U', 'ű' => 'u',
|
||||
'Ų' => 'U', 'ų' => 'u',
|
||||
'Ŵ' => 'W', 'ŵ' => 'w',
|
||||
'Ŷ' => 'Y', 'ŷ' => 'y',
|
||||
'Ÿ' => 'Y', 'Ź' => 'Z',
|
||||
'ź' => 'z', 'Ż' => 'Z',
|
||||
'ż' => 'z', 'Ž' => 'Z',
|
||||
'ž' => 'z', 'ſ' => 's',
|
||||
// Decompositions for Latin Extended-B
|
||||
'Ș' => 'S', 'ș' => 's',
|
||||
'Ț' => 'T', 'ț' => 't',
|
||||
// Euro Sign
|
||||
'€' => 'E',
|
||||
// GBP (Pound) Sign
|
||||
'£' => '',
|
||||
// Vowels with diacritic (Vietnamese)
|
||||
// unmarked
|
||||
'Ơ' => 'O', 'ơ' => 'o',
|
||||
'Ư' => 'U', 'ư' => 'u',
|
||||
// grave accent
|
||||
'Ầ' => 'A', 'ầ' => 'a',
|
||||
'Ằ' => 'A', 'ằ' => 'a',
|
||||
'Ề' => 'E', 'ề' => 'e',
|
||||
'Ồ' => 'O', 'ồ' => 'o',
|
||||
'Ờ' => 'O', 'ờ' => 'o',
|
||||
'Ừ' => 'U', 'ừ' => 'u',
|
||||
'Ỳ' => 'Y', 'ỳ' => 'y',
|
||||
// hook
|
||||
'Ả' => 'A', 'ả' => 'a',
|
||||
'Ẩ' => 'A', 'ẩ' => 'a',
|
||||
'Ẳ' => 'A', 'ẳ' => 'a',
|
||||
'Ẻ' => 'E', 'ẻ' => 'e',
|
||||
'Ể' => 'E', 'ể' => 'e',
|
||||
'Ỉ' => 'I', 'ỉ' => 'i',
|
||||
'Ỏ' => 'O', 'ỏ' => 'o',
|
||||
'Ổ' => 'O', 'ổ' => 'o',
|
||||
'Ở' => 'O', 'ở' => 'o',
|
||||
'Ủ' => 'U', 'ủ' => 'u',
|
||||
'Ử' => 'U', 'ử' => 'u',
|
||||
'Ỷ' => 'Y', 'ỷ' => 'y',
|
||||
// tilde
|
||||
'Ẫ' => 'A', 'ẫ' => 'a',
|
||||
'Ẵ' => 'A', 'ẵ' => 'a',
|
||||
'Ẽ' => 'E', 'ẽ' => 'e',
|
||||
'Ễ' => 'E', 'ễ' => 'e',
|
||||
'Ỗ' => 'O', 'ỗ' => 'o',
|
||||
'Ỡ' => 'O', 'ỡ' => 'o',
|
||||
'Ữ' => 'U', 'ữ' => 'u',
|
||||
'Ỹ' => 'Y', 'ỹ' => 'y',
|
||||
// acute accent
|
||||
'Ấ' => 'A', 'ấ' => 'a',
|
||||
'Ắ' => 'A', 'ắ' => 'a',
|
||||
'Ế' => 'E', 'ế' => 'e',
|
||||
'Ố' => 'O', 'ố' => 'o',
|
||||
'Ớ' => 'O', 'ớ' => 'o',
|
||||
'Ứ' => 'U', 'ứ' => 'u',
|
||||
// dot below
|
||||
'Ạ' => 'A', 'ạ' => 'a',
|
||||
'Ậ' => 'A', 'ậ' => 'a',
|
||||
'Ặ' => 'A', 'ặ' => 'a',
|
||||
'Ẹ' => 'E', 'ẹ' => 'e',
|
||||
'Ệ' => 'E', 'ệ' => 'e',
|
||||
'Ị' => 'I', 'ị' => 'i',
|
||||
'Ọ' => 'O', 'ọ' => 'o',
|
||||
'Ộ' => 'O', 'ộ' => 'o',
|
||||
'Ợ' => 'O', 'ợ' => 'o',
|
||||
'Ụ' => 'U', 'ụ' => 'u',
|
||||
'Ự' => 'U', 'ự' => 'u',
|
||||
'Ỵ' => 'Y', 'ỵ' => 'y',
|
||||
// Vowels with diacritic (Chinese, Hanyu Pinyin)
|
||||
'ɑ' => 'a',
|
||||
// macron
|
||||
'Ǖ' => 'U', 'ǖ' => 'u',
|
||||
// acute accent
|
||||
'Ǘ' => 'U', 'ǘ' => 'u',
|
||||
// caron
|
||||
'Ǎ' => 'A', 'ǎ' => 'a',
|
||||
'Ǐ' => 'I', 'ǐ' => 'i',
|
||||
'Ǒ' => 'O', 'ǒ' => 'o',
|
||||
'Ǔ' => 'U', 'ǔ' => 'u',
|
||||
'Ǚ' => 'U', 'ǚ' => 'u',
|
||||
// grave accent
|
||||
'Ǜ' => 'U', 'ǜ' => 'u',
|
||||
);
|
||||
|
||||
$string = strtr($string, $chars);
|
||||
|
||||
return $string;
|
||||
}
|
||||
}
|
@@ -14,6 +14,10 @@ class BandcampBridge extends BridgeAbstract {
|
||||
)
|
||||
));
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://s4.bcbits.com/img/bc_favicon.ico';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('No results for this query.');
|
||||
|
@@ -7,6 +7,10 @@ class BlaguesDeMerdeBridge extends BridgeAbstract {
|
||||
const CACHE_TIMEOUT = 7200; // 2h
|
||||
const DESCRIPTION = 'Blagues De Merde';
|
||||
|
||||
public function getIcon() {
|
||||
return self::URI . 'assets/img/favicon.ico';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
|
@@ -31,6 +31,10 @@ class BloombergBridge extends BridgeAbstract
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://assets.bwbx.io/s3/javelin/public/hub/images/favicon-black-63fe5249d3.png';
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
switch($this->queriedContext) {
|
||||
|
@@ -27,6 +27,10 @@ class BundesbankBridge extends BridgeAbstract {
|
||||
)
|
||||
);
|
||||
|
||||
public function getIcon() {
|
||||
return self::URI . 'resource/crblob/1890/a7f48ee0ae35348748121770ba3ca009/mL/favicon-ico-data.ico';
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
switch($this->getInput(self::PARAM_LANG)) {
|
||||
case self::LANG_EN: return self::URI . 'en/publications/reports/studies';
|
||||
|
@@ -7,6 +7,9 @@ class ChristianDailyReporterBridge extends BridgeAbstract {
|
||||
const DESCRIPTION = 'The Unofficial Christian Daily Reporter RSS';
|
||||
// const CACHE_TIMEOUT = 86400; // 1 day
|
||||
|
||||
public function getIcon() {
|
||||
return self::URI . 'images/cdrfavicon.png';
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$uri = 'https://www.christiandailyreporter.com/';
|
||||
|
@@ -32,6 +32,10 @@ class ContainerLinuxReleasesBridge extends BridgeAbstract {
|
||||
return json_decode($json, true);
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://coreos.com/assets/ico/favicon.png';
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$data = $this->getReleaseFeed($this->getJsonUri());
|
||||
|
||||
|
@@ -48,6 +48,10 @@ class DailymotionBridge extends BridgeAbstract {
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://static1-ssl.dmcdn.net/images/neon/favicons/android-icon-36x36.png.vf806ca4ed0deed812';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = '';
|
||||
$limit = 5;
|
||||
|
@@ -75,6 +75,10 @@ class DiceBridge extends BridgeAbstract {
|
||||
),
|
||||
));
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://assets.dice.com/techpro/img/favicons/favicon.ico';
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$uri = 'https://www.dice.com/jobs/advancedResult.html';
|
||||
$uri .= '?for_one=' . urlencode($this->getInput('for_one'));
|
||||
|
@@ -7,6 +7,11 @@ class DribbbleBridge extends BridgeAbstract {
|
||||
const CACHE_TIMEOUT = 1800;
|
||||
const DESCRIPTION = 'Returns the newest popular shots from Dribbble.';
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://cdn.dribbble.com/assets/
|
||||
favicon-63b2904a073c89b52b19aa08cebc16a154bcf83fee8ecc6439968b1e6db569c7.ico';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM(self::URI . '/shots')
|
||||
or returnServerError('Error while downloading the website content');
|
||||
|
@@ -7,6 +7,11 @@ class EliteDangerousGalnetBridge extends BridgeAbstract {
|
||||
const CACHE_TIMEOUT = 7200; // 2h
|
||||
const DESCRIPTION = 'Returns the latest page of news from Galnet';
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://community.elitedangerous.com/sites/
|
||||
EDSITE_COMM/themes/bootstrap/bootstrap_community/favicon.ico';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
or returnServerError('Error while downloading the website content');
|
||||
|
@@ -57,6 +57,10 @@ class ElsevierBridge extends BridgeAbstract {
|
||||
return '';
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://cdn.elsevier.io/verona/includes/favicons/favicon-32x32.png';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$uri = self::URI . $this->getInput('j') . '/recent-articles/';
|
||||
$html = getSimpleHTMLDOM($uri)
|
||||
|
@@ -17,7 +17,7 @@ class EtsyBridge extends BridgeAbstract {
|
||||
'queryextension' => array(
|
||||
'name' => 'Query extension',
|
||||
'type' => 'text',
|
||||
'requied' => false,
|
||||
'required' => false,
|
||||
'title' => 'Insert additional query parts here
|
||||
(anything after ?search=<your search query>)',
|
||||
'exampleValue' => '&explicit=1&locationQuery=2921044'
|
||||
@@ -25,9 +25,9 @@ class EtsyBridge extends BridgeAbstract {
|
||||
'showimage' => array(
|
||||
'name' => 'Show image in content',
|
||||
'type' => 'checkbox',
|
||||
'requrired' => false,
|
||||
'required' => false,
|
||||
'title' => 'Activate to show the image in the content',
|
||||
'defaultValue' => false
|
||||
'defaultValue' => 'checked'
|
||||
)
|
||||
)
|
||||
);
|
||||
@@ -36,26 +36,27 @@ class EtsyBridge extends BridgeAbstract {
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Failed to receive ' . $this->getURI());
|
||||
|
||||
$results = $html->find('div.block-grid-item');
|
||||
$results = $html->find('li.block-grid-item');
|
||||
|
||||
foreach($results as $result) {
|
||||
// Skip banner cards (ads for categories)
|
||||
if($result->find('a.banner-card'))
|
||||
if($result->find('span.ad-indicator'))
|
||||
continue;
|
||||
|
||||
$item = array();
|
||||
|
||||
$item['title'] = $result->find('a', 0)->title;
|
||||
$item['uri'] = $result->find('a', 0)->href;
|
||||
$item['author'] = $result->find('div.card-shop-name', 0)->plaintext;
|
||||
$item['author'] = $result->find('p.text-gray-lighter', 0)->plaintext;
|
||||
|
||||
$item['content'] = '<p>'
|
||||
. $result->find('div.card-price', 0)->plaintext
|
||||
. $result->find('span.currency-value', 0)->plaintext . ' '
|
||||
. $result->find('span.currency-symbol', 0)->plaintext
|
||||
. '</p><p>'
|
||||
. $result->find('div.card-title', 0)->plaintext
|
||||
. $result->find('a', 0)->title
|
||||
. '</p>';
|
||||
|
||||
$image = $result->find('img.placeholder', 0)->src;
|
||||
$image = $result->find('img.display-block', 0)->src;
|
||||
|
||||
if($this->getInput('showimage')) {
|
||||
$item['content'] .= '<img src="' . $image . '">';
|
||||
|
@@ -15,6 +15,10 @@ class FB2Bridge extends BridgeAbstract {
|
||||
)
|
||||
));
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://static.xx.fbcdn.net/rsrc.php/yo/r/iRmz9lCMBD2.ico';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
|
||||
//Utility function for cleaning a Facebook link
|
||||
@@ -65,14 +69,14 @@ class FB2Bridge extends BridgeAbstract {
|
||||
if($this->getInput('u') !== null) {
|
||||
$page = 'https://touch.facebook.com/' . $this->getInput('u');
|
||||
$cookies = $this->getCookies($page);
|
||||
$pageID = $this->getPageID($page, $cookies);
|
||||
$pageInfo = $this->getPageInfos($page, $cookies);
|
||||
|
||||
if($pageID === null) {
|
||||
if($pageInfo['userId'] === null) {
|
||||
echo <<<EOD
|
||||
Unable to get the page id. You should consider getting the ID by hand, then importing it into FB2Bridge
|
||||
EOD;
|
||||
die();
|
||||
} elseif($pageID == -1) {
|
||||
} elseif($pageInfo['userId'] == -1) {
|
||||
echo <<<EOD
|
||||
This page is not accessible without being logged in.
|
||||
EOD;
|
||||
@@ -81,24 +85,31 @@ EOD;
|
||||
}
|
||||
|
||||
//Build the string for the first request
|
||||
$requestString = 'https://touch.facebook.com/pages_reaction_units/more/?page_id='
|
||||
. $pageID
|
||||
. '&cursor={"card_id"%3A"videos"%2C"has_next_page"%3Atrue}&surface=mobile_page_home&unit_count=8';
|
||||
|
||||
$requestString = 'https://touch.facebook.com/page_content_list_view/more/?page_id='
|
||||
. $pageInfo['userId']
|
||||
. '&start_cursor=1&num_to_fetch=105&surface_type=timeline';
|
||||
$fileContent = getContents($requestString);
|
||||
|
||||
$articleIndex = 0;
|
||||
$maxArticle = 3;
|
||||
|
||||
$html = $this->buildContent($fileContent);
|
||||
$author = $this->getInput('u');
|
||||
$author = $pageInfo['username'];
|
||||
|
||||
foreach($html->find('article') as $content) {
|
||||
|
||||
$item = array();
|
||||
//echo $content; die();
|
||||
preg_match('/publish_time\\\":([0-9]+),/', $content->getAttribute('data-store', 0), $match);
|
||||
if(isset($match[1]))
|
||||
$timestamp = $match[1];
|
||||
else
|
||||
$timestamp = 0;
|
||||
|
||||
$item['uri'] = 'http://touch.facebook.com'
|
||||
. $content->find("div[class='_52jc _5qc4 _24u0 _36xo']", 0)->find('a', 0)->getAttribute('href');
|
||||
$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);
|
||||
|
||||
//Decode images
|
||||
$imagecleaned = preg_replace_callback('/<i [^>]* style="[^"]*url\(\'(.*?)\'\).*?><\/i>/m', function ($matches) {
|
||||
return "<img src='" . str_replace(['\\3a ', '\\3d ', '\\26 '], [':', '=', '&'], $matches[1]) . "' />";
|
||||
}, $content);
|
||||
$content = str_get_html($imagecleaned);
|
||||
|
||||
if($content->find('header', 0) !== null) {
|
||||
$content->find('header', 0)->innertext = '';
|
||||
@@ -108,8 +119,13 @@ EOD;
|
||||
$content->find('footer', 0)->innertext = '';
|
||||
}
|
||||
|
||||
// Replace emoticon images by their textual representation (part of the span)
|
||||
foreach($content->find('span[title*="emoticon"]') as $emoticon) {
|
||||
$emoticon->innertext = $emoticon->find('span[aria-hidden="true"]', 0)->innertext;
|
||||
}
|
||||
|
||||
//Remove html nodes, keep only img, links, basic formatting
|
||||
$content = strip_tags($content, '<a><img><i><u><br><p><h3><h4>');
|
||||
$content = strip_tags($content, '<a><img><i><u><br><p><h3><h4><section>');
|
||||
|
||||
//Adapt link hrefs: convert relative links into absolute links and bypass external link redirection
|
||||
$content = preg_replace_callback('/ href=\"([^"]+)\"/i', $unescape_fb_link, $content);
|
||||
@@ -122,7 +138,6 @@ EOD;
|
||||
'ajaxify',
|
||||
'tabindex',
|
||||
'class',
|
||||
'style',
|
||||
'data-[^=]*',
|
||||
'aria-[^=]*',
|
||||
'role',
|
||||
@@ -135,7 +150,36 @@ EOD;
|
||||
// "<i><u>smile emoticon</u></i>" back to ASCII emoticons eg ":)"
|
||||
$content = preg_replace_callback('/<i><u>([^ <>]+) ([^<>]+)<\/u><\/i>/i', $unescape_fb_emote, $content);
|
||||
|
||||
$item['content'] = $content;
|
||||
//Remove the "...Plus" tag
|
||||
$content = preg_replace(
|
||||
'/… (<span>|)<a href="https:\/\/www\.facebook\.com\/story\.php\?story_fbid=.*?<\/a>/m',
|
||||
'', $content, 1);
|
||||
|
||||
//Remove tracking images
|
||||
$content = preg_replace('/<img src=\'.*?safe_image\.php.*?\' \/>/m', '', $content);
|
||||
|
||||
//Remove the double section tags
|
||||
$content = str_replace(['<section><section>', '</section></section>'], ['<section>', '</section>'], $content);
|
||||
|
||||
//Move the section tag link upper, if it is down
|
||||
$content = str_get_html($content);
|
||||
$sectionContent = $content->find('section', 0);
|
||||
if($sectionContent != null) {
|
||||
$sectionLink = $sectionContent->nextSibling();
|
||||
if($sectionLink != null) {
|
||||
$fullLink = '<a href="' . $sectionLink->getAttribute('href') . '">' . $sectionContent->innertext . '</a>';
|
||||
$sectionContent->innertext = $fullLink;
|
||||
}
|
||||
}
|
||||
|
||||
//Move the href tag upper if it is inside the section
|
||||
foreach($content->find('section > a') as $sectionToFix) {
|
||||
$sectionLink = $sectionToFix->getAttribute('href');
|
||||
$section = $sectionToFix->parent();
|
||||
$section->outertext = '<a href="' . $sectionLink . '">' . $section . '</a>';
|
||||
}
|
||||
|
||||
$item['content'] = html_entity_decode($content, ENT_QUOTES);
|
||||
|
||||
$title = $author;
|
||||
if (strlen($title) > 24)
|
||||
@@ -144,57 +188,29 @@ EOD;
|
||||
if (strlen($title) > 64)
|
||||
$title = substr($title, 0, strpos(wordwrap($title, 64), "\n")) . '...';
|
||||
|
||||
$item['title'] = $title;
|
||||
$item['author'] = $author;
|
||||
$item['title'] = html_entity_decode($title, ENT_QUOTES);
|
||||
$item['author'] = html_entity_decode($author, ENT_QUOTES);
|
||||
$item['timestamp'] = html_entity_decode($timestamp, ENT_QUOTES);
|
||||
|
||||
if($item['timestamp'] != 0)
|
||||
array_push($this->items, $item);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Currently not used. Is used to get more than only 3 elements, as they appear on another page.
|
||||
private function computeNextLink($string, $pageID){
|
||||
|
||||
$regex = implode(
|
||||
'',
|
||||
array(
|
||||
'/timeline_unit',
|
||||
"\\\\\\\\u00253A1",
|
||||
"\\\\\\\\u00253A([0-9]*)",
|
||||
"\\\\\\\\u00253A([0-9]*)",
|
||||
"\\\\\\\\u00253A([0-9]*)",
|
||||
"\\\\\\\\u00253A([0-9]*)/"
|
||||
)
|
||||
);
|
||||
|
||||
preg_match($regex, $string, $result);
|
||||
|
||||
return implode(
|
||||
'',
|
||||
array(
|
||||
'https://touch.facebook.com/pages_reaction_units/more/?page_id=',
|
||||
$pageID,
|
||||
'&cursor=%7B%22timeline_cursor%22%3A%22timeline_unit%3A1%3A',
|
||||
$result[1],
|
||||
'%3A',
|
||||
$result[2],
|
||||
'%3A',
|
||||
$result[3],
|
||||
'%3A',
|
||||
$result[4],
|
||||
'%22%2C%22timeline_section_cursor%22%3A%7B%7D%2C%22',
|
||||
'has_next_page%22%3Atrue%7D&surface=mobile_page_home&unit_count=3'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
//Builds the HTML from the encoded JS that Facebook provides.
|
||||
private function buildContent($pageContent){
|
||||
// The html ends with:
|
||||
// /div>","replaceifexists
|
||||
$regex = '/\\"html\\":(\".+\/div>"),"replace/';
|
||||
preg_match($regex, $pageContent, $result);
|
||||
return str_get_html(html_entity_decode(json_decode($result[1])));
|
||||
|
||||
$htmlContent = json_decode($result[1]);
|
||||
$htmlContent = preg_replace('/(?<!style)="(.*?)"/', '=\'$1\'', $htmlContent);
|
||||
$htmlContent = html_entity_decode($htmlContent, ENT_QUOTES, 'UTF-8');
|
||||
|
||||
return str_get_html($htmlContent);
|
||||
}
|
||||
|
||||
|
||||
@@ -224,8 +240,8 @@ EOD;
|
||||
return substr($cookies, 1);
|
||||
}
|
||||
|
||||
//Get the page ID from the Facebook page.
|
||||
private function getPageID($page, $cookies){
|
||||
//Get the page ID and username from the Facebook page.
|
||||
private function getPageInfos($page, $cookies){
|
||||
|
||||
$context = stream_context_create(array(
|
||||
'http' => array(
|
||||
@@ -241,19 +257,28 @@ EOD;
|
||||
return -1;
|
||||
}
|
||||
|
||||
//Get the username
|
||||
$usernameRegex = '/data-nt=\"FB:TEXT4\">(.*?)<\/div>/m';
|
||||
preg_match($usernameRegex, $pageContent, $usernameMatches);
|
||||
if(count($usernameMatches) > 0) {
|
||||
$username = strip_tags($usernameMatches[1]);
|
||||
} else {
|
||||
$username = $this->getInput('u');
|
||||
}
|
||||
|
||||
//Get the page ID if we don't have a captcha
|
||||
$regex = '/page_id=([0-9]*)&/';
|
||||
preg_match($regex, $pageContent, $matches);
|
||||
|
||||
if(count($matches) > 0) {
|
||||
return $matches[1];
|
||||
return array('userId' => $matches[1], 'username' => $username);
|
||||
}
|
||||
|
||||
//Get the page ID if we do have a captcha
|
||||
$regex = '/"pageID":"([0-9]*)"/';
|
||||
preg_match($regex, $pageContent, $matches);
|
||||
|
||||
return $matches[1];
|
||||
return array('userId' => $matches[1], 'username' => $username);
|
||||
|
||||
}
|
||||
|
||||
|
@@ -19,6 +19,10 @@ class FDroidBridge extends BridgeAbstract {
|
||||
)
|
||||
));
|
||||
|
||||
public function getIcon() {
|
||||
return self::URI . 'assets/favicon.ico?v=8j6PKzW9Mk';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$url = self::URI;
|
||||
$html = getSimpleHTMLDOM($url)
|
||||
|
@@ -56,6 +56,10 @@ class FacebookBridge extends BridgeAbstract {
|
||||
private $authorName = '';
|
||||
private $groupName = '';
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://static.xx.fbcdn.net/rsrc.php/yo/r/iRmz9lCMBD2.ico';
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
|
||||
switch($this->queriedContext) {
|
||||
@@ -95,7 +99,7 @@ class FacebookBridge extends BridgeAbstract {
|
||||
$user = $this->sanitizeUser($this->getInput('u'));
|
||||
|
||||
if(!strpos($user, '/')) {
|
||||
$uri .= '/pg/' . urlencode($user) . '/posts';
|
||||
$uri .= urlencode($user) . '/posts';
|
||||
} else {
|
||||
$uri .= 'pages/' . $user;
|
||||
}
|
||||
@@ -506,8 +510,6 @@ EOD;
|
||||
returnServerError('You must be logged in to view this page. This is not supported by RSS-Bridge.');
|
||||
}
|
||||
|
||||
$html = defaultLinkTo($html, self::URI);
|
||||
|
||||
$element = $html
|
||||
->find('#pagelet_timeline_main_column')[0]
|
||||
->children(0)
|
||||
@@ -554,11 +556,7 @@ EOD;
|
||||
|
||||
if(count($post->find('abbr')) > 0) {
|
||||
|
||||
//Retrieve post contents
|
||||
$content = preg_replace(
|
||||
'/(?i)><div class=\"clearfix([^>]+)>(.+?)div\ class=\"userContent\"/i',
|
||||
'',
|
||||
$post);
|
||||
$content = $post->find('.userContentWrapper', 0);
|
||||
|
||||
$content = preg_replace(
|
||||
'/(?i)><div class=\"_59tj([^>]+)>(.+?)<\/div><\/div><a/i',
|
||||
@@ -607,6 +605,12 @@ EOD;
|
||||
|
||||
$this->unescape_fb_emote($content);
|
||||
|
||||
// Restore links in the post before further parsing
|
||||
$post = defaultLinkTo($post, self::URI);
|
||||
|
||||
// Restore links in the content before adding to the item
|
||||
$content = defaultLinkTo($content, self::URI);
|
||||
|
||||
// Retrieve date of the post
|
||||
$date = $post->find('abbr')[0];
|
||||
|
||||
|
@@ -7,6 +7,10 @@ class FierPandaBridge extends BridgeAbstract {
|
||||
const CACHE_TIMEOUT = 21600; // 6h
|
||||
const DESCRIPTION = 'Returns latest articles from Fier Panda.';
|
||||
|
||||
public function getIcon() {
|
||||
return self::URI . 'wp-content/themes/fier-panda/img/favicon.png';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
|
@@ -54,7 +54,7 @@ class GitHubGistBridge extends BridgeAbstract {
|
||||
DEFAULT_SPAN_TEXT)
|
||||
or returnServerError('Could not request ' . $this->getURI());
|
||||
|
||||
$html = defaultLinkTo($html, static::URI);
|
||||
$html = defaultLinkTo($html, $this->getURI());
|
||||
|
||||
$fileinfo = $html->find('[class="file-info"]', 0)
|
||||
or returnServerError('Could not find file info!');
|
||||
@@ -69,7 +69,7 @@ class GitHubGistBridge extends BridgeAbstract {
|
||||
|
||||
foreach($comments as $comment) {
|
||||
|
||||
$uri = $comment->find('a[href^=#gistcomment]', 0)
|
||||
$uri = $comment->find('a[href*=#gistcomment]', 0)
|
||||
or returnServerError('Could not find comment anchor!');
|
||||
|
||||
$title = $comment->find('div[class="unminimized-comment"] h3[class="timeline-comment-header-text"]', 0)
|
||||
@@ -86,7 +86,7 @@ class GitHubGistBridge extends BridgeAbstract {
|
||||
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = $this->getURI() . $uri->href;
|
||||
$item['uri'] = $uri->href;
|
||||
$item['title'] = str_replace('commented', 'commented on', $title->plaintext);
|
||||
$item['timestamp'] = strtotime($datetime->datetime);
|
||||
$item['author'] = '<a href="' . $author->href . '">' . $author->plaintext . '</a>';
|
||||
|
@@ -22,6 +22,10 @@ class GooglePlusPostBridge extends BridgeAbstract{
|
||||
)
|
||||
));
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://ssl.gstatic.com/images/branding/product/ico/google_plus_alldp.ico';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
|
||||
$username = $this->getInput('username');
|
||||
|
@@ -3,7 +3,7 @@ class InstagramBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'pauder';
|
||||
const NAME = 'Instagram Bridge';
|
||||
const URI = 'https://instagram.com/';
|
||||
const URI = 'https://www.instagram.com/';
|
||||
const DESCRIPTION = 'Returns the newest images';
|
||||
|
||||
const PARAMETERS = array(
|
||||
@@ -101,16 +101,21 @@ class InstagramBridge extends BridgeAbstract {
|
||||
$mediaInfo = $data->entry_data->PostPage[0]->graphql->shortcode_media;
|
||||
|
||||
//Process the first element, that isn't in the node graph
|
||||
if (count($mediaInfo->edge_media_to_caption->edges) > 0) {
|
||||
$caption = $mediaInfo->edge_media_to_caption->edges[0]->node->text;
|
||||
} else {
|
||||
$caption = '';
|
||||
}
|
||||
|
||||
$enclosures = [$mediaInfo->display_url];
|
||||
$content = '<img src="' . htmlentities($mediaInfo->display_url) . '" alt="' . $caption . '" />';
|
||||
|
||||
foreach($mediaInfo->edge_sidecar_to_children->edges as $media) {
|
||||
|
||||
$content .= '<img src="' . htmlentities($media->node->display_url) . '" alt="'. $caption . '" />';
|
||||
$enclosures[] = $media->node->display_url;
|
||||
|
||||
$display_url = $media->node->display_url;
|
||||
if(!in_array($display_url, $enclosures)) { // add only if not added yet
|
||||
$content .= '<img src="' . htmlentities($display_url) . '" alt="' . $caption . '" />';
|
||||
$enclosures[] = $display_url;
|
||||
}
|
||||
}
|
||||
|
||||
return [$content, $enclosures];
|
||||
@@ -139,7 +144,7 @@ class InstagramBridge extends BridgeAbstract {
|
||||
|
||||
public function getURI(){
|
||||
if(!is_null($this->getInput('u'))) {
|
||||
return self::URI . urlencode($this->getInput('u'));
|
||||
return self::URI . urlencode($this->getInput('u')) . '/';
|
||||
} elseif(!is_null($this->getInput('h'))) {
|
||||
return self::URI . 'explore/tags/' . urlencode($this->getInput('h'));
|
||||
}
|
||||
|
@@ -13,6 +13,10 @@ class JapanExpoBridge extends BridgeAbstract {
|
||||
)
|
||||
));
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://s.japan-expo.com/katana/images/JES073/favicons/paris.png';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
|
||||
function frenchPubDateToTimestamp($date_to_parse) {
|
||||
|
@@ -36,6 +36,11 @@ class KATBridge extends BridgeAbstract {
|
||||
'name' => 'Only get results from Elite or Verified uploaders ?',
|
||||
),
|
||||
));
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://statuskatcrco-631f.kxcdn.com/assets/images/favicon.ico';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
function parseDateTimestamp($element){
|
||||
$guessedDate = strptime($element, '%d-%m-%Y %H:%M:%S');
|
||||
|
@@ -38,6 +38,10 @@ class KernelBugTrackerBridge extends BridgeAbstract {
|
||||
private $bugid = '';
|
||||
private $bugdesc = '';
|
||||
|
||||
public function getIcon() {
|
||||
return self::URI . '/images/favicon.ico';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$limit = $this->getInput('limit');
|
||||
$sorting = $this->getInput('sorting');
|
||||
|
@@ -38,6 +38,10 @@ class LegifranceJOBridge extends BridgeAbstract {
|
||||
return $item;
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://www.legifrance.gouv.fr/img/favicon.ico';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
or $this->returnServer('Unable to download ' . self::URI);
|
||||
|
@@ -26,6 +26,10 @@ class NotAlwaysBridge extends BridgeAbstract {
|
||||
)
|
||||
));
|
||||
|
||||
public function getIcon() {
|
||||
return self::URI . 'favicon_nar.png';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Could not request NotAlways.');
|
||||
|
@@ -54,6 +54,10 @@ class NyaaTorrentsBridge extends BridgeAbstract {
|
||||
)
|
||||
);
|
||||
|
||||
public function getIcon() {
|
||||
return self::URI . 'static/favicon.png';
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
|
||||
// Build Search URL from user-provided parameters
|
||||
|
@@ -25,6 +25,10 @@ class PinterestBridge extends FeedExpander {
|
||||
)
|
||||
);
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://s.pinimg.com/webapp/style/images/favicon-9f8f9adf.png';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
switch($this->queriedContext) {
|
||||
case 'By username and board':
|
||||
|
@@ -5,6 +5,10 @@ class RadioMelodieBridge extends BridgeAbstract {
|
||||
const DESCRIPTION = 'Retourne les actualités publiées par Radio Melodie';
|
||||
const MAINTAINER = 'sysadminstory';
|
||||
|
||||
public function getIcon() {
|
||||
return self::URI . 'img/favicon.png';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM(self::URI . 'actu')
|
||||
or returnServerError('Could not request Radio Melodie.');
|
||||
|
@@ -7,6 +7,10 @@ class RainbowSixSiegeBridge extends BridgeAbstract {
|
||||
const CACHE_TIMEOUT = 7200; // 2h
|
||||
const DESCRIPTION = 'Latest articles from the Rainbow Six Siege blog';
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://ubistatic19-a.akamaihd.net/resource/en-us/game/rainbow6/siege-v3/r6s-favicon_316592.ico';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$dlUrl = 'https://prod-tridionservice.ubisoft.com/live/v1/News/Latest?templateId=tcm%3A152-7677';
|
||||
$dlUrl .= '8-32&pageIndex=0&pageSize=10&language=en-US&detailPageId=tcm%3A152-194572-64';
|
||||
|
@@ -13,6 +13,10 @@ class SupInfoBridge extends BridgeAbstract {
|
||||
)
|
||||
));
|
||||
|
||||
public function getIcon() {
|
||||
return self::URI . '/favicon.png';
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
|
||||
if(empty($this->getInput('tag'))) {
|
||||
|
@@ -14,6 +14,10 @@ class TagBoardBridge extends BridgeAbstract {
|
||||
)
|
||||
));
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://static.tagboard.com/public/favicon-32x32.png';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$link = 'https://post-cache.tagboard.com/search/' . $this->getInput('u');
|
||||
|
||||
|
@@ -21,6 +21,10 @@ class TebeoBridge extends FeedExpander {
|
||||
)
|
||||
));
|
||||
|
||||
public function getIcon() {
|
||||
return self::URI . 'images/header_logo.png';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$url = self::URI . '/le-replay/' . $this->getInput('cat');
|
||||
$html = getSimpleHTMLDOM($url)
|
||||
|
@@ -158,6 +158,10 @@ class TheTVDBBridge extends BridgeAbstract {
|
||||
}
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
return self::URI . 'application/themes/thetvdb/images/logo.png';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$serie_id = $this->getInput('serie_id');
|
||||
$nbepisode = $this->getInput('nb_episode');
|
||||
|
167
bridges/ThingiverseBridge.php
Normal file
167
bridges/ThingiverseBridge.php
Normal file
@@ -0,0 +1,167 @@
|
||||
<?php
|
||||
class ThingiverseBridge extends BridgeAbstract {
|
||||
|
||||
const NAME = 'Thingiverse Search';
|
||||
const URI = 'https://thingiverse.com';
|
||||
const DESCRIPTION = 'Returns feeds for search results';
|
||||
const MAINTAINER = 'AntoineTurmel';
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'query' => array(
|
||||
'name' => 'Search query',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'title' => 'Insert your search term here',
|
||||
'exampleValue' => 'Enter your search term'
|
||||
),
|
||||
'sortby' => array(
|
||||
'name' => 'Sort by',
|
||||
'type' => 'list',
|
||||
'required' => false,
|
||||
'values' => array(
|
||||
'Relevant' => 'relevant',
|
||||
'Text' => 'text',
|
||||
'Popular' => 'popular',
|
||||
'# of Makes' => 'makes',
|
||||
'Newest' => 'newest',
|
||||
),
|
||||
'defaultValue' => 'newest'
|
||||
),
|
||||
'category' => array(
|
||||
'name' => 'Category',
|
||||
'type' => 'list',
|
||||
'required' => false,
|
||||
'values' => array(
|
||||
'Any' => '',
|
||||
'3D Printing' => '73',
|
||||
'Art' => '63',
|
||||
'Fashion' => '64',
|
||||
'Gadgets' => '65',
|
||||
'Hobby' => '66',
|
||||
'Household' => '67',
|
||||
'Learning' => '69',
|
||||
'Models' => '70',
|
||||
'Tools' => '71',
|
||||
'Toys & Games' => '72',
|
||||
'2D Art' => '144',
|
||||
'Art Tools' => '75',
|
||||
'Coins & Badges' => '143',
|
||||
'Interactive Art' => '78',
|
||||
'Math Art' => '79',
|
||||
'Scans & Replicas' => '145',
|
||||
'Sculptures' => '80',
|
||||
'Signs & Logos' => '76',
|
||||
'Accessories' => '81',
|
||||
'Bracelets' => '82',
|
||||
'Costume' => '142',
|
||||
'Earrings' => '139',
|
||||
'Glasses' => '83',
|
||||
'Jewelry' => '84',
|
||||
'Keychains' => '130',
|
||||
'Rings' => '85',
|
||||
'Audio' => '141',
|
||||
'Camera' => '86',
|
||||
'Computer' => '87',
|
||||
'Mobile Phone' => '88',
|
||||
'Tablet' => '90',
|
||||
'Video Games' => '91',
|
||||
'Automotive' => '155',
|
||||
'DIY' => '93',
|
||||
'Electronics' => '92',
|
||||
'Music' => '94',
|
||||
'R/C Vehicles' => '95',
|
||||
'Robotics' => '96',
|
||||
'Sport & Outdoors' => '140',
|
||||
'Bathroom' => '147',
|
||||
'Containers' => '146',
|
||||
'Decor' => '97',
|
||||
'Household Supplies' => '99',
|
||||
'Kitchen & Dining' => '100',
|
||||
'Office' => '101',
|
||||
'Organization' => '102',
|
||||
'Outdoor & Garden' => '98',
|
||||
'Pets' => '103',
|
||||
'Replacement Parts' => '153',
|
||||
'Biology' => '106',
|
||||
'Engineering' => '104',
|
||||
'Math' => '105',
|
||||
'Physics & Astronomy' => '148',
|
||||
'Animals' => '107',
|
||||
'Buildings & Structures' => '108',
|
||||
'Creatures' => '109',
|
||||
'Food & Drink' => '110',
|
||||
'Model Furniture' => '111',
|
||||
'Model Robots' => '115',
|
||||
'People' => '112',
|
||||
'Props' => '114',
|
||||
'Vehicles' => '116',
|
||||
'Hand Tools' => '118',
|
||||
'Machine Tools' => '117',
|
||||
'Parts' => '119',
|
||||
'Tool Holders & Boxes' => '120',
|
||||
'Chess' => '151',
|
||||
'Construction Toys' => '121',
|
||||
'Dice' => '122',
|
||||
'Games' => '123',
|
||||
'Mechanical Toys' => '124',
|
||||
'Playsets' => '113',
|
||||
'Puzzles' => '125',
|
||||
'Toy & Game Accessories' => '149',
|
||||
'3D Printer Accessories' => '127',
|
||||
'3D Printer Extruders' => '152',
|
||||
'3D Printer Parts' => '128',
|
||||
'3D Printers' => '126',
|
||||
'3D Printing Tests' => '129',
|
||||
),
|
||||
'defaultValue' => ''
|
||||
),
|
||||
'showimage' => array(
|
||||
'name' => 'Show image in content',
|
||||
'type' => 'checkbox',
|
||||
'required' => false,
|
||||
'title' => 'Activate to show the image in the content',
|
||||
'defaultValue' => 'checked'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Failed to receive ' . $this->getURI());
|
||||
|
||||
$results = $html->find('div.thing-card');
|
||||
|
||||
foreach($results as $result) {
|
||||
|
||||
$item = array();
|
||||
|
||||
$item['title'] = $result->find('span.ellipsis', 0);
|
||||
$item['uri'] = self::URI . $result->find('a', 1)->href;
|
||||
$item['author'] = $result->find('span.item-creator', 0);
|
||||
$item['content'] = '';
|
||||
|
||||
$image = $result->find('img.card-img', 0)->src;
|
||||
|
||||
if($this->getInput('showimage')) {
|
||||
$item['content'] .= '<img src="' . $image . '">';
|
||||
}
|
||||
|
||||
$item['enclosures'] = array($image);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI(){
|
||||
if(!is_null($this->getInput('query'))) {
|
||||
$uri = self::URI . '/search?q=' . urlencode($this->getInput('query'));
|
||||
$uri .= '&sort=' . $this->getInput('sortby');
|
||||
$uri .= '&category_id=' . $this->getInput('category');
|
||||
|
||||
return $uri;
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
}
|
@@ -16,6 +16,11 @@ class WhydBridge extends BridgeAbstract {
|
||||
|
||||
private $userName = '';
|
||||
|
||||
public function getIcon() {
|
||||
return self::URI . 'assets/favicons/
|
||||
32-6b62a9f14d5e1a9213090d8f00f286bba3a6022381a76390d1d0926493b12593.png?v=6';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = '';
|
||||
if(strlen(preg_replace('/[^0-9a-f]/', '', $this->getInput('u'))) == 24) {
|
||||
|
462
bridges/XenForoBridge.php
Normal file
462
bridges/XenForoBridge.php
Normal file
@@ -0,0 +1,462 @@
|
||||
<?php
|
||||
/**
|
||||
* This bridge generates feeds for threads from forums running XenForo version 2
|
||||
*
|
||||
* Examples:
|
||||
* - https://xenforo.com/community/
|
||||
* - http://www.ign.com/boards/
|
||||
*
|
||||
* Notice: XenForo does provide RSS feeds for forums. For example:
|
||||
* - https://xenforo.com/community/forums/-/index.rss
|
||||
*
|
||||
* For more information on XenForo, visit
|
||||
* - https://xenforo.com/
|
||||
* - https://en.wikipedia.org/wiki/XenForo
|
||||
*/
|
||||
class XenForoBridge extends BridgeAbstract {
|
||||
|
||||
// Bridge specific constants
|
||||
const CONTEXT_THREAD = 'Thread';
|
||||
const XENFORO_VERSION_1 = '1.0';
|
||||
const XENFORO_VERSION_2 = '2.0';
|
||||
|
||||
// RSS-Bridge constants
|
||||
const NAME = 'XenForo Bridge';
|
||||
const URI = 'https://xenforo.com/';
|
||||
const DESCRIPTION = 'Generates feeds for threads in forums powered by XenForo';
|
||||
const MAINTAINER = 'logmanoriginal';
|
||||
const PARAMETERS = array(
|
||||
self::CONTEXT_THREAD => array(
|
||||
'url' => array(
|
||||
'name' => 'Thread URL',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'title' => 'Insert URL to the thread for which the feed should be generated',
|
||||
'exampleValue' => 'https://xenforo.com/community/threads/guide-to-suggestions.2285/'
|
||||
)
|
||||
),
|
||||
'global' => array(
|
||||
'limit' => array(
|
||||
'name' => 'Limit',
|
||||
'type' => 'number',
|
||||
'required' => false,
|
||||
'title' => 'Specify maximum number of elements to return in the feed',
|
||||
'defaultValue' => 10
|
||||
)
|
||||
)
|
||||
);
|
||||
const CACHE_TIMEOUT = 7200; // 10 minutes
|
||||
|
||||
private $title = '';
|
||||
private $threadurl = '';
|
||||
private $version; // Holds the XenForo version
|
||||
|
||||
public function getName() {
|
||||
|
||||
switch($this->queriedContext) {
|
||||
case self::CONTEXT_THREAD: return $this->title . ' - ' . static::NAME;
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
|
||||
switch($this->queriedContext) {
|
||||
case self::CONTEXT_THREAD: return $this->threadurl;
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
|
||||
$this->threadurl = filter_var(
|
||||
$this->getInput('url'),
|
||||
FILTER_VALIDATE_URL,
|
||||
FILTER_FLAG_SCHEME_REQUIRED | FILTER_FLAG_HOST_REQUIRED | FILTER_FLAG_PATH_REQUIRED);
|
||||
|
||||
if($this->threadurl === false) {
|
||||
returnClientError('The URL you provided is invalid!');
|
||||
}
|
||||
|
||||
$urlparts = parse_url($this->threadurl, PHP_URL_SCHEME);
|
||||
|
||||
// Scheme must be "http" or "https"
|
||||
if(preg_match('/http[s]{0,1}/', parse_url($this->threadurl, PHP_URL_SCHEME)) == false) {
|
||||
returnClientError('The URL you provided doesn\'t specify a valid scheme (http or https)!');
|
||||
}
|
||||
|
||||
// Path cannot be root (../)
|
||||
if(parse_url($this->threadurl, PHP_URL_PATH) === '/') {
|
||||
returnClientError('The URL you provided doesn\'t link to a valid thread (root path)!');
|
||||
}
|
||||
|
||||
// XenForo adds a thread ID to the URL, like "...-thread.454934283". It must be present
|
||||
if(preg_match('/.+\.\d+[\/]{0,1}/', parse_URL($this->threadurl, PHP_URL_PATH)) == false) {
|
||||
returnClientError('The URL you provided doesn\'t link to a valid thread (ID missing)!');
|
||||
}
|
||||
|
||||
// We want to start at the first page in the thread. XenForo uses "../page-n" syntax
|
||||
// to identify pages (except for the first page).
|
||||
// Notice: XenForo uses the concept of "sentinels" to find and replace parts in the
|
||||
// URL. Technically forum hosts can change the syntax!
|
||||
if(preg_match('/.+\/(page-\d+.*)$/', $this->threadurl, $matches) != false) {
|
||||
|
||||
// before: https://xenforo.com/community/threads/guide-to-suggestions.2285/page-5
|
||||
// after : https://xenforo.com/community/threads/guide-to-suggestions.2285/
|
||||
$this->threadurl = str_replace($matches[1], '', $this->threadurl);
|
||||
|
||||
}
|
||||
|
||||
$html = getSimpleHTMLDOMCached($this->threadurl)
|
||||
or returnServerError('Failed loading data from "' . $this->threadurl . '"!');
|
||||
|
||||
$html = defaultLinkTo($html, $this->threadurl);
|
||||
|
||||
// Notice: The DOM structure changes depending on the XenForo version used
|
||||
if($mainContent = $html->find('div.mainContent', 0)) {
|
||||
$this->version = self::XENFORO_VERSION_1;
|
||||
} elseif ($mainContent = $html->find('div[class="p-body"]', 0)) {
|
||||
$this->version = self::XENFORO_VERSION_2;
|
||||
} else {
|
||||
returnServerError('This forum is currently not supported!');
|
||||
}
|
||||
|
||||
switch($this->version) {
|
||||
case self::XENFORO_VERSION_1:
|
||||
|
||||
$titleBar = $mainContent->find('div.titleBar h1', 0)
|
||||
or returnServerError('Error finding title bar!');
|
||||
|
||||
$this->title = $titleBar->plaintext;
|
||||
|
||||
// Store items from current page (we'll use $this->items as LIFO buffer)
|
||||
$this->extractThreadPostsV1($html, $this->threadurl);
|
||||
$this->extractPagesV1($html);
|
||||
|
||||
break;
|
||||
|
||||
case self::XENFORO_VERSION_2:
|
||||
|
||||
$titleBar = $mainContent->find('div[class="p-title"] h1', 0)
|
||||
or returnServerError('Error finding title bar!');
|
||||
|
||||
$this->title = $titleBar->plaintext;
|
||||
$this->extractThreadPostsV2($html, $this->threadurl);
|
||||
$this->extractPagesV2($html);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
while(count($this->items) > $this->getInput('limit')) {
|
||||
array_shift($this->items);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts thread posts
|
||||
* @param $html A simplehtmldom object
|
||||
* @param $url The url from which $html was loaded
|
||||
*/
|
||||
private function extractThreadPostsV1($html, $url) {
|
||||
|
||||
$lang = $html->find('html', 0)->lang;
|
||||
|
||||
// Posts are contained in an "ol"
|
||||
$messageList = $html->find('#messageList li')
|
||||
or returnServerError('Error finding message list!');
|
||||
|
||||
foreach($messageList as $post) {
|
||||
|
||||
if(!isset($post->attr['id'])) { // Skip ads
|
||||
continue;
|
||||
}
|
||||
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = $url . '#' . $post->getAttribute('id');
|
||||
|
||||
$content = $post->find('.messageContent article', 0);
|
||||
|
||||
// Add some style to quotes
|
||||
foreach($content->find('.bbCodeQuote') as $quote) {
|
||||
$quote->style = '
|
||||
color: #495566;
|
||||
background-color: rgb(248,251,253);
|
||||
border: 1px solid rgb(111, 140, 180);
|
||||
border-color: rgb(111, 140, 180);
|
||||
font-style: italic;';
|
||||
}
|
||||
|
||||
// Remove script tags
|
||||
foreach($content->find('script') as $script) {
|
||||
$script->outertext = '';
|
||||
}
|
||||
|
||||
$item['content'] = $content->innertext;
|
||||
|
||||
// Remove quotes (for the title)
|
||||
foreach($content->find('.bbCodeQuote') as $quote) {
|
||||
$quote->innertext = '';
|
||||
}
|
||||
|
||||
$title = trim($content->plaintext);
|
||||
|
||||
if(strlen($title) > 70) {
|
||||
$item['title'] = substr($title, 0, strpos($title, ' ', 70)) . '...';
|
||||
} else {
|
||||
$item['title'] = $title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps are presented in two forms:
|
||||
*
|
||||
* 1) short version (for older posts?)
|
||||
* <span
|
||||
* class="DateTime"
|
||||
* title="22 Oct. 2018 at 23:47"
|
||||
* >22 Oct. 2018</span>
|
||||
*
|
||||
* This form has to be interpreted depending on the current language.
|
||||
*
|
||||
* 2) long version (for newer posts?)
|
||||
* <abbr
|
||||
* class="DateTime"
|
||||
* data-time="1541008785"
|
||||
* data-diff="310694"
|
||||
* data-datestring="31 Oct. 2018"
|
||||
* data-timestring="18:59"
|
||||
* title="31 Oct. 2018 at 18:59"
|
||||
* >Wednesday at 18:59</abbr>
|
||||
*
|
||||
* This form has the timestamp embedded (data-time)
|
||||
*/
|
||||
if($timestamp = $post->find('abbr.DateTime', 0)) { // long version (preffered)
|
||||
$item['timestamp'] = $timestamp->{'data-time'};
|
||||
} elseif($timestamp = $post->find('span.DateTime', 0)) { // short version
|
||||
$item['timestamp'] = $this->fixDate($timestamp->title, $lang);
|
||||
}
|
||||
|
||||
$item['author'] = $post->getAttribute('data-author');
|
||||
|
||||
// Bridge specific properties
|
||||
$item['id'] = $post->getAttribute('id');
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function extractThreadPostsV2($html, $url) {
|
||||
|
||||
$lang = $html->find('html', 0)->lang;
|
||||
|
||||
$messageList = $html->find('div[class="block-body"] article')
|
||||
or returnServerError('Error finding message list!');
|
||||
|
||||
foreach($messageList as $post) {
|
||||
|
||||
if(!isset($post->attr['id'])) { // Skip ads
|
||||
continue;
|
||||
}
|
||||
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = $url . '#' . $post->getAttribute('id');
|
||||
|
||||
$title = $post->find('div[class="message-content"] article', 0)->plaintext;
|
||||
$end = strpos($title, ' ', 70);
|
||||
$item['title'] = substr($title, 0, $end);
|
||||
|
||||
$item['timestamp'] = $this->fixDate($post->find('time', 0)->title, $lang);
|
||||
$item['author'] = $post->getAttribute('data-author');
|
||||
$item['content'] = $post->find('div[class="message-content"] article', 0);
|
||||
|
||||
// Bridge specific properties
|
||||
$item['id'] = $post->getAttribute('id');
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function extractPagesV1($html) {
|
||||
|
||||
// A navigation bar becomes available if the number of posts grows too
|
||||
// high. When this happens we need to load further pages (from last backwards)
|
||||
if(($pageNav = $html->find('div.PageNav', 0))) {
|
||||
|
||||
$lastpage = $pageNav->{'data-last'};
|
||||
$baseurl = $pageNav->{'data-baseurl'};
|
||||
$sentinel = $pageNav->{'data-sentinel'};
|
||||
|
||||
$hosturl = parse_url($this->threadurl, PHP_URL_SCHEME)
|
||||
. '://'
|
||||
. parse_url($this->threadurl, PHP_URL_HOST)
|
||||
. '/';
|
||||
|
||||
$page = $lastpage;
|
||||
|
||||
// Load at least the last page
|
||||
do {
|
||||
|
||||
$pageurl = $hosturl . str_replace($sentinel, $lastpage, $baseurl);
|
||||
|
||||
// We can optimize performance by caching all but the last page
|
||||
if($page != $lastpage) {
|
||||
$html = getSimpleHTMLDOMCached($pageurl)
|
||||
or returnServerError('Error loading contents from ' . $pageurl . '!');
|
||||
} else {
|
||||
$html = getSimpleHTMLDOM($pageurl)
|
||||
or returnServerError('Error loading contents from ' . $pageurl . '!');
|
||||
}
|
||||
|
||||
$html = defaultLinkTo($html, $hosturl);
|
||||
|
||||
$this->extractThreadPostsV1($html, $pageurl);
|
||||
|
||||
$page--;
|
||||
|
||||
} while (count($this->items) < $this->getInput('limit') && $page != 1);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function extractPagesV2($html) {
|
||||
|
||||
// A navigation bar becomes available if the number of posts grows too
|
||||
// high. When this happens we need to load further pages (from last backwards)
|
||||
if(($pageNav = $html->find('div.pageNav', 0))) {
|
||||
|
||||
foreach($pageNav->find('li') as $nav) {
|
||||
$lastpage = $nav->plaintext;
|
||||
}
|
||||
|
||||
// Manually extract baseurl and inject sentinel
|
||||
$baseurl = $pageNav->find('li a', -1)->href;
|
||||
$baseurl = str_replace('page-' . $lastpage, 'page-{{sentinel}}', $baseurl);
|
||||
|
||||
$sentinel = '{{sentinel}}';
|
||||
|
||||
$hosturl = parse_url($this->threadurl, PHP_URL_SCHEME)
|
||||
. '://'
|
||||
. parse_url($this->threadurl, PHP_URL_HOST);
|
||||
|
||||
$page = $lastpage;
|
||||
|
||||
// Load at least the last page
|
||||
do {
|
||||
|
||||
$pageurl = $hosturl . str_replace($sentinel, $lastpage, $baseurl);
|
||||
|
||||
// We can optimize performance by caching all but the last page
|
||||
if($page != $lastpage) {
|
||||
$html = getSimpleHTMLDOMCached($pageurl)
|
||||
or returnServerError('Error loading contents from ' . $pageurl . '!');
|
||||
} else {
|
||||
$html = getSimpleHTMLDOM($pageurl)
|
||||
or returnServerError('Error loading contents from ' . $pageurl . '!');
|
||||
}
|
||||
|
||||
$html = defaultLinkTo($html, $this->hosturl);
|
||||
|
||||
$this->extractThreadPostsV2($html, $this->pageurl);
|
||||
|
||||
$page--;
|
||||
|
||||
} while (count($this->items) < $this->getInput('limit') && $page != 1);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixes dates depending on the choosen language:
|
||||
*
|
||||
* de : dd.mm.yy
|
||||
* en : dd.mm.yy
|
||||
* it : dd/mm/yy
|
||||
*
|
||||
* Basically strtotime doesn't convert dates correctly due to formats
|
||||
* being hard to interpret. So we use the DateTime object.
|
||||
*
|
||||
* We don't know the timezone, so just assume +00:00 (or whatever
|
||||
* DateTime chooses)
|
||||
*/
|
||||
private function fixDate($date, $lang = 'en-US') {
|
||||
|
||||
$mnamesen = [
|
||||
'January',
|
||||
'Feburary',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December'
|
||||
];
|
||||
|
||||
switch($lang) {
|
||||
case 'en-US': // example: Jun 9, 2018 at 11:46 PM
|
||||
|
||||
$df = date_create_from_format('M d, Y \a\t H:i A', $date);
|
||||
break;
|
||||
|
||||
case 'de-DE': // example: 19 Juli 2018 um 19:27 Uhr
|
||||
|
||||
$mnamesde = [
|
||||
'Januar',
|
||||
'Februar',
|
||||
'März',
|
||||
'April',
|
||||
'Mai',
|
||||
'Juni',
|
||||
'Juli',
|
||||
'August',
|
||||
'September',
|
||||
'Oktober',
|
||||
'November',
|
||||
'Dezember'
|
||||
];
|
||||
|
||||
$mnamesdeshort = [
|
||||
'Jan.',
|
||||
'Feb.',
|
||||
'Mär.',
|
||||
'Apr.',
|
||||
'Mai',
|
||||
'Juni',
|
||||
'Juli',
|
||||
'Aug.',
|
||||
'Sep.',
|
||||
'Okt.',
|
||||
'Nov.',
|
||||
'Dez.'
|
||||
];
|
||||
|
||||
$date = str_ireplace($mnamesde, $mnamesen, $date);
|
||||
$date = str_ireplace($mnamesdeshort, $mnamesen, $date);
|
||||
|
||||
$df = date_create_from_format('d M Y \u\m H:i \U\h\r', $date);
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
// debugMessage(date_format($df, 'U'));
|
||||
|
||||
return date_format($df, 'U');
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -147,12 +147,19 @@ class YoutubeBridge extends BridgeAbstract {
|
||||
$time = 0;
|
||||
$vid = str_replace('/watch?v=', '', $element->find('a', 0)->href);
|
||||
$vid = substr($vid, 0, strpos($vid, '&') ?: strlen($vid));
|
||||
$title = $this->ytBridgeFixTitle($element->find($title_selector, 0)->plaintext);
|
||||
$title = trim($this->ytBridgeFixTitle($element->find($title_selector, 0)->plaintext));
|
||||
|
||||
if (strpos($vid, 'googleads') !== false
|
||||
|| $title == '[Private video]'
|
||||
|| $title == '[Deleted video]'
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// The duration comes in one of the formats:
|
||||
// hh:mm:ss / mm:ss / m:ss
|
||||
// 01:03:30 / 15:06 / 1:24
|
||||
$durationText = trim($element->find('span[class="video-time"]', 0)->plaintext);
|
||||
$durationText = trim($element->find('div.timestamp span', 0)->plaintext);
|
||||
$durationText = preg_replace('/([\d]{1,2})\:([\d]{2})/', '00:$1:$2', $durationText);
|
||||
|
||||
sscanf($durationText, '%d:%d:%d', $hours, $minutes, $seconds);
|
||||
@@ -162,7 +169,6 @@ class YoutubeBridge extends BridgeAbstract {
|
||||
continue;
|
||||
}
|
||||
|
||||
if($title != '[Private Video]' && strpos($vid, 'googleads') === false) {
|
||||
if ($add_parsed_items) {
|
||||
$this->ytBridgeQueryVideoInfo($vid, $author, $desc, $time);
|
||||
$this->ytBridgeAddItem($vid, $title, $author, $desc, $time);
|
||||
@@ -170,7 +176,6 @@ class YoutubeBridge extends BridgeAbstract {
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $count;
|
||||
}
|
||||
|
||||
@@ -181,7 +186,9 @@ class YoutubeBridge extends BridgeAbstract {
|
||||
|
||||
private function ytGetSimpleHTMLDOM($url){
|
||||
return getSimpleHTMLDOM($url,
|
||||
$header = array(),
|
||||
$header = array(
|
||||
'Accept-Language: en-US'
|
||||
),
|
||||
$opts = array(),
|
||||
$lowercase = true,
|
||||
$forceTagsClosed = true,
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
class ZoneTelechargementBridge extends BridgeAbstract {
|
||||
const NAME = 'Zone Telechargement';
|
||||
const URI = 'https://ww4.zone-telechargement1.org/';
|
||||
const URI = 'https://www.zone-telechargement1.org/';
|
||||
const DESCRIPTION = 'Suivi de série sur Zone Telechargement';
|
||||
const MAINTAINER = 'sysadminstory';
|
||||
const PARAMETERS = array(
|
||||
@@ -16,6 +16,10 @@ class ZoneTelechargementBridge extends BridgeAbstract {
|
||||
)
|
||||
);
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://ww7.zone-telechargement1.org/templates/Default/images/favicon.ico';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM(self::URI . $this->getInput('url'))
|
||||
or returnServerError('Could not request Zone Telechargement.');
|
||||
@@ -68,8 +72,7 @@ class ZoneTelechargementBridge extends BridgeAbstract {
|
||||
}
|
||||
}
|
||||
|
||||
private function findLinkHoster($element)
|
||||
{
|
||||
private function findLinkHoster($element) {
|
||||
// The hoster name is one level higher than the link tag : get the parent element
|
||||
$element = $element->parent();
|
||||
//echo "PARENT : $element \n";
|
||||
|
@@ -11,6 +11,12 @@
|
||||
; false = disabled (default)
|
||||
custom_timeout = false
|
||||
|
||||
[admin]
|
||||
; Advertise an email address where people can reach the administrator.
|
||||
; This address is displayed on the main page, visible to everyone!
|
||||
; "" = Disabled (default)
|
||||
email = ""
|
||||
|
||||
[proxy]
|
||||
|
||||
; Sets the proxy url (i.e. "tcp://192.168.0.0:32")
|
||||
|
12
index.php
12
index.php
@@ -202,6 +202,7 @@ try {
|
||||
'format',
|
||||
'_noproxy',
|
||||
'_cache_timeout',
|
||||
'_error_time'
|
||||
), '')
|
||||
);
|
||||
|
||||
@@ -214,6 +215,7 @@ try {
|
||||
'format',
|
||||
'_noproxy',
|
||||
'_cache_timeout',
|
||||
'_error_time'
|
||||
), '')
|
||||
);
|
||||
|
||||
@@ -262,6 +264,8 @@ try {
|
||||
'icon' => $bridge->getIcon()
|
||||
);
|
||||
} catch(Error $e) {
|
||||
error_log($e);
|
||||
|
||||
$item = array();
|
||||
|
||||
// Create "new" error message every 24 hours
|
||||
@@ -280,6 +284,8 @@ try {
|
||||
|
||||
$items[] = $item;
|
||||
} catch(Exception $e) {
|
||||
error_log($e);
|
||||
|
||||
$item = array();
|
||||
|
||||
// Create "new" error message every 24 hours
|
||||
@@ -306,13 +312,15 @@ try {
|
||||
$format = Format::create($format);
|
||||
$format->setItems($items);
|
||||
$format->setExtraInfos($infos);
|
||||
$format->setLastModified($mtime);
|
||||
$format->setLastModified($cache->getTime());
|
||||
$format->display();
|
||||
} catch(Error $e) {
|
||||
error_log($e);
|
||||
http_response_code($e->getCode());
|
||||
header('Content-Type: text/html');
|
||||
die(buildTransformException($e, $bridge));
|
||||
} catch(Exception $e) {
|
||||
error_log($e);
|
||||
http_response_code($e->getCode());
|
||||
header('Content-Type: text/html');
|
||||
die(buildTransformException($e, $bridge));
|
||||
@@ -321,9 +329,11 @@ try {
|
||||
echo BridgeList::create($whitelist_selection, $showInactive);
|
||||
}
|
||||
} catch(HttpException $e) {
|
||||
error_log($e);
|
||||
http_response_code($e->getCode());
|
||||
header('Content-Type: text/plain');
|
||||
die($e->getMessage());
|
||||
} catch(\Exception $e) {
|
||||
error_log($e);
|
||||
die($e->getMessage());
|
||||
}
|
||||
|
@@ -193,7 +193,7 @@ This bridge is not fetching its content through a secure connection</div>';
|
||||
. '" type="checkbox" name="'
|
||||
. $name
|
||||
. '" '
|
||||
. ($entry['defaultValue'] === 'checked' ?: '')
|
||||
. ($entry['defaultValue'] === 'checked' ? 'checked' : '')
|
||||
. ' />'
|
||||
. PHP_EOL;
|
||||
}
|
||||
|
@@ -96,6 +96,18 @@ EOD;
|
||||
private static function getFooter($totalBridges, $totalActiveBridges, $showInactive) {
|
||||
$version = Configuration::getVersion();
|
||||
|
||||
$email = Configuration::getConfig('admin', 'email');
|
||||
$admininfo = '';
|
||||
if (!empty($email)) {
|
||||
$admininfo = <<<EOD
|
||||
<br />
|
||||
<span>
|
||||
You may email the administrator of this RSS-Bridge instance
|
||||
at <a href="mailto:{$email}">{$email}</a>
|
||||
</span>
|
||||
EOD;
|
||||
}
|
||||
|
||||
$inactive = '';
|
||||
|
||||
if($totalActiveBridges !== $totalBridges) {
|
||||
@@ -114,6 +126,7 @@ EOD;
|
||||
<p class="version">{$version}</p>
|
||||
{$totalActiveBridges}/{$totalBridges} active bridges.<br>
|
||||
{$inactive}
|
||||
{$admininfo}
|
||||
</section>
|
||||
EOD;
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
class Configuration {
|
||||
|
||||
public static $VERSION = '2018-10-15';
|
||||
public static $VERSION = 'dev.2018-10-15';
|
||||
|
||||
public static $config = null;
|
||||
|
||||
@@ -107,7 +107,8 @@ class Configuration {
|
||||
|
||||
$headFile = '.git/HEAD';
|
||||
|
||||
if(file_exists($headFile)) {
|
||||
// '@' is used to mute open_basedir warning
|
||||
if(@is_readable($headFile)) {
|
||||
|
||||
$revisionHashFile = '.git/' . substr(file_get_contents($headFile), 5, -1);
|
||||
$branchName = explode('/', $revisionHashFile)[3];
|
||||
|
@@ -1,24 +1,37 @@
|
||||
<?php
|
||||
function getContents($url, $header = array(), $opts = array()){
|
||||
debugMessage('Reading contents from "' . $url . '"');
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||
|
||||
if(is_array($header) && count($header) !== 0)
|
||||
if(is_array($header) && count($header) !== 0) {
|
||||
|
||||
debugMessage('Setting headers: ' . json_encode($header));
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
|
||||
|
||||
}
|
||||
|
||||
curl_setopt($ch, CURLOPT_USERAGENT, ini_get('user_agent'));
|
||||
curl_setopt($ch, CURLOPT_ENCODING, '');
|
||||
curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
|
||||
|
||||
if(is_array($opts)) {
|
||||
if(is_array($opts) && count($opts) !== 0) {
|
||||
|
||||
debugMessage('Setting options: ' . json_encode($opts));
|
||||
|
||||
foreach($opts as $key => $value) {
|
||||
curl_setopt($ch, $key, $value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if(defined('PROXY_URL') && !defined('NOPROXY')) {
|
||||
|
||||
debugMessage('Setting proxy url: ' . PROXY_URL);
|
||||
curl_setopt($ch, CURLOPT_PROXY, PROXY_URL);
|
||||
|
||||
}
|
||||
|
||||
// We always want the response header as part of the data!
|
||||
@@ -34,6 +47,9 @@ function getContents($url, $header = array(), $opts = array()){
|
||||
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
|
||||
$errorCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$header = substr($data, 0, $headerSize);
|
||||
|
||||
debugMessage('Response header: ' . $header);
|
||||
|
||||
$headers = parseResponseHeader($header);
|
||||
$finalHeader = end($headers);
|
||||
|
||||
@@ -48,7 +64,7 @@ EOD
|
||||
}
|
||||
|
||||
returnError(<<<EOD
|
||||
The requested resouce cannot be found!
|
||||
The requested resource cannot be found!
|
||||
Please make sure your input parameters are correct!
|
||||
EOD
|
||||
, $errorCode);
|
||||
|
@@ -13,6 +13,13 @@
|
||||
<rule ref="Generic.CodeAnalysis.UnnecessaryFinalModifier"/>
|
||||
<!-- Do not override methods to call their parent -->
|
||||
<rule ref="Generic.CodeAnalysis.UselessOverridingMethod"/>
|
||||
<!-- Make sure the concatenation operator has spaces around it -->
|
||||
<rule ref="Squiz.Strings.ConcatenationSpacing">
|
||||
<properties>
|
||||
<property name="spacing" value="1"/>
|
||||
<property name="ignoreNewlines" value="true"/>
|
||||
</properties>
|
||||
</rule>
|
||||
<!-- One line should not have more than 80 characters -->
|
||||
<!-- One line must never exceed 120 characters -->
|
||||
<rule ref="Generic.Files.LineLength">
|
||||
|
9
vendor/php-urljoin/src/urljoin.php
vendored
9
vendor/php-urljoin/src/urljoin.php
vendored
@@ -26,6 +26,15 @@ function urljoin($base, $rel) {
|
||||
$pbase = parse_url($base);
|
||||
$prel = parse_url($rel);
|
||||
|
||||
if ($prel === false || preg_match('/^[a-z0-9\-.]*[^a-z0-9\-.:][a-z0-9\-.]*:/i', $rel)) {
|
||||
/*
|
||||
Either parse_url couldn't parse this, or the original URL
|
||||
fragment had an invalid scheme character before the first :,
|
||||
which can confuse parse_url
|
||||
*/
|
||||
$prel = array('path' => $rel);
|
||||
}
|
||||
|
||||
if (array_key_exists('path', $pbase) && $pbase['path'] === '/') {
|
||||
unset($pbase['path']);
|
||||
}
|
||||
|
Reference in New Issue
Block a user