123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327 |
- <?php
- class MoinMoinBridge extends BridgeAbstract {
- const MAINTAINER = 'logmanoriginal';
- const NAME = 'MoinMoin Bridge';
- const URI = 'https://moinmo.in';
- const DESCRIPTION = 'Generates feeds for pages of a MoinMoin (compatible) wiki';
- const PARAMETERS = array(
- array(
- 'source' => array(
- 'name' => 'Source',
- 'type' => 'text',
- 'required' => true,
- 'title' => 'Insert wiki page URI (e.g.: https://moinmo.in/MoinMoin)',
- 'exampleValue' => 'https://moinmo.in/MoinMoin'
- ),
- 'separator' => array(
- 'name' => 'Separator',
- 'type' => 'list',
- 'requied' => true,
- 'title' => 'Defines the separtor for splitting content into feeds',
- 'defaultValue' => 'h2',
- 'values' => array(
- 'Header (h1)' => 'h1',
- 'Header (h2)' => 'h2',
- 'Header (h3)' => 'h3',
- 'List element (li)' => 'li',
- 'Anchor (a)' => 'a'
- )
- ),
- 'limit' => array(
- 'name' => 'Limit',
- 'type' => 'number',
- 'required' => false,
- 'title' => 'Number of items to return (from top)',
- 'defaultValue' => -1
- ),
- 'content' => array(
- 'name' => 'Content',
- 'type' => 'list',
- 'required' => false,
- 'title' => 'Defines how feed contents are build',
- 'defaultValue' => 'separator',
- 'values' => array(
- 'By separator' => 'separator',
- 'Follow link (only for anchor)' => 'follow',
- 'None' => 'none'
- )
- )
- )
- );
- private $title = '';
- public function collectData(){
- /* MoinMoin uses a rather unpleasent representation of HTML. Instead of
- * using tags like <article/>, <navigation/>, <header/>, etc... it uses
- * <div/>, <span/> and <p/>. Also each line is literaly identified via
- * IDs. The only way to distinguish content is via headers, though not
- * in all cases.
- *
- * Example (indented for the sake of readability):
- * ...
- * <span class="anchor" id="line-1"></span>
- * <span class="anchor" id="line-2"></span>
- * <span class="anchor" id="line-3"></span>
- * <span class="anchor" id="line-4"></span>
- * <span class="anchor" id="line-5"></span>
- * <span class="anchor" id="line-6"></span>
- * <span class="anchor" id="line-7"></span>
- * <span class="anchor" id="line-8"></span>
- * <span class="anchor" id="line-9"></span>
- * <p class="line867">MoinMoin is a Wiki software implemented in
- * <a class="interwiki" href="/Python" title="MoinMoin">Python</a>
- * and distributed as Free Software under
- * <a class="interwiki" href="/GPL" title="MoinMoin">GNU GPL license</a>.
- * ...
- */
- $html = getSimpleHTMLDOM($this->getInput('source'))
- or returnServerError('Could not load ' . $this->getInput('source'));
- // Some anchors link to local sites or local IDs (both don't work well
- // in feeds)
- $html = $this->fixAnchors($html);
- $this->title = $html->find('title', 0)->innertext . ' | ' . self::NAME;
- // Here we focus on simple author and timestamp information from the given
- // page. Later we update this information in case the anchor is followed.
- $author = $this->findAuthor($html);
- $timestamp = $this->findTimestamp($html);
- $sections = $this->splitSections($html);
- foreach($sections as $section) {
- $item = array();
- $item['uri'] = $this->findSectionAnchor($section[0]);
- switch($this->getInput('content')) {
- case 'none': // Do not return any content
- break;
- case 'follow': // Follow the anchor
- // We can only follow anchors (use default otherwise)
- if($this->getInput('separator') === 'a') {
- $content = $this->followAnchor($item['uri']);
- // Return only actual content
- $item['content'] = $content->find('div#page', 0)->innertext;
- // Each page could have its own author and timestamp
- $author = $this->findAuthor($content);
- $timestamp = $this->findTimestamp($content);
- break;
- }
- case 'separator':
- default: // Use contents from the current page
- $item['content'] = $this->cleanArticle($section[2]);
- }
- if(!is_null($author)) $item['author'] = $author;
- if(!is_null($timestamp)) $item['timestamp'] = $timestamp;
- $item['title'] = strip_tags($section[1]);
- // Skip items with empty title
- if(empty(trim($item['title']))) {
- continue;
- }
- $this->items[] = $item;
- if($this->getInput('limit') > 0
- && count($this->items) >= $this->getInput('limit')) {
- break;
- }
- }
- }
- public function getName(){
- return $this->title ?: parent::getName();
- }
- public function getURI(){
- return $this->getInput('source') ?: parent::getURI();
- }
- /**
- * Splits the html into sections.
- *
- * Returns an array with one element per section. Each element consists of:
- * [0] The entire section
- * [1] The section title
- * [2] The section content
- */
- private function splitSections($html){
- $content = $html->find('div#page', 0)->innertext
- or returnServerError('Unable to find <div id="page"/>!');
- $sections = array();
- $regex = implode(
- '',
- array(
- "\<{$this->getInput('separator')}.+?(?=\>)\>",
- "(.+?)(?=\<\/{$this->getInput('separator')}\>)",
- "\<\/{$this->getInput('separator')}\>",
- "(.+?)((?=\<{$this->getInput('separator')})|(?=\<div\sid=\"pagebottom\")){1}"
- )
- );
- preg_match_all(
- '/' . $regex . '/m',
- $content,
- $sections,
- PREG_SET_ORDER
- );
- // Some pages don't use headers, return page as one feed
- if(count($sections) === 0) {
- return array(
- array(
- $content,
- $html->find('title', 0)->innertext,
- $content
- )
- );
- }
- return $sections;
- }
- /**
- * Returns the anchor for a given section
- */
- private function findSectionAnchor($section){
- $html = str_get_html($section);
- // For IDs
- $anchor = $html->find($this->getInput('separator') . '[id=]', 0);
- if(!is_null($anchor)) {
- return $this->getInput('source') . '#' . $anchor->id;
- }
- // For actual anchors
- $anchor = $html->find($this->getInput('separator') . '[href=]', 0);
- if(!is_null($anchor)) {
- return $anchor->href;
- }
- // Nothing found
- return $this->getInput('source');
- }
- /**
- * Returns the author
- *
- * Notice: Some pages don't provide author information
- */
- private function findAuthor($html){
- /* Example:
- * <p id="pageinfo" class="info" dir="ltr" lang="en">MoinMoin: LocalSpellingWords
- * (last edited 2017-02-16 15:36:31 by <span title="??? @ hosted-by.leaseweb.com
- * [178.162.199.143]">hosted-by</span>)</p>
- */
- $pageinfo = $html->find('[id="pageinfo"]', 0);
- if(is_null($pageinfo)) {
- return null;
- } else {
- $author = $pageinfo->find('[title=]', 0);
- if(is_null($author)) {
- return null;
- } else {
- return trim(explode('@', $author->title)[0]);
- }
- }
- }
- /**
- * Returns the time of last edit
- *
- * Notice: Some pages don't provide this information
- */
- private function findTimestamp($html){
- // See example of findAuthor()
- $pageinfo = $html->find('[id="pageinfo"]', 0);
- if(is_null($pageinfo)) {
- return null;
- } else {
- $timestamp = $pageinfo->innertext;
- $matches = array();
- preg_match('/.+?(?=\().+?(?=\d)([0-9\-\s\:]+)/m', $pageinfo, $matches);
- return strtotime($matches[1]);
- }
- }
- /**
- * Returns the original HTML with all anchors fixed (makes relative anchors
- * absolute)
- */
- private function fixAnchors($html, $source = null){
- $source = $source ?: $this->getURI();
- foreach($html->find('a') as $anchor) {
- switch(substr($anchor->href, 0, 1)) {
- case 'h': // http or https, no actions required
- break;
- case '/': // some relative path
- $anchor->href = $this->findDomain($source) . $anchor->href;
- break;
- case '#': // it's an ID
- default: // probably something like ? or &, skip empty ones
- if(!isset($anchor->href))
- break;
- $anchor->href = $source . $anchor->href;
- }
- }
- return $html;
- }
- /**
- * Loads the full article of a given anchor (if the anchor is from the same
- * wiki domain)
- */
- private function followAnchor($anchor){
- if(strrpos($anchor, $this->findDomain($this->getInput('source')) === false)) {
- return null;
- }
- $html = getSimpleHTMLDOMCached($anchor);
- if(!$html) { // Cannot load article
- return null;
- }
- return $this->fixAnchors($html, $anchor);
- }
- /**
- * Finds the domain for a given URI
- */
- private function findDomain($uri){
- $matches = array();
- preg_match('/(http[s]{0,1}:\/\/.+?(?=\/))/', $uri, $matches);
- return $matches[1];
- }
- /* This function is a copy from CNETBridge */
- private function stripWithDelimiters($string, $start, $end){
- while(strpos($string, $start) !== false) {
- $section_to_remove = substr($string, strpos($string, $start));
- $section_to_remove = substr($section_to_remove, 0, strpos($section_to_remove, $end) + strlen($end));
- $string = str_replace($section_to_remove, '', $string);
- }
- return $string;
- }
- /* This function is based on CNETBridge */
- private function cleanArticle($article_html){
- $article_html = $this->stripWithDelimiters($article_html, '<script', '</script>');
- return $article_html;
- }
- }
|