Jump to content

User:SoledadKabocha/linkclassifier.js

From Wikipedia, the free encyclopedia
This is an old revision of this page, as edited by SoledadKabocha (talk | contribs) at 06:50, 2 January 2016 (one more thing). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
/* Modified from [[User:Anomie/linkclassifier.js]]
If you want to use this script, simply add the following line to your [[Special:MyPage/common.js]] or [[Special:MyPage/skin.js]]:
 
importScript('User:Anomie/linkclassifier.js'); //Linkback: [[User:SoledadKabocha/linkclassifier.js]]
 
(Please keep the comment so I can see how many people use this). You will also want to
add some CSS rules, such as those at [[User:Anomie/linkclassifier.css]].
Note that the classes newly added to this script do not have rules in Anomie's stylesheet.
See the documentation page for a complete list of such classes.
 */
window.LinkClassifierSupportsFuncChain = true; // flag to distinguish this script from the original linkclassifier; SHOULD be read but not modified by other scripts

// These need to be global so other scripts (e.g. jsMessage) can read them (XXX: really?)
var LinkClassifierApiRequestsOutstanding = 0;
var LinkClassifierChainedFuncCalledAlready = false;

function defaultPref( cfig, dflt ) {
  return ( typeof cfig == typeof dflt ? cfig : dflt );
}

window.LinkClassifierErrMsgPrefix = defaultPref( window.LinkClassifierErrMsgPrefix, '<span class="linkclassifier-error">LinkClassifier FAILED: Something went wrong with the server or your Internet connection, or you impatiently browsed to another page.</span><br />' );

var LinkClassifier={
    /* This object maps classes to the categories for which to apply them. Values may be an array of strings or a regex. */
    cats:{
        deletion:[
            'Category:All articles proposed for deletion',
            'Category:All books proposed for deletion',
            'Category:All disputed non-free Wikipedia files',
            'Category:All orphaned non-free use Wikipedia files',
            'Category:All possibly unfree Wikipedia files',
            'Category:All replaceable non-free use Wikipedia files',
            'Category:All Wikipedia files with no copyright tag',
            'Category:All Wikipedia files with no non-free use rationale',
            'Category:All Wikipedia files with unknown copyright status',
            'Category:All Wikipedia files with unknown source',
            'Category:Articles for deletion',
            'Category:Articles on deletion review',
            'Category:Candidates for speedy deletion',
            'Category:Candidates for undeletion',
            'Category:Categories for conversion',
            'Category:Categories for deletion',
            'Category:Categories for listifying',
            'Category:Categories for merging',
            'Category:Categories for renaming',
            'Category:Categories for speedy renaming',
            'Category:Categories to be listified then deleted',
            'Category:Duplicate or hardcoded templates awaiting deletion',
            'Category:Items pending OTRS confirmation of permission for over 30 days',
            'Category:Miscellaneous pages for deletion',
            'Category:Stub categories for deletion',
            'Category:Stub template deletion candidates',
            'Category:Templates for deletion',
            'Category:Wikipedia deprecated and orphaned templates',
            'Category:Wikipedia files for deletion',
            'Category:Wikipedia files with unknown source for deletion in dispute',
            'Category:Wikipedia templates for deletion'
        ].sort(),
        'deletion-rfd':[
            'Category:Redirects for discussion', // moved from deletion - obsolete?
            'Category:All redirects for discussion'
        ].sort(),
        disambiguation:[
            'Category:All disambiguation pages'
        ].sort(),
        'set-index':[
            'Category:All set index articles'
        ].sort(),
        'featured-content':[
            'Category:Featured articles',
            'Category:Featured lists',
            'Category:Featured pictures',
            'Category:Featured sounds',
            'Category:Featured videos',
            'Category:Featured portals'
        ].sort(),
        'good-content':[
            'Category:Good articles'
        ].sort(),
        'soft-redirect':[
            'Category:Wikipedia soft redirects',
            'Category:Wikipedia interwiki soft redirects',
            'Category:Wikipedia soft redirected project pages',
            'Category:Protected soft redirects',
            'Category:Wikipedia soft redirected talk pages',
            'Category:Wikipedia soft redirected templates',
            'Category:User soft redirects',
            'Category:Redirects to Wikiquote',
            'Category:Deprecated shortcuts',
            'Category:Redirects to Wiktionary'
        ].sort(),
        'soft-redirect-cats':[
            'Category:Wikipedia soft redirected categories',
        ].sort(),
        'spoken-articles':[
            'Category:Spoken articles'
        ].sort(),
        stubcls:/^Category:.* stubs$/,
        'nonfree-media':[
            'Category:All non-free media'
        ].sort(),
        'no-attrib-reqd-media':[
            //'Category:Cc-zero images',
            'Category:CC-zero files',
            //'Category:All user-created public domain images',
            'Category:All User-created public domain files',
            //'Category:Author died more than 100 years ago public domain images',
            'Category:Author died more than 100 years ago public domain files',
            //'Category:Author died more than 70 years ago public domain images',
            'Category:Author died more than 70 years ago public domain files',
            //'Category:Copyright holder released public domain images',
            'Category:Copyright holder released public domain files',
            'Category:PD OpenClipart',
            'Category:PD US no notice',
            'Category:PD US not renewed',
            'Category:PD chem',
            'Category:PD other reasons',
            'Category:PD script',
            'Category:PD tag needs updating',
            'Category:PD-link',
            'Category:Pre 1978 without copyright notice US public domain images', // still current name
            'Category:Public domain art',
            //'Category:Public domain images',
            'Category:Public domain files',
            //'Category:Public domain images ineligible for copyright',
            'Category:Public domain files ineligible for copyright',
            'Category:Public domain images of currency', // still current name
            'Category:Public domain images of fonts', // still current name
            'Category:Public domain music images', // still current name
            'Category:Files copyrighted by the Wikimedia Foundation'
        ].sort(),
        unprintworthy:[
            'Category:Unprintworthy redirects',
            'Category:Middle-earth redirects from redundant titles'
        ].sort(),
        'unprintworthy-shortcut':[
            'Category:Redirects from shortcuts'
        ].sort(),
        printworthy:[
            'Category:Printworthy redirects',
            'Category:Redirects from systematic names',
            'Category:Redirects from systematic abbreviations',
            'Category:Redirects from ISO 3166 codes',
            'Category:Redirects from postal abbreviations',
            'Category:Redirects from scientific abbreviations',
            'Category:Redirects from stock symbols',
            'Category:Redirects from scientific names'
        ].sort(),
        invalidisbn:[
            'Category:Articles with invalid ISBNs',
            'Category:Articles with invalid ISSNs',
            'Category:Pages with ISBN errors' // applied by Citation Style 1 module, so also in cs1luaerror
        ].sort(),
        missingisbn:[
            'Category:Articles lacking ISBNs'
        ].sort(),
        'blp-sources':/^Category:BLP articles lacking sources( from |$)/,
        'blp-unreferenced':[
            'Category:All unreferenced BLPs',
        ].sort(),
        'blp-sourceimdb':/^Category:Articles sourced (only )?by IMDb( from |$)/,
        'unclear-notability':[
            'Category:All articles with topics of unclear notability'
        ].sort(),
        'need-coord':[
            'Category:All articles needing coordinates',
            'Category:Pages with malformed coordinate tags'
        ].sort(),
        'cleanup-translation':[
            'Category:Wikipedia articles needing cleanup after translation'
        ].sort(),
        deadend:[
            'Category:All dead-end pages',
            'Category:All articles with too few wikilinks',
            'Category:All articles covered by WikiProject Wikify'
        ].sort(),
        'expert-attention':[
            'Category:All articles needing expert attention'
        ].sort(),
        'user-special-block':[
            'Category:Wikipedia contact role accounts',
            'Category:Wikipedia maintenance scripts'
        ].sort(),
        'user-compromised-block':[ // not 100% useful because the user(talk) pages may not exist
            'Category:Compromised accounts'
        ].sort(),
        'user-altaccount':[
            'Category:Wikipedia doppelganger accounts'
        ].sort(),
        'redirect-possibilities':[
            'Category:Redirects with possibilities',
            'Category:Redirects from albums',
            'Category:Redirects from books',
            'Category:Redirects from brand names',
            'Category:Redirects from EPs',
            'Category:Redirects from films',
            'Category:Redirects from former names',
            'Category:Redirects to list entries',
            //'Category:Redirects from people', // doesn't currently contain articles
            'Category:Redirects from phrases',
            'Category:Redirects from products',
            'Category:Redirects from school articles',
            'Category:Redirects from songs',
            'Category:Redirects from writers',
            //'Category:Redirects from transport routes', // doesn't currently contain articles
            'Category:Comics redirects with possibilities',
            'Category:Redirects to decade',
            'Category:Middle-earth redirects with possibilities',
            'Category:Redirects from birth names',
            'Category:Redirects from alternative characters',
            'Category:Comics redirects to lists',
            'Category:Comics redirects from related words',
            'Category:Comics redirects to sections',
            'Category:Middle-earth redirects to lists',
            'Category:Redirects from birth names',
            'Category:Redirects from historic names',
            'Category:Redirects from maiden names',
            'Category:Redirects from married names',
            'Category:Middle-earth redirects from former names',
            'Category:Characters redirects to lists',
            'Category:Episode redirects to lists', // XXX: Subcats not done!
            'Category:Redirected episode articles',
            'Category:Fiction-based redirects to list entries',
            'Category:Redirected characters articles',
            'Category:Aqua Teen Hunger Force characters redirects to lists',
            'Category:Courage the Cowardly Dog characters redirects to lists',
            'Category:I Am Weasel characters redirects',
            'Category:SpongeBob SquarePants characters redirects to lists',
            'Category:The Venture Bros. characters redirects to lists',
            'Category:Fictional character redirects to lists', // XXX: Subcats not done!
            //'Category:Fictional element redirects to lists', // doesn't currently contain articles
            'Category:Redirected fictional element articles',
            'Category:Redirects from individual people',
            'Category:Redirects from multiple people',
            'Category:Redirects from members',
            'Category:Redirects from quotations',
            'Category:Redirects from slogans',
            'Category:Redirects from highway routes',
            'Category:Redirects from London bus routes',
            'Category:Redirects from New York City area bus routes',
            'Category:Redirects from railroad names with ampersands',
            'Category:Redirects from highway in region' // has a "without possibilities" subcat...
        ].sort(),
        'redirect-section':[                  // Anomie said not 100% useful because a human has to manually add category
            'Category:Redirects to sections',
            'Category:Redirects to embedded anchors'
        ].sort(),
        'redirect-listentry':[
            'Category:Redirects to list entries' // also in -possibilities, I know
        ].sort(),
        'redirect-incompletedab':[
            'Category:Redirects from incomplete disambiguations'
        ].sort(),
        chemobot:/^Category:(\w+boxes which contain changes to (watched|verified) fields|Articles (containing unverified chemical infoboxes|with changed (\w+ identifier|CASNo)))$/,
        citeerror:/^Category:(Pages with (\w+ reference names|incorrect ref formatting|missing references list|reference errors)|Articles with incorrect citation syntax)$/,
        npovdispute:[
            'Category:All NPOV disputes'
        ].sort(),
        accuracydispute:[
            'Category:All accuracy disputes'
        ].sort(),
        copyedit:[
            'Category:All articles needing copy edit'
        ].sort(),
        deadextlink:[
            'Category:All articles with dead external links',
            'Category:All articles with broken or outdated citations'
        ].sort(),
        outofdate:/^Category:(Articles with obsolete information|Wikipedia articles in need of updating)( from |$)/,
        nofootnotes:[
            'Category:All articles lacking in-text citations'
        ].sort(),
        'disambiguation-cleanup':/^Category:(Disambiguation pages (in need of (being split|cleanup)|to be converted to broad concept articles)|Incomplete disambiguation)( from |$)/,
        'redirect-crossnamespace':[
            'Category:Cross-namespace redirects',
            'Category:Redirects to category space',
            'Category:Redirects to help namespace',
            'Category:Redirects to the main namespace',
            'Category:Redirects to portal space',
            'Category:Redirects to template from non-template namespace',
            'Category:Redirects to project space',
            'Category:Redirects to user namespace',
            'Category:Redirects to talk pages'
        ].sort(),
        'systemicbias-geo':/^Category:((Articles with (disproportional|limited)|Vague or ambiguous) geographic scope( from |$)|.+-centric$)/,
        'systemicbias-time':/^Category:Articles (slanted towards recent events( from |$)|lacking historical information$)/,
        'systemicbias-other':/^Category:Articles needing more viewpoints( from |$)/,
        tempundelete:[
            'Category:Candidates for undeletion'
        ].sort(),
        'promotional-tone':[
            'Category:All articles with a promotional tone'
        ].sort(),
        'retracted-pub':[
            'Category:Articles citing retracted publications'
        ].sort(),
        rewrite:/^Category:Wikipedia articles needing rewrite( from |$)/,
        subscriptiononly:[
            'Category:Pages containing links to subscription only content'
        ].sort(),
        povcheck:/^Category:Articles needing POV-check( from |$)/,
        cs1luaerror:/^Category:(Pages (using (web )?citations with|with archiveurl cit.+ errors$|with citations ((hav|us)ing|lacking titles$)|with [A-Z]+ errors$|with empty citations$)|CS1 (errors|maint))/,
        'transwiki-cleanup':[
            'Category:Transwiki cleanup'
        ].sort(),
        'template-error':[
            'Category:Pages containing omitted template arguments',
            'Category:Pages where expansion depth is exceeded',
            'Category:Pages where node count is exceeded',
            'Category:Pages where node-count is exceeded', //no longer used on WMF wikis but may be elsewhere
            'Category:Pages where template include size is exceeded',
            'Category:Template loop warnings'
        ].sort(),
        'disambiguation-cats':[
            'Category:Disambiguation categories'
        ].sort(),
        luaerror:[
            'Category:Pages with script errors'
        ].sort(),
        'redirect-grammaticalnumber':/^Category:Redirects (from|to) plurals$/
    },

    /* This object maps page props to CSS classes for which to apply them. Values may be an array of strings or a function returning such. */
    props: {
        disambiguation:[
            'disambiguation'
        ],
    },

    /* This regex matches page titles to be marked as intentional links to disambiguation pages */
    intentionaldab: / \(disambiguation\)$/,

    initUnloadHandlerIfNeeded:function( ) {
        if ( window.LinkClassifierUninterruptible === true ) {
            if ( ( wgAction == 'edit' || wgAction == 'submit' ) && $.compareObject( mw.user.options.get( [ 'skin' ] ), { skin: 'vector' } ) ) {
                mw.util.jsMessage( 'linkclassifier: Ignoring user setting of uninterruptible mode to avoid spurious warnings saving edits in the Vector skin' );
                window.LinkClassifierUninterruptible = false; // still needed?
                return;
            }
            else {
                window.LinkClassifierOldOnbeforeunload = window.onbeforeunload;
                window.onbeforeunload = function (evt) {
                    var message = 'LinkClassifier is still waiting for API responses.';
                                  //XXX: At least Firefox no longer shows the custom msg...
                    if (evt === undefined) {
                        evt = window.event;
                    }
                    if (evt) {
                        evt.returnValue = message;
                    }
                    return message;
                }
            }
            // need to check whether the timeout already exists?
            window.LinkClassifierUnloadSafetyTimeout = setTimeout( LinkClassifier.removeUnloadHandlerIfNeeded, 45000 );

            if ( typeof window.LinkClassifierStatusSel == 'string' ) {
                $( window.LinkClassifierStatusSel ).addClass( 'linkclassifier-unload-added' );
            }
        }
    },

    removeUnloadHandlerIfNeeded:function( ) {
        if ( !window.LinkClassifierUninterruptible || window.LinkClassifierUnloadHandlerRemoved ) return;

        window.onbeforeunload = window.LinkClassifierOldOnbeforeunload; // does this need a type safety check?
        if ( typeof window.LinkClassifierUnloadSafetyTimeout == 'number' && window.LinkClassifierUnloadSafetyTimeout != 0 ) {
            clearTimeout( window.LinkClassifierUnloadSafetyTimeout );

            if ( typeof window.LinkClassifierStatusSel == 'string' ) {
                var statusElement = $( window.LinkClassifierStatusSel );
                statusElement.removeClass( 'linkclassifier-unload-added' );
                statusElement.addClass(    'linkclassifier-unload-removed' );
            }
        }

        window.LinkClassifierUnloadHandlerRemoved = true;
    },

    forceCallChainedFunc:function( ) {
        if ( !LinkClassifierChainedFuncCalledAlready && typeof( window.LinkClassifierChainedFunc ) === 'function' ) {
            LinkClassifierChainedFuncCalledAlready = true;
            if ( window.LinkClassifierChainedFuncArg === undefined ) {
                LinkClassifierChainedFunc();
            }
            else {
                LinkClassifierChainedFunc( window.LinkClassifierChainedFuncArg );
            }
        }
        LinkClassifier.removeUnloadHandlerIfNeeded( );

        if ( typeof window.LinkClassifierStatusSel == 'string' ) {
            var statusElement = $( window.LinkClassifierStatusSel );
            statusElement.removeClass( 'linkclassifier-started' );
            statusElement.addClass(    'linkclassifier-finished' );
        }
    },

    // should be renamed "finalizeRequest" or similar
    maybeCallChainedFunc:function(xhr, sts){
        if ( LinkClassifierApiRequestsOutstanding <= 0 ) {
            if ( typeof window.LinkClassifierStatusSel == 'string' ) {
                var statusElement = $( window.LinkClassifierStatusSel );
                statusElement.removeClass( 'linkclassifier-started' );
                statusElement.addClass(    'linkclassifier-aborted' );
            }

            if ( window.console !== undefined && typeof(window.console.error)=='function' ) {
                window.console.error( 'linkclassifier: Finished more API requests than started (maybe multiple copies are running)?! (curr=' + LinkClassifierApiRequestsOutstanding + ')' );
            }
            mw.util.jsMessage( 'linkclassifier: Finished more API requests than started<br />(maybe multiple copies are running)?!<br />(curr=' + LinkClassifierApiRequestsOutstanding + ')' );
        }
        else --LinkClassifierApiRequestsOutstanding;
        if ( LinkClassifierApiRequestsOutstanding <= 0 ) {
            LinkClassifier.forceCallChainedFunc( );
        }
    },

    callback:function(r, sts, xhr){
        if(!r.query) {
            LinkClassifier.removeUnloadHandlerIfNeeded( );

            if ( typeof window.LinkClassifierStatusSel == 'string' ) {
                var statusElement = $( window.LinkClassifierStatusSel );
                statusElement.removeClass( 'linkclassifier-started' );
                statusElement.addClass(    'linkclassifier-aborted' );
            }

            mw.util.jsMessage( 'linkclassifier: Bad response; ' + LinkClassifierApiRequestsOutstanding + ' requests were in progress' );
            if( window.console === undefined || typeof(window.console.error)!='function')
                throw new Error('Bad response; ' + LinkClassifierApiRequestsOutstanding + ' requests were in progress');
            window.console.error("Bad response; " + LinkClassifierApiRequestsOutstanding + ' requests were in progress', r);
            return;
        }
        if(r['query-continue']){
            mw.util.jsMessage( 'linkclassifier DEBUG: Query is continuing' );
            var cc=this.rawdata;
            for(var k in r['query-continue']){
                for(var k2 in r['query-continue'][k]){
                    cc[k2]=r['query-continue'][k][k2];
                }
            }
            LinkClassifierApiRequestsOutstanding++;
            $.ajax({
                url:mw.util.wikiScript('api'),
                dataType:'json',
                type:'POST',
                data:cc,
                rawdata:cc,
                success:LinkClassifier.callback, // recurse
                error:function(xhr,textStatus,errorThrown){
                    LinkClassifier.removeUnloadHandlerIfNeeded( );

                    if ( typeof window.LinkClassifierStatusSel == 'string' ) {
                        var statusElement = $( window.LinkClassifierStatusSel );
                        statusElement.removeClass( 'linkclassifier-started' );
                        statusElement.addClass(    'linkclassifier-aborted' );
                    }

                    var errTxt = 'AJAX error (callback query-continue), with '
                               + LinkClassifierApiRequestsOutstanding + ' requests in progress: '+textStatus+' '+errorThrown;
                    mw.util.jsMessage( window.LinkClassifierErrMsgPrefix + errTxt );
                    throw new Error( errTxt );
                },
                complete:LinkClassifier.maybeCallChainedFunc
            });
        }
        r=r.query;

        var a=document.getElementById('wikiPreview');
        if(!a && $('.mw-redirectedfrom').length == 1) a=document.getElementById('mw-content-text');
        if(!a) a=document.getElementById('bodyContent');
        if(!a) {
            LinkClassifier.removeUnloadHandlerIfNeeded( );

            if ( typeof window.LinkClassifierStatusSel == 'string' ) {
                var statusElement = $( window.LinkClassifierStatusSel );
                statusElement.removeClass( 'linkclassifier-started' );
                statusElement.addClass(    'linkclassifier-aborted' );
            }

            throw new Error('Huh? No body content?');
        }
        a=a.getElementsByTagName('A');
        if(!a || a.length==0) { LinkClassifier.forceCallChainedFunc( ); return; }

        var redir={};
        var redirlist=[];
        if(r.redirects) for(var i=r.redirects.length-1; i>=0; i--){
            redir[r.redirects[i].from]=r.redirects[i].to;
            redirlist.push(r.redirects[i].from);
        }
        if(redirlist.length>0) {
            var q = {format:'json', action:'query', titles:redirlist.join('|'), prop:'categories|info', cllimit:'max', inprop:'protection', rawcontinue:1 }
            LinkClassifierApiRequestsOutstanding++;
            $.ajax({
                url:mw.util.wikiScript('api'),
                dataType:'json',
                type:'POST',
                data:q,
                rawdata:q,
                success:LinkClassifier.callback, // recurse
                error:function(xhr,textStatus,errorThrown){
                    LinkClassifier.removeUnloadHandlerIfNeeded( );

                    if ( typeof window.LinkClassifierStatusSel == 'string' ) {
                        var statusElement = $( window.LinkClassifierStatusSel );
                        statusElement.removeClass( 'linkclassifier-started' );
                        statusElement.addClass(    'linkclassifier-aborted' );
                    }

                    var errTxt = 'AJAX error (callback redir), with '
                               + LinkClassifierApiRequestsOutstanding + ' requests in progress: '+textStatus+' '+errorThrown;
                    mw.util.jsMessage( window.LinkClassifierErrMsgPrefix + errTxt );
                    throw new Error( errTxt );
                },
                complete:LinkClassifier.maybeCallChainedFunc
            });
        }

        var defaultPrefix = 'redir-'; // make configurable?
        var prefix=( this.rawdata.redirects ? '' : defaultPrefix );
        var cats={};
        var missing={};
        var classes={};
        var uncategorized={};
        if(r.pages) { for(var i in r.pages){
            classes[r.pages[i].title] = [];
            missing[r.pages[i].title]=( r.pages[i].missing !== undefined );
            if ( !missing[r.pages[i].title] ) {
                if( r.pages[i].categories !== undefined ){
                    cats[r.pages[i].title]=r.pages[i].categories.map(function(a){ return a.title; }).sort();
                    uncategorized[r.pages[i].title]=( r.pages[i].categories.length == 0 );
                }
                else {
                    uncategorized[r.pages[i].title]=true;
                }
            }
            if(r.pages[i].pageprops!==undefined){
                for ( var k in r.pages[i].pageprops ) {
                        if ( !LinkClassifier.props[k] ) {
                                continue;
                        }
                        var v = LinkClassifier.props[k];
                        if ( $.isFunction( v ) ) { // TODO: What does isFunction do that typeof v == 'function' doesn't?
                                v = v( r.pages[i].pageprops[k], k, r.pages[i].title );
                        }
                        classes[r.pages[i].title].push.apply( classes[r.pages[i].title], v );
                }
            }
            if(r.pages[i].protection!== undefined){
                classes[r.pages[i].title]=[];
                var x={};
                for(var j=r.pages[i].protection.length-1; j>=0; j--){
                    var p=prefix+'protection-'+r.pages[i].protection[j].type+'-'+r.pages[i].protection[j].level;
                    if( x[p] === undefined ){
                        x[p]=1;
                        classes[r.pages[i].title].push(p);
                    }
                    if(r.pages[i].protection[j].expiry=='infinity'){
                        p+='-indef';
                        if( x[p] === undefined ){
                            x[p]=1;
                            classes[r.pages[i].title].push(p);
                        }
                    }
                }
            }
            if(r.pages[i].flagged!==undefined){
                if(r.pages[i].lastrevid!=r.pages[i].flagged.stable_revid){
                    classes[r.pages[i].title].push('needs-review');
                }
            }
        }/* end of for loop */}
        else {
            mw.util.jsMessage( 'linkclassifier DEBUG: Got response with no matching pages' );
        }
        Array.prototype.forEach.call(a, function(a){
            if( a.wikipage === undefined ) {
                mw.util.jsMessage( 'linkclassifier DEBUG: Got undefined wikipage for link<br />' + a.href );
                //should also print a.title?
                return;
            }
            var origHref = a.href;
            var curRedirMalformed = false;
            if( redir[a.wikipage] !== undefined ){
                $(a).addClass('redirect');
                a.wikipage=redir[a.wikipage];
                var newTitle='';
                if ( a.wikipage != '' ) {
                    newTitle = a.wikipage;
                    var cns=mw.config.get('wgCanonicalNamespace');
                    if(a.wikipage==(cns?cns+':':'')+mw.config.get('wgTitle'))
                        $(a).addClass('self-redirect');
                    else if(missing[a.wikipage])
                        $(a).addClass('broken-redirect');
                } 
                else {
                    newTitle = 'Malformed redirect (target begins with #)';
                    curRedirMalformed = true;
                    $(a).addClass('broken-redirect');
                }
                a.title=( window.LinkClassifierRedirTitleAppend === true ? a.title + '\nRedirects to: ' + newTitle : newTitle );
            }
            var m=origHref.match(/#.*/);
            if(m && m[0].substr(0,10)!=="#cite_note" && !curRedirMalformed){
                //HACK to avoid overwriting tooltips already modified by [[User:SoledadKabocha/markBlockedPlus.js]] (or the original markblocked)
                //TODO: logging, as this might not always work
                if ( !( typeof markBlocked == 'function' && !window.mbTipBox && $(a).hasClass('userlink') ) ) {
                    //Anchor unencoding. Since fromCharCode only supports the Basic Multilingual Plane, and fromCodePoint is still experimental,
                    //we handle only up to 3 UTF-8 bytes.

                    //Basic Latin (1 UTF-8 byte), avoiding control chars
                    var mfix = m[0].replace(/_/g,' ').replace(/\.([2-7][0-9A-F])/g, function(x,n){ return String.fromCharCode(parseInt(n,16)); });
                    mfix = mfix.replace(/\u007F/g, '.7F');

                    //2 UTF-8 bytes
                    var regex2b  = /\.([CD][0-9A-F])\.([89AB][0-9A-F])/;
                    var result2b = regex2b.exec( mfix );
                    while ( result2b !== null ) {
                        var byte1 = parseInt( result2b[ 1 ], 16 );
                        var byte2 = parseInt( result2b[ 2 ], 16 );
                        var fixedCode = ( ( byte1 & 0x1F ) << 6 ) | ( byte2 & 0x3F );
                        mfix = mfix.replace( regex2b, String.fromCharCode( fixedCode ) );
                        result2b = regex2b.exec( mfix );
                    }

                    //3 UTF-8 bytes
                    var regex3b  = /\.E([0-9A-F])\.([89AB][0-9A-F])\.([89AB][0-9A-F])/;
                    var result3b = regex3b.exec( mfix );
                    while ( result3b !== null ) {
                        var nibb1 = parseInt( result3b[ 1 ], 16 );
                        var byte2 = parseInt( result3b[ 2 ], 16 );
                        var byte3 = parseInt( result3b[ 3 ], 16 );
                        var fixedCode = ( nibb1 << 12 ) | ( ( byte2 & 0x3F ) << 6 ) | ( byte3 & 0x3F );
                        mfix = mfix.replace( regex3b, String.fromCharCode( fixedCode ) );
                        result3b = regex3b.exec( mfix );
                    }

                    a.title=a.title.replace(/#.*/,'')+mfix;
                }
            }
            if(LinkClassifier.intentionaldab.test(a.origwikipage)){
                $(a).addClass('intentional-disambiguation');
            }
            var shouldDetectUncat = window.LinkClassifierReallyEnableUncategorized && ( !window.LinkClassifierFilterUncategorized || !(/^((User|Talk|MediaWiki|Module|\w+ talk):|Main Page$)/.test( a.wikipage )) );
            if( shouldDetectUncat && uncategorized[a.wikipage] === true ) {
                $(a).addClass('uncategorized');
            }
            if( a.wikipage != a.origwikipage && uncategorized[a.origwikipage] === true ) {
                //XXX: uncategorized test here wasn't originally type-safe...did I have a good reason?
                $(a).addClass(defaultPrefix + 'uncategorized');
            }
            if( classes[a.wikipage] !== undefined ){
                for(var j=classes[a.wikipage].length-1; j>=0; j--)
                    $(a).addClass(classes[a.wikipage][j]);
            }
            if( a.wikipage!=a.origwikipage && classes[a.origwikipage] !== undefined ){
                for(var j=classes[a.origwikipage].length-1; j>=0; j--)
                    $(a).addClass(classes[a.origwikipage][j]);
            }
            var c1=[];
            if( cats[a.wikipage] !== undefined ){
                c1=c1.concat(cats[a.wikipage]);
            }
            if(a.wikipage!=a.origwikipage && cats[a.origwikipage] !== undefined ){
                c1=c1.concat(cats[a.origwikipage]);
            }
            if(c1.length>0){
                c1=c1.sort();
                for(var cls in LinkClassifier.cats){
                    var i1=c1.length-1;
                    var c2=LinkClassifier.cats[cls];
                    if(c2 instanceof RegExp){
                        while(i1>=0){
                            if(c2.test(c1[i1])){
                                $(a).addClass(cls);
                                break;
                            }
                            i1--;
                        }
                    } else {
                        var i2=c2.length-1;
                        while(i1>=0 && i2>=0){
                            if(c1[i1]==c2[i2]){
                                $(a).addClass(cls);
                                break;
                            }
                            (c1[i1]>c2[i2])?--i1:--i2;
                        }
                    }
                }
            }
        });
    },

    getPageName:function(url){
        var m=url.match(/\/wiki\/([^?#]+)/);
        if(!m) m=url.match(/\/w\/index.php\?(?:.*&)?title=([^&#]+)/);
        if(!m) return '';
        var t=decodeURIComponent(m[1]).replace(/_/g,' ');
        if(t.substr(0,6)=='Image:') t='File:'+t.substr(6);
        if(t.substr(0,11)=='Image talk:') t='File talk:'+t.substr(6);
        if(t.substr(0,8)=='Special:') t='';
        return t;
    },

    classifyChildren:function(node){
        mw.loader.using(['mediawiki.util','mediawiki.user'], function(){
            var fixedNode = ( node === undefined ? document.getElementById( 'wikiPreview' ) : node );
            if ( !fixedNode ) fixedNode = document.getElementById( 'bodyContent' );
            if ( !fixedNode ) return;

            if ( typeof window.LinkClassifierStatusSel == 'string' ) {
                $( window.LinkClassifierStatusSel ).addClass( 'linkclassifier-started' );
            }

            var a=fixedNode.getElementsByTagName('A');
            if(!a || a.length==0) { LinkClassifier.forceCallChainedFunc( ); return; }

            var self= LinkClassifier.getPageName(location.href);
            var self2=wgPageName.replace(/_/g,' ');
            a=Array.prototype.map.call(a, function(a){
                a.wikipage='';
                if(/(^|\s)(external|extiw)(\s|$)/.test(a.className)) return '';
                if(!/(^|\s)(image)(\s|$)/.test(a.className)) a.className+=" nonimage";
                a.wikipage=LinkClassifier.getPageName(a.href);
                if(a.wikipage==self || a.wikipage==self2) a.wikipage='';
                a.origwikipage=a.wikipage;
                return a.wikipage;
            }).sort().filter(function(e,i,a){
                return e!=='' && (i==0 || a[i-1]!==e);
            });

            function processLinksWithSlowdown(limit){
                var props = [];
                for ( var k in LinkClassifier.props ) {
                        props.push( k );
                }
                if ( a.length == 0 ) {
                    // This is where we detect linklessness for completely empty pages (0byte wikitext).
                    // This isn't a fatal error, so call the chained function in addition to removing the safety timeout
                    LinkClassifier.forceCallChainedFunc( );
                }

                function sendABatch( za, zlimit, zprops ) {
                    if ( za.length == 0 ) {
                        LinkClassifier.forceCallChainedFunc( );
                        return;
                    }

                    var nexta = za;
                    var curra = nexta.splice(0,zlimit);

                    LinkClassifierApiRequestsOutstanding++;
                    var q={
                        format:'json',
                        action:'query',
                        titles:curra.join('|'),
                        prop:'categories|pageprop|info|flagged',
                        redirects:1,
                        cllimit:'max',
                        inprop:'protection',
                        rawcontinue:1
                    };
                    if ( zprops.length <= zlimit ) {
                        q.ppprop = props.join( '|' );
                    }
                    $.ajax({
                        url:mw.util.wikiScript('api'),
                        dataType:'json',
                        type:'POST',
                        data:q,
                        rawdata:q,
                        success:LinkClassifier.callback,
                        error:function(xhr,textStatus,errorThrown){
                            LinkClassifier.removeUnloadHandlerIfNeeded( );

                            if ( typeof window.LinkClassifierStatusSel == 'string' ) {
                                var statusElement = $( window.LinkClassifierStatusSel );
                                statusElement.removeClass( 'linkclassifier-started' );
                                statusElement.addClass(    'linkclassifier-aborted' );
                            }

                            var errTxt = 'AJAX error (pLWS), with '
                                       + LinkClassifierApiRequestsOutstanding + ' requests in progress: '+textStatus+' '+errorThrown;
                            mw.util.jsMessage( window.LinkClassifierErrMsgPrefix + errTxt );
                            throw new Error( errTxt );
                        },
                        complete:LinkClassifier.maybeCallChainedFunc
                    });
                    setTimeout( sendABatch, window.LinkClassifierSlowDown, nexta, zlimit, zprops );
                }
                sendABatch( a, limit, props );
            }

            function processLinksWithoutSlowdown(limit){
                var props = [];
                for ( var k in LinkClassifier.props ) {
                        props.push( k );
                }
                if ( a.length == 0 ) {
                    // This is where we detect linklessness for completely empty pages (0byte wikitext).
                    // This isn't a fatal error, so call the chained function in addition to removing the safety timeout
                    LinkClassifier.forceCallChainedFunc( );
                }
                while(a.length>0){
                    LinkClassifierApiRequestsOutstanding++;
                    var q={
                        format:'json',
                        action:'query',
                        titles:a.splice(0,limit).join('|'),
                        prop:'categories|pageprop|info|flagged',
                        redirects:1,
                        cllimit:'max',
                        inprop:'protection',
                        rawcontinue:1
                    };
                    if ( props.length <= limit ) {
                        q.ppprop = props.join( '|' );
                    }
                    $.ajax({
                        url:mw.util.wikiScript('api'),
                        dataType:'json',
                        type:'POST',
                        data:q,
                        rawdata:q,
                        success:LinkClassifier.callback,
                        error:function(xhr,textStatus,errorThrown){
                            LinkClassifier.removeUnloadHandlerIfNeeded( );

                            if ( typeof window.LinkClassifierStatusSel == 'string' ) {
                                var statusElement = $( window.LinkClassifierStatusSel );
                                statusElement.removeClass( 'linkclassifier-started' );
                                statusElement.addClass(    'linkclassifier-aborted' );
                            }

                            var errTxt = 'AJAX error (pLWOS), with '
                                       + LinkClassifierApiRequestsOutstanding + ' requests in progress: '+textStatus+' '+errorThrown;
                            mw.util.jsMessage( window.LinkClassifierErrMsgPrefix + errTxt );
                            throw new Error( errTxt );
                        },
                        complete:LinkClassifier.maybeCallChainedFunc
                    });
                }
            }

            function processLinks(limit){
                if ( typeof window.LinkClassifierSlowDown == 'number'
                         && window.LinkClassifierSlowDown == Math.ceil( window.LinkClassifierSlowDown )
                         && window.LinkClassifierSlowDown > 0 ) {
                    processLinksWithSlowdown( limit );
                }
                else {
                    processLinksWithoutSlowdown( limit );
                }
            }

            if( (window.LinkClassifierNoApihighlimits === true) || a.length<=100){
                // Not worth querying the API to see if the user has apihighlimits
                // (or user has manually declared that they lack the right)
                processLinks(50);
            } else {
                // Note mw.user.getRights queries the API
                mw.user.getRights(function(rights){
                    processLinks( (rights.indexOf('apihighlimits')>=0) ? 500 : 50 );
                });
            }
        });
    },
 
    onLoad:function(){
        if(window.LinkClassifierOnDemand === true) return;
        if(window.AJAXPreview) window.AJAXPreview.AddOnLoadHook(LinkClassifier.classifyChildren);
        LinkClassifier.onDemand();
    },
 
    onDemand:function(){
        LinkClassifierChainedFuncCalledAlready = false; // belongs at the top of classifyChildren or perhaps processLinks?
        LinkClassifierApiRequestsOutstanding = 0;

        var node=document.getElementById('wikiPreview');
        if(!node) node=document.getElementById('bodyContent');
        if(node) {
            LinkClassifier.initUnloadHandlerIfNeeded();
            LinkClassifier.classifyChildren(node);
        }
        if ( window.LinkClassifierOnDemand === true ) {
            var myPortletLink = $('#ca-linkclassifier');
            if ( myPortletLink ) myPortletLink.remove( );
        }
    }
};

$(document).ready(function(){
    if( window.LinkClassifierOnDemand === true ) {
        mw.util.addPortletLink(defaultPref( window.LinkClassifierOnDemandLoc, 'p-cactions' ), 'javascript:LinkClassifier.onDemand()', defaultPref( window.LinkClassifierOnDemandText, 'linkclassifier' ), 'ca-linkclassifier');
    }
    else {
        LinkClassifier.onLoad( );
    }
});