jenkins-bot has submitted this change and it was merged.

Change subject: (bug 57162) red links don't work
......................................................................


(bug 57162) red links don't work

Bug: 57162
Change-Id: I78aea6256dd03d99ecb642283ba6e24a5958a273
---
M includes/Block/Topic.php
M includes/Model/PostRevision.php
M includes/ParsoidUtils.php
M includes/Templating.php
M templates/topic.html.php
M templates/topiclist.html.php
6 files changed, 224 insertions(+), 7 deletions(-)

Approvals:
  EBernhardson: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/includes/Block/Topic.php b/includes/Block/Topic.php
index cea9bef..82cc8a0 100644
--- a/includes/Block/Topic.php
+++ b/includes/Block/Topic.php
@@ -656,7 +656,7 @@
                );
        }
 
-       protected function loadRootPost() {
+       public function loadRootPost() {
                if ( $this->root !== null ) {
                        return $this->root;
                }
diff --git a/includes/Model/PostRevision.php b/includes/Model/PostRevision.php
index 63585eb..a5b0680 100644
--- a/includes/Model/PostRevision.php
+++ b/includes/Model/PostRevision.php
@@ -295,7 +295,6 @@
                        }
                }
 
-
                return array( $callbacks, $results );
        }
 
@@ -335,7 +334,7 @@
                 * @param int $result
                 * @return array Return array in the format of [result, 
continue]
                 */
-               $callback = function( $post, $result ) {
+               $callback = function( PostRevision $post, $result ) {
                        $creator = $post->getCreator();
                        if ( $creator instanceof User ) {
                                $result[$post->getCreatorName()] = $creator;
@@ -366,7 +365,7 @@
                 * @param int $result
                 * @return array Return array in the format of [result, 
continue]
                 */
-               $callback = function( $post, $result ) use ( &$postId ) {
+               $callback = function( PostRevision $post, $result ) use ( 
&$postId ) {
                        if ( $post->getPostId()->equals( $postId ) ) {
                                return array( $post, false );
                        }
diff --git a/includes/ParsoidUtils.php b/includes/ParsoidUtils.php
index 13b0833..d11c7bc 100644
--- a/includes/ParsoidUtils.php
+++ b/includes/ParsoidUtils.php
@@ -74,8 +74,7 @@
                         */
                        $response = mb_convert_encoding( $response, 
'HTML-ENTITIES', 'UTF-8' );
 
-                       $dom = new \DOMDocument();
-                       $dom->loadHTML( $response );
+                       $dom = self::createDOM( $response );
                        $body = $dom->getElementsByTagName( 'body' )->item(0);
 
                        $response = '';
@@ -135,6 +134,57 @@
                        $wgFlowParsoidTimeout ? $wgFlowParsoidTimeout : 
$wgVisualEditorParsoidTimeout,
                );
        }
+
+       /**
+        * Turns given $content string into a DOMDocument object.
+        *
+        * Some errors are forgivable (e.g. 801: there may be "invalid" HTML 
tags,
+        * like figcaption), so these can be ignored. Other errors will cause a
+        * warning.
+        *
+        * @param string $content
+        * @param array[optional] $ignoreErrorCodes
+        * @return \DOMDocument
+        * @throws \MWException
+        * @see http://www.xmlsoft.org/html/libxml-xmlerror.html
+        */
+       public static function createDOM( $content, $ignoreErrorCodes = array( 
801 ) ) {
+               /*
+                * Workaround because DOMDocument can't guess charset.
+                * Content should be utf-8. Alternative "workarounds" would be 
to
+                * provide the charset in $response, as either:
+                * * <?xml encoding="utf-8" ?>
+                * * <meta http-equiv="Content-Type" content="text/html; 
charset=utf-8">
+                */
+               $content = mb_convert_encoding( $content, 'HTML-ENTITIES', 
'UTF-8' );
+
+               $dom = new \DOMDocument();
+
+               // don't output warnings
+               $useErrors = libxml_use_internal_errors( true );
+
+               $dom->loadHTML( $content );
+
+               // check error codes; if not in the supplied list of ignorable 
errors,
+               // throw an exception
+               $errors = array_filter(
+                       libxml_get_errors(),
+                       function( $error ) use( $ignoreErrorCodes ) {
+                               return !in_array( $error->code, 
$ignoreErrorCodes );
+                       }
+               );
+               if ( $errors ) {
+                       throw new \MWException(
+                               implode( "\n", array_map( $errors, function( 
$error ) { return $error->message; } ) )
+                       );
+               }
+
+               // restore libxml error reporting
+               libxml_clear_errors();
+               libxml_use_internal_errors( $useErrors );
+
+               return $dom;
+       }
 }
 
 class NoParsoidException extends \MWException {}
diff --git a/includes/Templating.php b/includes/Templating.php
index 91ccdfc..50e93e9 100644
--- a/includes/Templating.php
+++ b/includes/Templating.php
@@ -24,6 +24,24 @@
        protected $namespaces;
        protected $globals;
 
+       /**
+        * @var array Array of PostRevision::registerRecursive return values
+        * @see Templating::registerParsoidLinks
+        */
+       public $parsoidLinksIdentifiers = array();
+
+       /**
+        * @var array Array of processed post Ids
+        * @see Templating::registerParsoidLinks
+        */
+       public $parsoidLinksProcessed = array();
+
+       /**
+        * @var array Array of Title objects
+        * @see Templating::registerParsoidLinks
+        */
+       public $parsoidLinks = array();
+
        public function __construct( UrlGenerator $urlGenerator, OutputPage 
$output, array $namespaces = array(), array $globals = array() ) {
                $this->urlGenerator = $urlGenerator;
                $this->output = $output;
@@ -308,7 +326,146 @@
                if ( !$revision->isAllowed( $permissionsUser ) && 
$message->exists() ) {
                        return $message->text();
                } else {
-                       return $revision->getContent( $format );
+                       $content = $revision->getContent( $format );
+
+                       if ( $format === 'html' ) {
+                               // Parsoid doesn't render redlinks
+                               $content = $this->applyRedlinks( $content );
+                       }
+
+                       return $content;
                }
        }
+
+       /**
+        * Parsoid ignores red links. With good reason: redlinks should only be
+        * applied when rendering the content, not when it's created.
+        *
+        * This method will parse a given content, fetch all of its links & let 
MW's
+        * Linker class build the link HTML (which will take redlinks into 
account.)
+        * It will then substitute original link HTML for the one Linker 
generated.
+        *
+        * @param string $content
+        * @return string
+        */
+       protected function applyRedlinks( $content ) {
+               /*
+                * In order to efficiently replace redlinks, multiple recursive
+                * functions (may) have been registered that fetch an array of 
linked-to
+                * titles. Since we'll now need to apply redlinks, it's time to 
execute
+                * all these callbacks & batch-load all of the titles they come 
up with.
+                */
+               foreach ( $this->parsoidLinksIdentifiers as $identifier => 
$post ) {
+                       $post->getRecursiveResult( $identifier );
+                       unset( $this->parsoidLinksIdentifiers[$identifier] );
+               }
+               if ( $this->parsoidLinks ) {
+                       $batch = new \LinkBatch( $this->parsoidLinks );
+                       $batch->execute();
+                       $this->parsoidLinks = array();
+               }
+
+               /*
+                * Workaround because DOMDocument can't guess charset.
+                * Content should be utf-8. Alternative "workarounds" would be 
to
+                * provide the charset in $response, as either:
+                * * <?xml encoding="utf-8" ?>
+                * * <meta http-equiv="Content-Type" content="text/html; 
charset=utf-8">
+                */
+               $content = mb_convert_encoding( $content, 'HTML-ENTITIES', 
'UTF-8' );
+
+               $dom = ParsoidUtils::createDOM( $content );
+
+               // find links in DOM
+               $xpath = new \DOMXPath( $dom );
+               $linkNodes = $xpath->query( 
'//a[@rel="mw:WikiLink"][@data-parsoid]' );
+               foreach ( $linkNodes as $linkNode ) {
+                       $parsoid = $linkNode->getAttribute( 'data-parsoid' );
+                       $parsoid = json_decode( $parsoid, true );
+
+                       if ( isset( $parsoid['sa']['href'] ) ) {
+                               // gather existing link attributes
+                               $attributes = array();
+                               foreach ( $linkNode->attributes as $attribute ) 
{
+                                       $attributes[$attribute->name] = 
$attribute->value;
+                               }
+
+                               // let MW build link HTML based on Parsoid data
+                               $title = Title::newFromText( 
$parsoid['sa']['href'] );
+                               $linkHTML = Linker::link( $title, 
$linkNode->nodeValue, $attributes );
+
+                               // create new DOM from this MW-built link
+                               $linkDom = ParsoidUtils::createDOM( $linkHTML );
+
+                               // import MW-built link node into content DOM
+                               $replacementNode = 
$linkDom->getElementsByTagName( 'a' )->item( 0 );
+                               $replacementNode = $dom->importNode( 
$replacementNode, true );
+
+                               // replace Parsoid link with MW-built link
+                               $linkNode->parentNode->replaceChild( 
$replacementNode, $linkNode );
+                       }
+               }
+
+               return $dom->saveHTML();
+       }
+
+       /**
+        * Registers callback function to find content links in Parsoid html.
+        * The goal is to batch-load and add to LinkCache as much links as 
possible.
+        */
+       public function registerParsoidLinks( PostRevision $post ) {
+               /*
+                * This can be registered on multiple posts (e.g. multiple 
topics) to
+                * batch-load as much as possible; all of the identifiers have 
to be
+                * saved and will be processed as soon as they first are needed.
+                */
+               $identifier = $post->registerRecursive( array( $this, 
'registerParsoidLinksCallback' ), array(), 'parsoidlinks' );
+               $this->parsoidLinksIdentifiers[$identifier] = $post;
+       }
+
+       /**
+        * DON'T CALL THIS METHOD!
+        * This is for internal use only: it's a callback function to
+        * PostRevision::registerRecursive, which can be registered via
+        * Templating::registerParsoidLinks.
+        *
+        * Returns an array of linked pages in Parsoid.
+        *
+        * @param PostRevision $post
+        * @param array $result
+        * @return array Return array in the format of [result, continue]
+        */
+       public function registerParsoidLinksCallback( PostRevision $post, 
$result ) {
+               $content = $post->getContent( 'html' );
+
+               // make sure a post is not checked more than once
+               $revisionId = $post->getRevisionId()->getHex();
+               if ( isset( $this->parsoidLinksProcessed[$revisionId] ) ) {
+                       return array( array(), false );
+               }
+               $this->parsoidLinksProcessed[$revisionId] = true;
+
+               // find links in DOM
+               $dom = ParsoidUtils::createDOM( $content );
+               $xpath = new \DOMXPath( $dom );
+               $linkNodes = $xpath->query( 
'//a[@rel="mw:WikiLink"][@data-parsoid]' );
+
+               foreach ( $linkNodes as $linkNode ) {
+                       $parsoid = $linkNode->getAttribute( 'data-parsoid' );
+                       $parsoid = json_decode( $parsoid, true );
+
+                       if ( isset( $parsoid['sa']['href'] ) ) {
+                               // real results will be stored in 
Templating::parsoidLinks
+                               $link = $parsoid['sa']['href'];
+                               $this->parsoidLinks[$link] = 
Title::newFromText( $link );
+                       }
+               }
+
+               /*
+                * $result will not be used; we'll register this callback 
multiple
+                * times and will want to gather overlapping results, so they'll
+                * be stored at Templating::parsoidLinks
+                */
+               return array( array(), true );
+       }
 }
diff --git a/templates/topic.html.php b/templates/topic.html.php
index 4414cbf..56cd75d 100644
--- a/templates/topic.html.php
+++ b/templates/topic.html.php
@@ -7,6 +7,7 @@
 // first one's result is requested
 $indexDescendantCount = $root->registerDescendantCount();
 $indexParticipants = $root->registerParticipants();
+$this->registerParsoidLinks( $root );
 
 // topic reply box.
 $topicReplyBox = '';
diff --git a/templates/topiclist.html.php b/templates/topiclist.html.php
index 4b704c8..6f918a7 100644
--- a/templates/topiclist.html.php
+++ b/templates/topiclist.html.php
@@ -46,6 +46,16 @@
        echo $this->getPagingLink( $block, 'rev', $linkData['offset'], 
$linkData['limit'] );
 }
 
+/*
+ * We'll want to register the Parsoidlinks callback as soon as possible, so 
that
+ * once the first topic is being rendered, the links for the last topic have
+ * already been pre-loaded as well.
+ */
+foreach ( $topics as $topic ) {
+       $root = $topic->loadRootPost();
+       $this->registerParsoidLinks( $root );
+}
+
 foreach ( $topics as $topic ) {
        echo $topic->render( $this,
                array(

-- 
To view, visit https://gerrit.wikimedia.org/r/96494
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I78aea6256dd03d99ecb642283ba6e24a5958a273
Gerrit-PatchSet: 6
Gerrit-Project: mediawiki/extensions/Flow
Gerrit-Branch: master
Gerrit-Owner: Matthias Mullie <[email protected]>
Gerrit-Reviewer: EBernhardson <[email protected]>
Gerrit-Reviewer: Matthias Mullie <[email protected]>
Gerrit-Reviewer: Werdna <[email protected]>
Gerrit-Reviewer: jenkins-bot

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to