Merge "mediawiki.special.preferences.js: Simplify modulo"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Fri, 20 Feb 2015 13:41:47 +0000 (13:41 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Fri, 20 Feb 2015 13:41:47 +0000 (13:41 +0000)
30 files changed:
.rubocop.yml
.rubocop_todo.yml
Gemfile
RELEASE-NOTES-1.25
docs/hooks.txt
includes/CategoryViewer.php
includes/DefaultSettings.php
includes/cache/BacklinkCache.php
includes/deferred/HTMLCacheUpdate.php
includes/deferred/LinksUpdate.php
includes/jobqueue/jobs/RefreshLinksJob.php
includes/page/Article.php
includes/page/WikiPage.php
includes/parser/ParserOutput.php
includes/poolcounter/PoolWorkArticleView.php
maintenance/jsduck/CustomTags.rb
resources/src/mediawiki.special/mediawiki.special.preferences.js
tests/browser/features/step_definitions/create_account_steps.rb
tests/browser/features/step_definitions/create_and_follow_wiki_link_steps.rb
tests/browser/features/step_definitions/edit_page_steps.rb
tests/browser/features/step_definitions/file_steps.rb
tests/browser/features/step_definitions/preferences_appearance_steps.rb
tests/browser/features/step_definitions/preferences_user_profile_steps.rb
tests/browser/features/step_definitions/view_history_steps.rb
tests/browser/features/support/pages/edit_page.rb
tests/browser/features/support/pages/login_error_page.rb
tests/browser/features/support/pages/main_page.rb
tests/browser/features/support/pages/preferences_appearance_page.rb
tests/browser/features/support/pages/view_history_page.rb
tests/browser/features/support/pages/ztargetpage.rb

index 00479d1..c04818e 100644 (file)
@@ -6,3 +6,6 @@ AllCops:
         - 'skins/**/*'
         - 'tests/frontend/node_modules/**/*'
         - 'vendor/**/*'
+
+Metrics/LineLength:
+  Max: 100
index f0702ba..90671fa 100644 (file)
@@ -5,34 +5,15 @@
 # Note that changes in the inspected code, or installation of new
 # versions of RuboCop, may require this file to be generated again.
 
-# Offense count: 1
-Lint/AmbiguousRegexpLiteral:
-  Enabled: false
-
 # Offense count: 2
 # Cop supports --auto-correct.
 Lint/UnusedMethodArgument:
   Enabled: false
 
-# Offense count: 19
-# Configuration parameters: AllowURI, URISchemes.
-Metrics/LineLength:
-  Max: 94
-
 # Offense count: 10
 Style/Documentation:
   Enabled: false
 
-# Offense count: 1
-# Cop supports --auto-correct.
-Style/EmptyLines:
-  Enabled: false
-
-# Offense count: 1
-# Cop supports --auto-correct.
-Style/EmptyLinesAroundBody:
-  Enabled: false
-
 # Offense count: 1
 # Configuration parameters: Exclude.
 Style/FileName:
@@ -44,41 +25,13 @@ Style/FileName:
 Style/HashSyntax:
   Enabled: false
 
-# Offense count: 2
-# Cop supports --auto-correct.
-Style/LeadingCommentSpace:
-  Enabled: false
-
 # Offense count: 4
 # Cop supports --auto-correct.
 Style/PerlBackrefs:
   Enabled: false
 
-# Offense count: 4
-# Cop supports --auto-correct.
-Style/SpaceAroundOperators:
-  Enabled: false
-
-# Offense count: 1
-# Cop supports --auto-correct.
-# Configuration parameters: EnforcedStyle, SupportedStyles, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters.
-Style/SpaceInsideBlockBraces:
-  Enabled: true
-
-# Offense count: 6
-# Cop supports --auto-correct.
-# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces, SupportedStyles.
-Style/SpaceInsideHashLiteralBraces:
-  Enabled: false
-
 # Offense count: 89
 # Cop supports --auto-correct.
 # Configuration parameters: EnforcedStyle, SupportedStyles.
 Style/StringLiterals:
   Enabled: false
-
-# Offense count: 11
-# Cop supports --auto-correct.
-# Configuration parameters: EnforcedStyle, SupportedStyles.
-Style/TrailingBlankLines:
-  Enabled: false
diff --git a/Gemfile b/Gemfile
index 1559d0e..d3f8506 100644 (file)
--- a/Gemfile
+++ b/Gemfile
@@ -1,5 +1,5 @@
-#ruby=ruby-2.1.2
-#ruby-gemset=core
+# ruby=ruby-2.1.2
+# ruby-gemset=core
 
 source "https://rubygems.org"
 
index 67a32ec..df13daa 100644 (file)
@@ -103,6 +103,8 @@ production.
   dynamically-compiled Mustache templates (currently uses lightncandy library).
 * Clickable anchors for each section heading in the content are now generated
   and appear in the gutter on hovering over the heading.
+* Added 'CategoryViewer::doCategoryQuery' and 'CategoryViewer::generateLink' hooks
+  to allow extensions to override how links to pages are rendered within NS_CATEGORY
 
 ==== External libraries ====
 * MediaWiki now requires certain external libraries to be installed. In the past
index 78ac2ff..a88803b 100644 (file)
@@ -870,6 +870,20 @@ $wikiPage: WikiPage that was removed
 '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
index 6b86853..1e0bf16 100644 (file)
@@ -174,19 +174,30 @@ class CategoryViewer extends ContextSource {
                // Subcategory; strip the 'Category' namespace from the link text.
                $title = $cat->getTitle();
 
-               $link = Linker::link( $title, htmlspecialchars( $title->getText() ) );
-               if ( $title->isRedirect() ) {
-                       // This didn't used to add redirect-in-category, but might
-                       // as well be consistent with the rest of the sections
-                       // on a category page.
-                       $link = '<span class="redirect-in-category">' . $link . '</span>';
-               }
-               $this->children[] = $link;
+               $this->children[] = $this->generateLink(
+                       'subcat',
+                       $title,
+                       $title->isRedirect(),
+                       htmlspecialchars( $title->getText() )
+               );
 
                $this->children_start_char[] =
                        $this->getSubcategorySortChar( $cat->getTitle(), $sortkey );
        }
 
+       function generateLink( $type, Title $title, $isRedirect, $html = null ) {
+               $link = null;
+               Hooks::run( 'CategoryViewer::generateLink', array( $type, $title, $html, &$link ) );
+               if ( $link === null ) {
+                       $link = Linker::link( $title, $html );
+               }
+               if ( $isRedirect ) {
+                       $link = '<span class="redirect-in-category">' . $link . '</span>';
+               }
+
+               return $link;
+       }
+
        /**
         * Get the character to be used for sorting subcategories.
         * If there's a link from Category:A to Category:B, the sortkey of the resulting
@@ -229,13 +240,7 @@ class CategoryViewer extends ContextSource {
                                $this->gallery->add( $title );
                        }
                } else {
-                       $link = Linker::link( $title );
-                       if ( $isRedirect ) {
-                               // This seems kind of pointless given 'mw-redirect' class,
-                               // but keeping for back-compatibility with user css.
-                               $link = '<span class="redirect-in-category">' . $link . '</span>';
-                       }
-                       $this->imgsNoGallery[] = $link;
+                       $this->imgsNoGallery[] = $this->generateLink( 'image', $title, $isRedirect );
 
                        $this->imgsNoGallery_start_char[] = $wgContLang->convert(
                                $this->collation->getFirstLetter( $sortkey ) );
@@ -252,13 +257,7 @@ class CategoryViewer extends ContextSource {
        function addPage( $title, $sortkey, $pageLength, $isRedirect = false ) {
                global $wgContLang;
 
-               $link = Linker::link( $title );
-               if ( $isRedirect ) {
-                       // This seems kind of pointless given 'mw-redirect' class,
-                       // but keeping for back-compatibility with user css.
-                       $link = '<span class="redirect-in-category">' . $link . '</span>';
-               }
-               $this->articles[] = $link;
+               $this->articles[] = $this->generateLink( 'page', $title, $isRedirect );
 
                $this->articles_start_char[] = $wgContLang->convert(
                        $this->collation->getFirstLetter( $sortkey ) );
@@ -331,6 +330,8 @@ class CategoryViewer extends ContextSource {
                                )
                        );
 
+                       Hooks::run( 'CategoryViewer::doCategoryQuery', array( $type, $res ) );
+
                        $count = 0;
                        foreach ( $res as $row ) {
                                $title = Title::newFromRow( $row );
index bddccec..d4cdf9e 100644 (file)
@@ -6422,7 +6422,6 @@ $wgJobClasses = array(
        'PublishStashedFile' => 'PublishStashedFileJob',
        'ThumbnailRender' => 'ThumbnailRenderJob',
        'recentChangesUpdate' => 'RecentChangesUpdateJob',
-       'refreshLinksPrioritized' => 'RefreshLinksJob', // for cascading protection
        'null' => 'NullJob'
 );
 
index c3aefc5..c6d9a18 100644 (file)
@@ -487,45 +487,4 @@ class BacklinkCache {
 
                return array( 'numRows' => $numRows, 'batches' => $batches );
        }
-
-       /**
-        * Get a Title iterator for cascade-protected template/file use backlinks
-        *
-        * @return TitleArray
-        * @since 1.25
-        */
-       public function getCascadeProtectedLinks() {
-               // This method is used to make redudant jobs anyway, so its OK to use
-               // a slave. Also, the set of cascade protected pages tends to be stable.
-               $dbr = $this->getDB();
-
-               $queries = array();
-               // @note: UNION filters any duplicate pages
-               $queries[] = $dbr->selectSQLText(
-                       array( 'templatelinks', 'page_restrictions', 'page' ),
-                       array( 'page_namespace', 'page_title', 'page_id' ),
-                       array(
-                               'tl_namespace' => $this->title->getNamespace(),
-                               'tl_title' => $this->title->getDBkey(),
-                               'tl_from = pr_page',
-                               'pr_cascade' => 1,
-                               'page_id = tl_from'
-                       )
-               );
-               $queries[] = $dbr->selectSQLText(
-                       array( 'imagelinks', 'page_restrictions', 'page' ),
-                       array( 'page_namespace', 'page_title', 'page_id' ),
-                       array(
-                               'il_to' => $this->title->getDBkey(),
-                               'il_from = pr_page',
-                               'pr_cascade' => 1,
-                               'page_id = il_from'
-                       )
-               );
-
-               return TitleArray::newFromResult( $dbr->query(
-                       $dbr->unionQueries( $queries, false ),
-                       __METHOD__
-               ) );
-       }
 }
index 862ac27..e02cfbc 100644 (file)
@@ -43,6 +43,7 @@ class HTMLCacheUpdate implements DeferrableUpdate {
        }
 
        public function doUpdate() {
+
                $job = new HTMLCacheUpdateJob(
                        $this->mTitle,
                        array(
@@ -62,5 +63,6 @@ class HTMLCacheUpdate implements DeferrableUpdate {
                                $job->run(); // just do the purge query now
                        } );
                }
+
        }
 }
index e4f00e7..9c377df 100644 (file)
@@ -228,24 +228,12 @@ class LinksUpdate extends SqlDataUpdate {
         * Which means do LinksUpdate on all pages that include the current page,
         * using the job queue.
         */
-       protected function queueRecursiveJobs() {
+       function queueRecursiveJobs() {
                self::queueRecursiveJobsForTable( $this->mTitle, 'templatelinks' );
                if ( $this->mTitle->getNamespace() == NS_FILE ) {
                        // Process imagelinks in case the title is or was a redirect
                        self::queueRecursiveJobsForTable( $this->mTitle, 'imagelinks' );
                }
-
-               $bc = $this->mTitle->getBacklinkCache();
-               // Get jobs for cascade-protected backlinks for a high priority queue.
-               // If meta-templates change to using a new template, the new template
-               // should be implicitly protected as soon as possible, if applicable.
-               // These jobs duplicate a subset of the above ones, but can run sooner.
-               // Which ever runs first generally no-ops the other one.
-               $jobs = array();
-               foreach ( $bc->getCascadeProtectedLinks() as $title ) {
-                       $jobs[] = new RefreshLinksJob( $title, array( 'prioritize' => true ) );
-               }
-               JobQueueGroup::singleton()->push( $jobs );
        }
 
        /**
@@ -265,7 +253,6 @@ class LinksUpdate extends SqlDataUpdate {
                                        "refreshlinks:{$table}:{$title->getPrefixedText()}"
                                )
                        );
-
                        JobQueueGroup::singleton()->push( $job );
                        JobQueueGroup::singleton()->deduplicateRootJob( $job );
                }
index 1252b0b..5d95792 100644 (file)
@@ -39,10 +39,6 @@ class RefreshLinksJob extends Job {
 
        function __construct( $title, $params = '' ) {
                parent::__construct( 'refreshLinks', $title, $params );
-               // A separate type is used just for cascade-protected backlinks
-               if ( !empty( $this->params['prioritize'] ) ) {
-                       $this->command .= 'Prioritized';
-               }
                // Base backlink update jobs and per-title update jobs can be de-duplicated.
                // If template A changes twice before any jobs run, a clean queue will have:
                //              (A base, A base)
@@ -104,10 +100,6 @@ class RefreshLinksJob extends Job {
                return true;
        }
 
-       /**
-        * @param Title $title
-        * @return bool
-        */
        protected function runForTitle( Title $title = null ) {
                $linkCache = LinkCache::singleton();
                $linkCache->clear();
index 83c3241..59f2ae7 100644 (file)
@@ -707,7 +707,7 @@ class Article implements Page {
                }
 
                # Get the ParserOutput actually *displayed* here.
-               # Note that $this->mParserOutput is the *current*/oldid version output.
+               # Note that $this->mParserOutput is the *current* version output.
                $pOutput = ( $outputDone instanceof ParserOutput )
                        ? $outputDone // object fetched by hook
                        : $this->mParserOutput;
index fe61f6f..d30f589 100644 (file)
@@ -3378,35 +3378,70 @@ class WikiPage implements Page, IDBAccessObject {
        }
 
        /**
-        * Opportunistically enqueue link update jobs given fresh parser output if useful
+        * Updates cascading protections
         *
-        * @param ParserOutput $parserOutput Current version page output
-        * @return bool Whether a job was pushed
-        * @since 1.25
+        * @param ParserOutput $parserOutput ParserOutput object for the current version
         */
-       public function triggerOpportunisticLinksUpdate( ParserOutput $parserOutput ) {
-               if ( wfReadOnly() ) {
-                       return false;
+       public function doCascadeProtectionUpdates( ParserOutput $parserOutput ) {
+               if ( wfReadOnly() || !$this->mTitle->areRestrictionsCascading() ) {
+                       return;
                }
 
-               if ( $this->mTitle->areRestrictionsCascading() ) {
-                       // If the page is cascade protecting, the links should really be up-to-date
-                       $params = array( 'prioritize' => true );
-               } elseif ( $parserOutput->hasDynamicContent() ) {
-                       // Assume the output contains time/random based magic words
-                       $params = array();
-               } else {
-                       // If the inclusions are deterministic, the edit-triggered link jobs are enough
-                       return false;
+               // templatelinks or imagelinks tables may have become out of sync,
+               // especially if using variable-based transclusions.
+               // For paranoia, check if things have changed and if
+               // so apply updates to the database. This will ensure
+               // that cascaded protections apply as soon as the changes
+               // are visible.
+
+               // Get templates from templatelinks and images from imagelinks
+               $id = $this->getId();
+
+               $dbLinks = array();
+
+               $dbr = wfGetDB( DB_SLAVE );
+               $res = $dbr->select( array( 'templatelinks' ),
+                       array( 'tl_namespace', 'tl_title' ),
+                       array( 'tl_from' => $id ),
+                       __METHOD__
+               );
+
+               foreach ( $res as $row ) {
+                       $dbLinks["{$row->tl_namespace}:{$row->tl_title}"] = true;
                }
 
-               // Check if the last link refresh was before page_touched
-               if ( $this->getLinksTimestamp() < $this->getTouched() ) {
-                       JobQueueGroup::singleton()->push( new RefreshLinksJob( $this->mTitle, $params ) );
-                       return true;
+               $dbr = wfGetDB( DB_SLAVE );
+               $res = $dbr->select( array( 'imagelinks' ),
+                       array( 'il_to' ),
+                       array( 'il_from' => $id ),
+                       __METHOD__
+               );
+
+               foreach ( $res as $row ) {
+                       $dbLinks[NS_FILE . ":{$row->il_to}"] = true;
                }
 
-               return false;
+               // Get templates and images from parser output.
+               $poLinks = array();
+               foreach ( $parserOutput->getTemplates() as $ns => $templates ) {
+                       foreach ( $templates as $dbk => $id ) {
+                               $poLinks["$ns:$dbk"] = true;
+                       }
+               }
+               foreach ( $parserOutput->getImages() as $dbk => $id ) {
+                       $poLinks[NS_FILE . ":$dbk"] = true;
+               }
+
+               // Get the diff
+               $links_diff = array_diff_key( $poLinks, $dbLinks );
+
+               if ( count( $links_diff ) > 0 ) {
+                       // Whee, link updates time.
+                       // Note: we are only interested in links here. We don't need to get
+                       // other DataUpdate items from the parser output.
+                       $u = new LinksUpdate( $this->mTitle, $parserOutput, false );
+                       $u->doUpdate();
+               }
        }
 
        /**
index da7842a..e9e72be 100644 (file)
@@ -879,22 +879,6 @@ class ParserOutput extends CacheTime {
                $this->mLimitReportData[$key] = $value;
        }
 
-       /**
-        * Check whether the cache TTL was lowered due to dynamic content
-        *
-        * When content is determined by more than hard state (e.g. page edits),
-        * such as template/file transclusions based on the current timestamp or
-        * extension tags that generate lists based on queries, this return true.
-        *
-        * @return bool
-        * @since 1.25
-        */
-       public function hasDynamicContent() {
-               global $wgParserCacheExpireTime;
-
-               return $this->getCacheExpiry() < $wgParserCacheExpireTime;
-       }
-
        /**
         * Get or set the prevent-clickjacking flag
         *
index 54cbb27..da20f94 100644 (file)
@@ -159,7 +159,7 @@ class PoolWorkArticleView extends PoolCounterWork {
                }
 
                if ( $isCurrent ) {
-                       $this->page->triggerOpportunisticLinksUpdate( $this->parserOutput );
+                       $this->page->doCascadeProtectionUpdates( $this->parserOutput );
                }
 
                return true;
index bf0e07f..2de3ac0 100644 (file)
@@ -77,7 +77,7 @@ class SeeTag < CommonTag
       doc = $2 ? ': ' + $2 : ''
       return formatter.format("{@link #{name}} #{doc}")
     else
-      JsDuck::Logger.warn(nil, 'Unexpected @see argument: "'+tag+'"', position)
+      JsDuck::Logger.warn(nil, 'Unexpected @see argument: "' + tag + '"', position)
       return tag
     end
   end
@@ -109,7 +109,7 @@ class ContextTag < CommonTag
       name = $1
       return formatter.format("`context` : {@link #{name}}")
     else
-      JsDuck::Logger.warn(nil, 'Unexpected @context argument: "'+tag+'"', position)
+      JsDuck::Logger.warn(nil, 'Unexpected @context argument: "' + tag + '"', position)
       return tag
     end
   end
index 6ea7501..4bd747b 100644 (file)
@@ -12,8 +12,11 @@ jQuery( function ( $ ) {
        };
 
        $( '#prefsubmit' ).attr( 'id', 'prefcontrol' );
-       $preftoc = $( '<ul id="preftoc"></ul>' )
-               .attr( 'role', 'tablist' );
+       $preftoc = $( '<ul>' )
+               .attr( {
+                       id: 'preftoc',
+                       role: 'tablist'
+               } );
        $preferences = $( '#preferences' )
                .addClass( 'jsprefs' )
                .before( $preftoc );
index 7fa2984..79507ca 100644 (file)
@@ -10,7 +10,7 @@
 # https://git.wikimedia.org/blob/qa%2Fbrowsertests/HEAD/CREDITS
 #
 Given(/^I go to Create account page at (.+)$/) do |path|
-  visit(CreateAccountPage, :using_params => {:page_title => path})
+  visit(CreateAccountPage, :using_params => { :page_title => path })
 end
 
 Then(/^form has Create account button$/) do
index ba41f7f..5271ea8 100644 (file)
@@ -6,23 +6,21 @@ end
 
 Given(/^I am on the (.+) page$/) do |article|
   article = article.gsub(/ /, '_')
-  visit(ZtargetPage, :using_params => {:article_name => article})
+  visit(ZtargetPage, :using_params => { :article_name => article })
 end
 
 Given(/^I create page "(.*?)" with content "(.*?)"$/) do |page_title, page_content|
   on(APIPage).create page_title, page_content
 end
 
-
 When(/^I click the Link Target link$/) do
   on(ZtargetPage).link_target_page_link
 end
 
 Then(/^I should be on the Link Target Test Page$/) do
-  @browser.url.should match /Link_Target_Test_Page/
+  @browser.url.should match(/Link_Target_Test_Page/)
 end
 
 Then(/^the page content should contain "(.*?)"$/) do |content|
   on(ZtargetPage).page_content.should match content
 end
-
index 5ab02be..713bb39 100644 (file)
@@ -21,4 +21,3 @@ end
 Then(/^the edited page content should contain "(.*?)"$/) do |content|
   on(MainPage).page_content.should match(content + @random_string)
 end
-
index a2ed1bf..6f017c1 100644 (file)
@@ -10,7 +10,7 @@
 # https://git.wikimedia.org/blob/qa%2Fbrowsertests/HEAD/CREDITS
 #
 Given(/^I am at file that does not exist$/) do
-  visit(FileDoesNotExistPage, using_params: {page_name: @random_string})
+  visit(FileDoesNotExistPage, using_params: { page_name: @random_string })
 end
 
 Then(/^page should show that no such file exists$/) do
index 1ecc008..6bb7e83 100644 (file)
@@ -5,4 +5,3 @@ end
 Then(/^I should see a link to a previous version of the page$/) do
   on(ViewHistoryPage).old_version_link_element.should be_visible
 end
-
index b619c34..237441e 100644 (file)
@@ -5,4 +5,4 @@ class EditPage
   button(:preview_button, id: "wpPreview")
   button(:show_changes_button, id: "wpDiff")
   button(:save_button, id: "wpSave")
-end
\ No newline at end of file
+end
index 4fc9ca7..fd000e6 100644 (file)
@@ -2,4 +2,4 @@ class LoginErrorPage
   include PageObject
 
   div(:error_box, class: "errorbox")
-end
\ No newline at end of file
+end
index 7d96c2b..8156800 100644 (file)
@@ -16,4 +16,4 @@ class MainPage
   li(:special_pages_link, id: "t-specialpages")
   a(:view_history_link, href: /action=history/)
   li(:what_links_here_link, id: "t-whatlinkshere")
-end
\ No newline at end of file
+end
index c24e386..ed4491e 100644 (file)
@@ -38,4 +38,3 @@ class PreferencesAppearancePage
   radio_button(:vector, id: "mw-input-wpskin-vector")
   radio_button(:year_mo_day_radio, id: "mw-input-wpdate-ymd")
 end
-
index 6689598..bb9c586 100644 (file)
@@ -3,5 +3,4 @@ class ViewHistoryPage
 
   a(:view_history_link, href: /action=history/)
   a(:old_version_link, href: /oldid=/)
-
-end
\ No newline at end of file
+end
index c1f46ec..7f168db 100644 (file)
@@ -4,4 +4,4 @@ class ZtargetPage < MainPage
   include PageObject
 
   a(:link_target_page_link, text: "link to the test target page")
-end
\ No newline at end of file
+end