Merge "Add hook for custom difference engine (WikEdDiff)"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Mon, 23 Feb 2015 12:32:10 +0000 (12:32 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Mon, 23 Feb 2015 12:32:10 +0000 (12:32 +0000)
1  2 
docs/hooks.txt
includes/content/ContentHandler.php
includes/diff/DifferenceEngine.php

diff --combined docs/hooks.txt
@@@ -66,7 -66,7 +66,7 @@@ email notification when an article is s
                # code to actually show the article goes here
  
                if ($wgNotifyArticle) {
 -                      wfNotifyArticleShow($article));
 +                      wfNotifyArticleShow($article);
                }
        }
  
@@@ -314,7 -314,7 +314,7 @@@ $output: The OutputPage object where ou
  
  'AfterImportPage': When a page import is completed.
  $title: Title under which the revisions were imported
 -$origTitle: Title provided by the XML file
 +$foreignTitle: ForeignTitle object based on data provided by the XML file
  $revCount: Number of revisions in the XML file
  $sRevCount: Number of successfully imported revisions
  $pageInfo: associative array of page information
@@@ -372,17 -372,6 +372,17 @@@ $editPage : the EditPage objec
  $text : the new text of the article (has yet to be saved)
  &$resultArr : data in this array will be added to the API result
  
 +'ApiFeedContributions::feedItem': Called to convert the result of ContribsPager
 +into a FeedItem instance that ApiFeedContributions can consume. Implementors of
 +this hook may cancel the hook to signal that the item is not viewable in the
 +provided context.
 +$row: A row of data from ContribsPager.  The set of data returned by ContribsPager
 + can be adjusted by handling the ContribsPager::reallyDoQuery hook.
 +$context: An IContextSource implementation.
 +&$feedItem: Set this to a FeedItem instance if the callback can handle the provided
 + row. This is provided to the hook as a null, if it is non null then another callback
 + has already handled the hook.
 +
  'ApiFormatHighlight': Use to syntax-highlight API pretty-printed output. When
  highlighting, add output to $context->getOutput() and return false.
  $context: An IContextSource.
@@@ -418,19 -407,6 +418,19 @@@ $module: ApiBase Module objec
  &$help: Array of HTML strings to be joined for the output.
  $options: Array Options passed to ApiHelp::getHelp
  
 +'ApiOpenSearchSuggest': Called when constructing the OpenSearch results. Hooks
 +can alter or append to the array.
 +&$results: array with integer keys to associative arrays. Keys in associative
 +array:
 +  - title: Title object.
 +  - redirect from: Title or null.
 +  - extract: Description for this result.
 +  - extract trimmed: If truthy, the extract will not be trimmed to
 +    $wgOpenSearchDescriptionLength.
 +  - image: Thumbnail for this result. Value is an array with subkeys 'source'
 +    (url), 'width', 'height', 'alt', 'align'.
 +  - url: Url for the given title.
 +
  'APIQueryAfterExecute': After calling the execute() method of an
  action=query submodule. Use this to extend core API modules.
  &$module: Module object
@@@ -788,10 -764,12 +788,10 @@@ $out: OutputPage objec
  'BeforeParserFetchFileAndTitle': Before an image is rendered by Parser.
  $parser: Parser object
  $nt: the image title
 -&$options: array of options to RepoGroup::findFile
 +&$options: array of options to RepoGroup::findFile. If it contains 'broken'
 +  as a key then the file will appear as a broken thumbnail.
  &$descQuery: query string to add to thumbnail URL
  
 -FIXME: Where does the below sentence fit in?
 -If 'broken' is a key in $options then the file will appear as a broken thumbnail.
 -
  'BeforeParserFetchTemplateAndtitle': Before a template is fetched by Parser.
  $parser: Parser object
  $title: title of the template
@@@ -870,20 -848,6 +870,20 @@@ $wikiPage: WikiPage that was remove
  'CategoryPageView': Before viewing a categorypage in CategoryPage::view.
  $catpage: CategoryPage instance
  
 +'CategoryViewer::doCategoryQuery': After querying for pages to be displayed
 +in a Category page. Gives extensions the opportunity to batch load any
 +related data about the pages.
 +$type: The category type. Either 'page', 'file' or 'subcat'
 +$res: Query result from DatabaseBase::select()
 +
 +'CategoryViewer::generateLink': Before generating an output link allow
 +extensions opportunity to generate a more specific or relevant link.
 +$type: The category type. Either 'page', 'img' or 'subcat'
 +$title: Title object for the categorized page
 +$html: Requested html content of anchor
 +&$link: Returned value. When set to a non-null value by a hook subscriber
 +this value will be used as the anchor instead of Linker::link
 +
  'ChangePasswordForm': For extensions that need to add a field to the
  ChangePassword form via the Preferences form.
  &$extraFields: An array of arrays that hold fields like would be passed to the
@@@ -917,38 -881,6 +917,38 @@@ $name: name of the special page, e.g. '
  &$join_conds: join conditions for the tables
  $opts: FormOptions for this request
  
 +'ChangeTagAfterDelete': Called after a change tag has been deleted (that is,
 +removed from all revisions and log entries to which it was applied). This gives
 +extensions a chance to take it off their books.
 +$tag: name of the tag
 +&$status: Status object. Add warnings to this as required. There is no point
 +  setting errors, as the deletion has already been partly carried out by this
 +  point.
 +
 +'ChangeTagCanCreate': Tell whether a change tag should be able to be created
 +from the UI (Special:Tags) or via the API. You could use this hook if you want
 +to reserve a specific "namespace" of tags, or something similar.
 +$tag: name of the tag
 +$user: user initiating the action
 +&$status: Status object. Add your errors using `$status->fatal()` or warnings
 +  using `$status->warning()`. Errors and warnings will be relayed to the user.
 +  If you set an error, the user will be unable to create the tag.
 +
 +'ChangeTagCanDelete': Tell whether a change tag should be able to be
 +deleted from the UI (Special:Tags) or via the API. The default is that tags
 +defined using the ListDefinedTags hook are not allowed to be deleted unless
 +specifically allowed. If you wish to allow deletion of the tag, set
 +`$status = Status::newGood()` to allow deletion, and then `return false` from
 +the hook function. Ensure you consume the 'ChangeTagAfterDelete' hook to carry
 +out custom deletion actions.
 +$tag: name of the tag
 +$user: user initiating the action
 +&$status: Status object. See above.
 +
 +'ChangeTagsListActive': Allows you to nominate which of the tags your extension
 +uses are in active use.
 +&$tags: list of all active tags. Append to this array.
 +
  'LoginUserMigrated': Called during login to allow extensions the opportunity to
  inform a user that their username doesn't exist for a specific reason, instead
  of letting the login form give the generic error message that the account does
@@@ -1057,6 -989,15 +1057,15 @@@ $title: the diff page title (nullable
  $old: the ?old= param value from the url
  $new: the ?new= param value from the url
  
+ 'GetDifferenceEngine': Called when getting a new difference engine interface object
+ Return false for valid object in $differenceEngine or true for the default difference engine
+ $context: IContextSource context to be used for diff
+ $old: Revision ID to show and diff with
+ $new: Either a revision ID or one of the strings 'cur', 'prev' or 'next'
+ $refreshCache: If set, refreshes the diff cache
+ $unhide: If set, allow viewing deleted revs
+ &$differenceEngine: output parameter, difference engine object to be used for diff
  'DiffRevisionTools': Override or extend the revision tools available from the
  diff view, i.e. undo, etc.
  $newRev: Revision object of the "new" revision
@@@ -1074,7 -1015,6 +1083,7 @@@ $article: article (object) being viewe
  $oldid: oldid (int) being viewed
  
  'DoEditSectionLink': Override the HTML generated for section edit links
 +* Deprecated in favour of SkinEditSectionLinks hook *
  $skin: Skin object rendering the UI
  $title: Title object for the title being linked to (may not be the same as
    the page title, if the section is included from a template)
@@@ -1105,8 -1045,7 +1114,8 @@@ This may be triggered by the EditPage o
  Use the $status object to indicate whether the edit should be allowed, and to provide
  a reason for disallowing it. Return false to abort the edit, and true to continue.
  Returning true if $status->isOK() returns false means "don't save but continue user
 -interaction", e.g. show the edit form.
 +interaction", e.g. show the edit form. $status->apiHookResult can be set to an array
 +to be returned by api.php action=edit. This is used to deliver captchas.
  $context: object implementing the IContextSource interface.
  $content: content of the edit box, as a Content object.
  $status: Status object to represent errors, etc.
@@@ -1311,15 -1250,15 +1320,15 @@@ $reason: reaso
  'FormatAutocomments': When an autocomment is formatted by the Linker.
   &$comment: Reference to the accumulated comment. Initially null, when set the
     default code will be skipped.
 - $pre: Initial part of the parsed comment before the call to the hook.
 + $pre: Boolean, true if there is text before this autocomment
   $auto: The extracted part of the parsed comment before the call to the hook.
 - $post: The final part of the parsed comment before the call to the hook.
 + $post: Boolean, true if there is text after this autocomment
   $title: An optional title object used to links to sections. Can be null.
   $local: Boolean indicating whether section links should refer to local page.
  
  'GalleryGetModes': Get list of classes that can render different modes of a
   gallery
 -$modeArray: An associative array mapping mode names to classes that implement
 +&$modeArray: An associative array mapping mode names to classes that implement
   that mode. It is expected all registered classes are a subclass of
   ImageGalleryBase.
  
@@@ -1519,7 -1458,7 +1528,7 @@@ $page: ImagePage objec
  'ImgAuthBeforeStream': executed before file is streamed to user, but only when
  using img_auth.php.
  &$title: the Title object of the file as it would appear for the upload page
 -&$path: the original file and path name when img_auth was invoked by the the web
 +&$path: the original file and path name when img_auth was invoked by the web
    server
  &$name: the name only component of the file
  &$result: The location to pass back results of the hook routine (only used if
@@@ -2224,7 -2163,6 +2233,7 @@@ $ns : array of int namespace keys to se
  $search : search term (not guaranteed to be conveniently normalized)
  $limit : maximum number of results to return
  &$results : out param: array of page names (strings)
 +$offset : number of results to offset from the beginning
  
  'PrefixSearchExtractNamespace': Called if core was not able to extract a
  namespace from the search string so that extensions can attempt it.
@@@ -2292,10 -2230,6 +2301,10 @@@ configuration variables to JavaScript. 
  or request state must be added through MakeGlobalVariablesScript instead.
  &$vars: array( variable name => value )
  
 +'ResourceLoaderGetLessVars': Called in ResourceLoader::getLessVars after variables
 +from $wgResourceLoaderLESSVars are added. Can be used to add context-based variables.
 +&$lessVars: array of variables already added
 +
  'ResourceLoaderRegisterModules': Right before modules information is required,
  such as when responding to a resource
  loader request or generating HTML output.
@@@ -2438,23 -2372,6 +2447,23 @@@ $type: 'normal' or 'history' for old/di
  &$forContent: overridable flag if copyright footer is shown in content language.
    This parameter is deprecated.
  
 +'SkinEditSectionLinks': Modify the section edit links
 +$skin: Skin object rendering the UI
 +$title: Title object for the title being linked to (may not be the same as
 +  the page title, if the section is included from a template)
 +$section: The designation of the section being pointed to, to be included in
 +  the link, like "&section=$section"
 +$tooltip: The default tooltip.  Escape before using.
 +  By default, this is wrapped in the 'editsectionhint' message.
 +&$result: Array containing all link detail arrays. Each link detail array should contain
 +  the following keys:
 +  * targetTitle - Target Title object
 +  * text - String for the text
 +  * attribs - Array of attributes
 +  * query - Array of query parameters to add to the URL
 +  * options - Array of options for Linker::link
 +$lang: The language code to use for the link in the wfMessage function
 +
  'SkinGetPoweredBy': TODO
  &$text: additional 'powered by' icons in HTML. Note: Modern skin does not use
  the MediaWiki icon but plain text instead.
@@@ -2503,7 -2420,7 +2512,7 @@@ after variants have been added
  'SkinTemplateOutputPageBeforeExec': Before SkinTemplate::outputPage() starts
  page output.
  &$sktemplate: SkinTemplate object
 -&$tpl: Template engine object
 +&$tpl: QuickTemplate engine object
  
  'SkinTemplatePreventOtherActiveTabs': Use this to prevent showing active tabs.
  $sktemplate: SkinTemplate object
@@@ -2771,8 -2688,7 +2780,8 @@@ that can be applied
  $title: The title in question.
  &$types: The types of protection available.
  
 -'TitleIsCssOrJsPage': Called when determining if a page is a CSS or JS page.
 +'TitleIsCssOrJsPage': DEPRECATED! Use ContentHandlerDefaultModelFor instead.
 +Called when determining if a page is a CSS or JS page.
  $title: Title object that is being checked
  $result: Boolean; whether MediaWiki currently thinks this is a CSS/JS page.
    Hooks may change this value to override the return value of
@@@ -2793,8 -2709,7 +2802,8 @@@ $result: Boolean; whether MediaWiki cur
    Hooks may change this value to override the return value of
    Title::isMovable().
  
 -'TitleIsWikitextPage': Called when determining if a page is a wikitext or should
 +'TitleIsWikitextPage': DEPRECATED! Use ContentHandlerDefaultModelFor instead.
 +Called when determining if a page is a wikitext or should
  be handled by separate handler (via ArticleViewCustom).
  $title: Title object that is being checked
  $result: Boolean; whether MediaWiki currently thinks this is a wikitext page.
@@@ -2976,7 -2891,7 +2985,7 @@@ $user: User objec
  &$timestamp: timestamp, change this to override local email authentication
    timestamp
  
 -'UserGetImplicitGroups': Called in User::getImplicitGroups().
 +'UserGetImplicitGroups': DEPRECATED, called in User::getImplicitGroups().
  &$groups: List of implicit (automatically-assigned) groups
  
  'UserGetLanguageObject': Called when getting user's interface language object.
@@@ -201,7 -201,7 +201,7 @@@ abstract class ContentHandler 
                $model = MWNamespace::getNamespaceContentModel( $ns );
  
                // Hook can determine default model
 -              if ( !wfRunHooks( 'ContentHandlerDefaultModelFor', array( $title, &$model ) ) ) {
 +              if ( !Hooks::run( 'ContentHandlerDefaultModelFor', array( $title, &$model ) ) ) {
                        if ( !is_null( $model ) ) {
                                return $model;
                        }
                }
  
                // Hook can force JS/CSS
 -              wfRunHooks( 'TitleIsCssOrJsPage', array( $title, &$isCssOrJsPage ) );
 +              Hooks::run( 'TitleIsCssOrJsPage', array( $title, &$isCssOrJsPage ), '1.25' );
  
                // Is this a .css subpage of a user page?
                $isJsCssSubpage = NS_USER == $ns
                $isWikitext = $isWikitext && !$isCssOrJsPage && !$isJsCssSubpage;
  
                // Hook can override $isWikitext
 -              wfRunHooks( 'TitleIsWikitextPage', array( $title, &$isWikitext ) );
 +              Hooks::run( 'TitleIsWikitextPage', array( $title, &$isWikitext ), '1.25' );
  
                if ( !$isWikitext ) {
                        switch ( $ext ) {
                if ( empty( $wgContentHandlers[$modelId] ) ) {
                        $handler = null;
  
 -                      wfRunHooks( 'ContentHandlerForModelID', array( $modelId, &$handler ) );
 +                      Hooks::run( 'ContentHandlerForModelID', array( $modelId, &$handler ) );
  
                        if ( $handler === null ) {
                                throw new MWException( "No handler for model '$modelId' registered in \$wgContentHandlers" );
        public function createDifferenceEngine( IContextSource $context, $old = 0, $new = 0,
                $rcid = 0, //FIXME: Deprecated, no longer used
                $refreshCache = false, $unhide = false ) {
-               $diffEngineClass = $this->getDiffEngineClass();
  
+               // hook: get difference engine
+               $differenceEngine = null;
+               if ( !wfRunHooks( 'GetDifferenceEngine',
+                       array( $context, $old, $new, $refreshCache, $unhide, &$differenceEngine )
+               ) ) {
+                       return $differenceEngine;
+               }
+               $diffEngineClass = $this->getDiffEngineClass();
                return new $diffEngineClass( $context, $old, $new, $rcid, $refreshCache, $unhide );
        }
  
                        $pageLang = wfGetLangObj( $lang );
                }
  
 -              wfRunHooks( 'PageContentLanguage', array( $title, &$pageLang, $wgLang ) );
 +              Hooks::run( 'PageContentLanguage', array( $title, &$pageLang, $wgLang ) );
  
                return wfGetLangObj( $pageLang );
        }
        public function canBeUsedOn( Title $title ) {
                $ok = true;
  
 -              wfRunHooks( 'ContentModelCanBeUsedOn', array( $this->getModelID(), $title, &$ok ) );
 +              Hooks::run( 'ContentModelCanBeUsedOn', array( $this->getModelID(), $title, &$ok ) );
  
                return $ok;
        }
                }
  
                // call the hook functions
 -              $ok = wfRunHooks( $event, $args );
 +              $ok = Hooks::run( $event, $args );
  
                // see if the hook changed the text
                foreach ( $contentTexts as $k => $orig ) {
@@@ -94,6 -94,10 +94,10 @@@ class DifferenceEngine extends ContextS
  
        /** @var bool Show rev_deleted content if allowed */
        protected $unhide = false;
+       /** @var bool Refresh the diff cache */
+       protected $mRefreshCache = false;
        /**#@-*/
  
        /**
        }
  
        public function showDiffPage( $diffOnly = false ) {
 -              wfProfileIn( __METHOD__ );
  
                # Allow frames except in certain special cases
                $out = $this->getOutput();
  
                if ( !$this->loadRevisionData() ) {
                        $this->showMissingRevision();
 -                      wfProfileOut( __METHOD__ );
  
                        return;
                }
                                $this->mOldPage->getUserPermissionsErrors( 'read', $user ) );
                }
                if ( count( $permErrors ) ) {
 -                      wfProfileOut( __METHOD__ );
                        throw new PermissionsError( 'read', $permErrors );
                }
  
                        $samePage = true;
                        $oldHeader = '';
                } else {
 -                      wfRunHooks( 'DiffViewHeader', array( $this, $this->mOldRev, $this->mNewRev ) );
 +                      Hooks::run( 'DiffViewHeader', array( $this, $this->mOldRev, $this->mNewRev ) );
  
                        if ( $this->mNewPage->equals( $this->mOldPage ) ) {
                                $out->setPageTitle( $this->msg( 'difference-title', $this->mNewPage->getPrefixedText() ) );
                $rdel = $this->revisionDeleteLink( $this->mNewRev );
  
                # Allow extensions to define their own revision tools
 -              wfRunHooks( 'DiffRevisionTools', array( $this->mNewRev, &$revisionTools, $this->mOldRev ) );
 +              Hooks::run( 'DiffRevisionTools', array( $this->mNewRev, &$revisionTools, $this->mOldRev ) );
                $formattedRevisionTools = array();
                // Put each one in parentheses (poor man's button)
                foreach ( $revisionTools as $key => $tool ) {
                                $this->renderNewRevision();
                        }
                }
 -              wfProfileOut( __METHOD__ );
        }
  
        /**
                                        array( 'USE INDEX' => 'rc_timestamp' )
                                );
  
 -                              if ( $change && $change->getPerformer()->getName() !== $user->getName() ) {
 +                              if ( $change && !$change->getPerformer()->equals( $user ) ) {
                                        $rcid = $change->getAttribute( 'rc_id' );
                                } else {
                                        // None found or the page has been created by the current user.
         * Show the new revision of the page.
         */
        public function renderNewRevision() {
 -              wfProfileIn( __METHOD__ );
                $out = $this->getOutput();
                $revHeader = $this->getRevisionHeader( $this->mNewRev );
                # Add "current version as of X" title
                <h2 class='diff-currentversion-title'>{$revHeader}</h2>\n" );
                # Page content may be handled by a hooked call instead...
                # @codingStandardsIgnoreStart Ignoring long lines.
 -              if ( wfRunHooks( 'ArticleContentOnDiff', array( $this, $out ) ) ) {
 +              if ( Hooks::run( 'ArticleContentOnDiff', array( $this, $out ) ) ) {
                        $this->loadNewText();
                        $out->setRevisionId( $this->mNewid );
                        $out->setRevisionTimestamp( $this->mNewRev->getTimestamp() );
                                                $out->addParserOutputContent( $po );
                                        }
                                }
 -                      } elseif ( !wfRunHooks( 'ArticleContentViewCustom', array( $this->mNewContent, $this->mNewPage, $out ) ) ) {
 +                      } elseif ( !Hooks::run( 'ArticleContentViewCustom', array( $this->mNewContent, $this->mNewPage, $out ) ) ) {
                                // Handled by extension
                        } elseif ( !ContentHandler::runLegacyHooks( 'ArticleViewCustom', array( $this->mNewContent, $this->mNewPage, $out ) ) ) {
                                // NOTE: deprecated hook, B/C only
                # Add redundant patrol link on bottom...
                $out->addHTML( $this->markPatrolledLink() );
  
 -              wfProfileOut( __METHOD__ );
        }
  
        protected function getParserOutput( WikiPage $page, Revision $rev ) {
                $parserOptions = $page->makeParserOptions( $this->getContext() );
  
 -              if ( !$rev->isCurrent() || !$rev->getTitle()->quickUserCan( "edit" ) ) {
 +              if ( !$rev->isCurrent() || !$rev->getTitle()->quickUserCan( 'edit', $this->getUser() ) ) {
                        $parserOptions->setEditSection( false );
                }
  
         */
        public function getDiffBody() {
                global $wgMemc;
 -              wfProfileIn( __METHOD__ );
                $this->mCacheHit = true;
                // Check if the diff should be hidden from this user
                if ( !$this->loadRevisionData() ) {
 -                      wfProfileOut( __METHOD__ );
  
                        return false;
                } elseif ( $this->mOldRev &&
                        !$this->mOldRev->userCan( Revision::DELETED_TEXT, $this->getUser() )
                ) {
 -                      wfProfileOut( __METHOD__ );
  
                        return false;
                } elseif ( $this->mNewRev &&
                        !$this->mNewRev->userCan( Revision::DELETED_TEXT, $this->getUser() )
                ) {
 -                      wfProfileOut( __METHOD__ );
  
                        return false;
                }
                if ( $this->mOldRev === false || ( $this->mOldRev && $this->mNewRev
                        && $this->mOldRev->getID() == $this->mNewRev->getID() )
                ) {
 -                      wfProfileOut( __METHOD__ );
  
                        return '';
                }
                                        wfIncrStats( 'diff_cache_hit' );
                                        $difftext = $this->localiseLineNumbers( $difftext );
                                        $difftext .= "\n<!-- diff cache key $key -->\n";
 -                                      wfProfileOut( __METHOD__ );
  
                                        return $difftext;
                                }
  
                // Loadtext is permission safe, this just clears out the diff
                if ( !$this->loadText() ) {
 -                      wfProfileOut( __METHOD__ );
  
                        return false;
                }
                $difftext = $this->generateContentDiffBody( $this->mOldContent, $this->mNewContent );
  
                // Save to cache for 7 days
 -              if ( !wfRunHooks( 'AbortDiffCache', array( &$this ) ) ) {
 +              if ( !Hooks::run( 'AbortDiffCache', array( &$this ) ) ) {
                        wfIncrStats( 'diff_uncacheable' );
                } elseif ( $key !== false && $difftext !== false ) {
                        wfIncrStats( 'diff_cache_miss' );
                if ( $difftext !== false ) {
                        $difftext = $this->localiseLineNumbers( $difftext );
                }
 -              wfProfileOut( __METHOD__ );
  
                return $difftext;
        }
        public function generateTextDiffBody( $otext, $ntext ) {
                global $wgExternalDiffEngine, $wgContLang;
  
 -              wfProfileIn( __METHOD__ );
 -
                $otext = str_replace( "\r\n", "\n", $otext );
                $ntext = str_replace( "\r\n", "\n", $ntext );
  
                        # input text to be HTML-escaped already
                        $otext = htmlspecialchars( $wgContLang->segmentForDiff( $otext ) );
                        $ntext = htmlspecialchars( $wgContLang->segmentForDiff( $ntext ) );
 -                      wfProfileOut( __METHOD__ );
  
                        return $wgContLang->unsegmentForDiff( wikidiff_do_diff( $otext, $ntext, 2 ) ) .
                        $this->debug( 'wikidiff1' );
                if ( $wgExternalDiffEngine == 'wikidiff2' && function_exists( 'wikidiff2_do_diff' ) ) {
                        # Better external diff engine, the 2 may some day be dropped
                        # This one does the escaping and segmenting itself
 -                      wfProfileIn( 'wikidiff2_do_diff' );
                        $text = wikidiff2_do_diff( $otext, $ntext, 2 );
                        $text .= $this->debug( 'wikidiff2' );
 -                      wfProfileOut( 'wikidiff2_do_diff' );
 -                      wfProfileOut( __METHOD__ );
  
                        return $text;
                }
  
                        $tempFile1 = fopen( $tempName1, "w" );
                        if ( !$tempFile1 ) {
 -                              wfProfileOut( __METHOD__ );
  
                                return false;
                        }
                        $tempFile2 = fopen( $tempName2, "w" );
                        if ( !$tempFile2 ) {
 -                              wfProfileOut( __METHOD__ );
  
                                return false;
                        }
                        fclose( $tempFile1 );
                        fclose( $tempFile2 );
                        $cmd = wfEscapeShellArg( $wgExternalDiffEngine, $tempName1, $tempName2 );
 -                      wfProfileIn( __METHOD__ . "-shellexec" );
                        $difftext = wfShellExec( $cmd );
                        $difftext .= $this->debug( "external $wgExternalDiffEngine" );
 -                      wfProfileOut( __METHOD__ . "-shellexec" );
                        unlink( $tempName1 );
                        unlink( $tempName2 );
 -                      wfProfileOut( __METHOD__ );
  
                        return $difftext;
                }
                $nta = explode( "\n", $wgContLang->segmentForDiff( $ntext ) );
                $diffs = new Diff( $ota, $nta );
                $formatter = new TableDiffFormatter();
 -              $difftext = $wgContLang->unsegmentForDiff( $formatter->format( $diffs ) ) .
 -                      wfProfileOut( __METHOD__ );
 +              $difftext = $wgContLang->unsegmentForDiff( $formatter->format( $diffs ) );
  
                return $difftext;
        }
                        $users = $this->mNewPage->getAuthorsBetween( $oldRev, $newRev, $limit );
                        $numUsers = count( $users );
  
 -                      if ( $numUsers == 1 && $users[0] == $newRev->getRawUserText() ) {
 +                      if ( $numUsers == 1 && $users[0] == $newRev->getUserText( Revision::RAW ) ) {
                                $numUsers = 0; // special case to say "by the same user" instead of "by one other user"
                        }
  
                        $key = $title->quickUserCan( 'edit', $user ) ? 'editold' : 'viewsourceold';
                        $msg = $this->msg( $key )->escaped();
                        $editLink = $this->msg( 'parentheses' )->rawParams(
 -                              Linker::linkKnown( $title, $msg, array( ), $editQuery ) )->plain();
 +                              Linker::linkKnown( $title, $msg, array( ), $editQuery ) )->escaped();
                        $header .= ' ' . Html::rawElement(
                                'span',
                                array( 'class' => 'mw-diff-edit' ),
                        $this->mNewid = 0;
                }
  
 -              wfRunHooks(
 +              Hooks::run(
                        'NewDifferenceEngine',
                        array( $this->getTitle(), &$this->mOldid, &$this->mNewid, $old, $new )
                );