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