Movatterモバイル変換


[0]ホーム

URL:


Jump to content
Wikimedia Commons
Search

MediaWiki:Gadget-libGlobalReplace.js

From Wikimedia Commons, the free media repository
Note: After saving, you have tobypass your browser's cache to see the changes.Internet Explorer: pressCtrl-F5,Mozilla: hold downShift while clickingReload (or pressCtrl-Shift-R),Opera/Konqueror: pressF5,Safari: hold downShift + Alt while clickingReload,Chrome: hold downShift while clickingReload.
Thisuser script seems to have a documentation page atMediaWiki:Gadget-libGlobalReplace.
/*** [[MediaWiki:Gadget-libGlobalReplace.js]]* Replaces a file on all wikis, including Wikimedia Commons* Uses either CORS under the current user account* or deputes the task to CommonsDelinker** The method used is determined by* -Browser capabilities (CORS required)* -The usage count: More than the given number*                   aren't attempted to be replaced*                   under the user account** It adds only one public method to the mw.libs - object:* @example*      var $jQuery_Deferred_Object;*      $jQuery_Deferred_Object = mw.libs.globalReplace(oldFile, newFile, shortReason, fullReason);*      $jQuery_Deferred_Object.done(function() { alert("Good news! " + oldFile + " has been replaced by " + newFile + "!") });** Internal stuff:* Since we don't use instances of classes, we have to pass around all the parameters** TODO: I18n (progress messages) when Krinkle is ready with Gadgets 2.0 :-)** @rev 1 (2012-11-26)* @rev 5 (2017-12-15)* @rev 6 (2019-09-21)* @author Rillke – 2012–2015, Perhelion 2017–2019* <nowiki>*/// List the global variables for jsHint-Validation. Please make sure that it passes http://jshint.com/// Scheme: globalVariable:allowOverwriting[, globalVariable:allowOverwriting][, globalVariable:allowOverwriting]/* eslint indent:[error,tab,{outerIIFEBody:0}] */// Set jsHint-options. You should not set forin or undef to false if your script does not validate./* jshint forin:true, noarg:true, noempty:true, eqeqeq:true, bitwise:true, undef:true, curly:false, browser:true*//* global jQuery:false, mediaWiki:false*/(function($,mw){'use strict';// Config// When this number is exceeded or reached, use CommonsDelinker// This number must not be higher than 50// (can't query more than 50 titles at once)varusageThreshold=45,// Internal stuffCORSsupported=false;/*** TODO: Outsource to library as I often use them OR does jQuery provide something like that?**/if(!Object.keys){Object.keys=function(o){vark=[],p;for(pino){if(Object.prototype.hasOwnProperty.call(o,p)){k.push(p);}}returnk;};}var_firstItem=function(o){returno[Object.keys(o)[0]];},// TODO: Keep in sync with CommonsDelinker source:// https://bitbucket.org/magnusmanske/commons-delinquent/src/master/demon.phpgetFileRegEx=function(title,prefix){prefix=prefix||'[\\n\\[\\:\\=\\>\\|]\\s*';returnnewRegExp('('+prefix+')['+mw.util.escapeRegExp(title[0].toUpperCase()+title[0].toLowerCase())+']'+mw.util.escapeRegExp(title.slice(1)).replace(/ /g,'[ _]'),'g');},queryGET=function(params,cb,errCb){mw.loader.using(['ext.gadget.libAPI'],function(){params.action=params.action||'query';mw.libs.commons.api.query(params,{cache:true,cb:cb,errCb:errCb});});},centralToken={},edittoken=mw.user.tokens.get('csrfToken'),fbToken,// fallbackfetchingFbToken;functiondoCORSreq(params,wiki,cb,errCb,method){varapi=newmw.ForeignApi('//'+wiki+mw.util.wikiScript('api'));method=method==='POST'?'post':'get';api[method](params).done(function(r){cb(r,wiki);}).fail(function(r){mw.log.warn('API FAIL:',JSON.stringify(arguments),r,params);errCb(r,wiki);});}vargetFbToken=function(cb,wiki){if(fbToken){returncb(fbToken);}if(fetchingFbToken){return;}varpara={meta:'tokens'},h=mw.hook('commons.libglobalreplace.fbToken.fetched').add(cb),errCb=function(/* r */){centralToken.centralauthtoken=0;fbToken='+\\';mw.notify('Error fetching csrftoken from Wikidata. ');};fetchingFbToken=true;if(centralToken.centralauthtoken){para.centralauthtoken=centralToken.centralauthtoken;centralToken.centralauthtoken=0;}if(!para.centralauthtoken){CORSsupported=false;// Test logged-inreturntestCORS(function(r){// If the user is suddenly reported to be logged-out try again.if(CORSsupported!=='OK'){returngetCentralAuth(cb,errCb,wiki);}fbToken=r.query.tokens.csrftoken;h.fire(fbToken);},wiki);}doCORSreq(para,wiki,function(r){fbToken=r.query.tokens.csrftoken;getCentralAuth(cb,errCb,wiki);// Need new authtoken},errCb);};functionqueryCentralToken(token,cb,wiki){if(token.centralauthtoken){centralToken=token.centralauthtoken;}getFbToken(cb,wiki);}functiongetCentralAuth(cb,errCb,wiki){newmw.Api().get({action:'centralauthtoken'}).done(function(r){fetchingFbToken=false;queryCentralToken(r,cb,wiki);}).fail(errCb);}functiontestCORS(done,wiki){mw.loader.using(['mediawiki.user','mediawiki.api','mediawiki.ForeignApi']).done(function(){if(CORSsupported){returndone();}doCORSreq({meta:'tokens|userinfo'},wiki||'www.mediawiki.org',function(data,textStatus){if(!data.query||!data.query.userinfo.id){CORSsupported='CORS supported but not logged-in';mw.log(CORSsupported,data,textStatus);}else{CORSsupported='OK';}done(data);},function(jqXHR,textStatus,errorThrown){CORSsupported='CORS not supported: '+textStatus+'\nError: '+errorThrown;done();});});}varupdateReplaceStatus=function($prog){/* If we are using CommonsDelinker (CD), it will mark this progress object* as resolved as soon as the requst was placed in the queue;* Don't know whether we should stop replacement under user account* when we request CD to do our job; but see no pressing need to */if(!$prog.remaining&&!$prog.usingCD){$prog.resolve('All usages replaced');// Kill the timer: Everything worked in time!if($prog.CDtimeout){clearTimeout($prog.CDtimeout);}}$prog.notify('Replacing usage: '+Math.round(($prog.total-$prog.remaining)*100/$prog.total)+'% ('+($prog.total-$prog.remaining)+'/'+$prog.total+')\nDo not close this window until the task is completed.');},decrementAndUpdate=function($prog){$prog.remaining--;updateReplaceStatus($prog);},incrementAndUpdate=function($prog){$prog.remaining++;updateReplaceStatus($prog);},checkPage=function($prog,pg,wiki,cb){if(!pg.revisions){$prog.notify('No page text for '+pg.title+' – '+wiki+' – private wiki or out of date?');if(typeofcb==='function'){cb();}returnfalse;}else{returntrue;}},compareTexts=function($prog,oldT,newT,title,wiki){if(oldT===newT){$prog.notify('No changes at '+title+' – '+wiki+' – template use?');decrementAndUpdate($prog);returnfalse;}else{returntrue;}};functionnoUnlinkFromNamespace(pg,$prog){return(pg.ns%2)||// Skip talk pages(pg.ns<0)||// Paranoia($prog.notOnNs&&$prog.notOnNs.indexOf(pg.ns)>=0);// Skip optional namespaces}/***  Asks CommonsDelinker to replace a file.**/varcommonsDelinker=function(of,nf,sr,fr,$prog){// Don't ask CommonsDelinker multiple times to replace the same fileif($prog.usingCD){return;}if($prog.dontUseCD){return$prog.reject('Unable replacing all usages. Usually CD would now have been instructed but you wished not to do so.');}// Tell other processes that we're now using the delinker// So they don't stop us by resolving the progress$prog.usingCD=true;mw.libs.globalReplaceDelinker(of,nf,sr+' '+fr,function(){$prog.resolve('CommonsDelinker has been instructed to replace '+of+' with '+nf);},function(t){$prog.reject('Error while asking CommonsDelinker to replace '+of+' with '+nf+' Reason: '+t);});},/***  Replace usage at Wikimedia Commons.**/localReplace=function(re,localUsage,of,nf,sr,fr,$prog){functionisBadPage(pg){return(pg.ns===6&&[of,nf].indexOf(pg.title.replace(/^File:/,''))!==-1)||// Self-reference(pg.ns===2&&/^User:\w+Bot\b/.test(pg.title))||// Bot subpage on Commons(pg.ns===4&&/(Deletion[_ ]requests\/[^\n]*|Undeletion[_ ]requests\/[^\n]*)\b/.test(pg.title));// DR and UDR on Commons}$.each(localUsage,function(id,pg){// Check page existsif(!checkPage($prog,pg,'Commons')||isBadPage(pg)||noUnlinkFromNamespace(pg,$prog)){decrementAndUpdate($prog);returnmw.log('LocalReplace skipped for',pg.title);}varisEditable=pg.actions.edit.length===0,summary=sr+' [[File:'+of+']] → [[File:'+nf+']] '+fr,edit;if(isEditable){varoldText=pg.revisions[0]['*'],nwe1=mw.libs.wikiDOM.nowikiEscaper(pg.revisions[0]['*']),newText=nwe1.secureReplace(re,'$1'+nf).getText();if(!compareTexts($prog,oldText,newText,pg.title,'Commons')){return;}edit={cb:function(){decrementAndUpdate($prog);},errCb:function(){decrementAndUpdate($prog);$prog.notify('Unable to update '+pg.title+' \nUsing CommonsDelinker');commonsDelinker(of,nf,sr,fr,$prog);},title:pg.title,text:newText,editType:'text',watchlist:'nochange',minor:true,summary:summary,basetimestamp:pg.revisions[0].timestamp};}else{// If page is protected, post a request to the talk pageedit={cb:function(){decrementAndUpdate($prog);},errCb:function(){decrementAndUpdate($prog);},title:mw.libs.commons.getTalkPageFromTitle(pg.title),text:'\n== Please replace [[:File:'+of+']] ==\n{{edit request}}\nThis page is protected while posting this message. Please replace <code>[[:File:'+of+']]</code> with <code>[[:File:'+nf+']]</code> because '+sr+' '+fr+'\nThank you. <small>Message added by [[:c:GR|global replace]]</small> -- ~~~~',editType:'appendtext',watchlist:'nochange',minor:true,summary:summary};}mw.loader.using(['ext.gadget.libAPI','mediawiki.user'],function(){if(!mw.user.isAnon()){edit.assert='user';}mw.libs.commons.api.editPage(edit);});});},sanitizeFileName=function(fn){returnfn.replace(/_/g,' ').trim().replace(/^(?:File|Image):/i,'');},/*** Replace usage in other wikis. It's not uncommon that edits fail due to* title blacklist, abuse filter, captcha, server timeouts, protected pages* etc. but in this case we kindly ask CommonsDelinker whether it will do* the remaining ones for us.** @param  {RegExp}   re           File RegExp object* @param  {Array}    globalUsage  The global usage* @param  {string}   of           Old file name. The old file name will be replaced with the new file name.* @param  {string}   nf           New file name.* @param  {string}   sr           Short reason like "file renamed". Will be prefixed to the edit summary.* @param  {string}   fr           Full reason like "file renamed because it was offending". Will be appended to the edit summary.* @param  {Object}   $prog        Deferred (factory function) object reflecting the current progress.* @return {boolean}*/globalReplace=function(re,globalUsage,of,nf,sr,fr,$prog){varguWiki={},queries=[],chunks=[],summary='([[c:GR|GR]]) '+sr.replace(/\[\[(.+)\]\]/,'[[c:$1]]')+' [[File:'+of+']] → [[File:'+nf+']] '+fr.replace(/\[\[(.+?)\]\]/g,'[[c:$1]]'),edit={action:'edit',summary:summary,minor:true,nocreate:true,watchlist:'nochange'},wdEdit={action:'wbsetclaimvalue',snaktype:'value',summary:summary},setQuery=function(wiki){window.setTimeout(function(){if(wiki&&chunks.length){runReplacements(wiki);}else{checkLocalFiles();}},10);};functiongetPageContentsFailed(err,wiki,text){err+=err?' \n':' ';$prog.notify((text||'Unable to get information from ')+wiki+err+'\nUsing CommonsDelinker');decrementAndUpdate($prog);commonsDelinker(of,nf,sr,fr,$prog);returnfalse;}// First we have to compile a list of pages per Wiki$.each(globalUsage,function(i,gu){varpg=gu.title,wiki=gu.wiki;// Exclude before do queryif(noUnlinkFromNamespace(gu,$prog)){decrementAndUpdate($prog);return;}if(wikiinguWiki){// Templates firstguWiki[wiki][(gu.ns==='10'?'unshift':'push')](pg);}else{guWiki[wiki]=[pg];}});vargotPagesContents=function(result,wiki){varpages=result.query.pages,pagelist=Object.keys(pages),setEdit=function(){window.setTimeout(function(){if(pagelist.length){performEdit(pages[pagelist.shift()]);}else{setQuery(wiki);}},30);},_onErr=function(r){setEdit();getPageContentsFailed('',wiki,JSON.stringify(r)+' Unable to update page at ');},editNow=function(edit){if(!mw.user.isAnon()){edit.assert='user';}doCORSreq(edit,wiki,function(r){mw.log('editNow',r);if(r.error||(r.edit&&(r.edit.spamblacklist||r.edit.result!=='Success'))){// ERROR_onErr(r);}else{// SUCCESSdecrementAndUpdate($prog);setEdit();}},_onErr,'POST');};$prog.notify('Got page contents for '+wiki+'. Updating them now.');edittoken=result.query.tokens.csrftoken;// TODO: Work around protectionfunctionperformEdit(pg){if(!checkPage($prog,pg,wiki,function(){// Perhaps it's a private wiki and CommonsDelinker has access?commonsDelinker(of,nf,sr,fr,$prog);})){decrementAndUpdate($prog);returnsetEdit();}varreplacementCount=0,newText,oldText=pg.revisions[0]['*'];if(wiki==='www.wikidata.org'&&pg.contentmodel==='wikibase-item'){try{newText=JSON.parse(oldText);$.each(newText.claims,function(propId,propClaims){$.each(propClaims,function(idx,claim){if(claim.type!=='statement'||!claim.mainsnak||!claim.mainsnak.datavalue||typeofclaim.mainsnak.datavalue.value!=='string'){returnsetEdit();}if(sanitizeFileName(claim.mainsnak.datavalue.value)===sanitizeFileName(of)){replacementCount++;if(replacementCount>1){incrementAndUpdate($prog);}getFbToken(function(token){$.extend(wdEdit,{claim:claim.id,baserevid:pg.lastrevid,value:JSON.stringify(nf),token:token});if(centralToken.centralauthtoken){wdEdit.centralauthtoken=centralToken.centralauthtoken;centralToken.centralauthtoken=0;}editNow(wdEdit);},'www.wikidata.org');}});});if(!replacementCount){setEdit();returngetPageContentsFailed('',wiki,'Nothing suitable for replacement found on '+pg.title+' on ');}}catch(noMatterWhat){setEdit();returngetPageContentsFailed('',wiki,noMatterWhat+' Issue replacing usage on entry '+pg.title+' on ');}}else{vareditNowCB=function(token){if(!token||/^\+\\+$/.test(token)){setEdit();returngetPageContentsFailed('',wiki,'No token for ');}newText=mw.libs.wikiDOM.nowikiEscaper(oldText).secureReplace(re,'$1'+nf).getText();if(!compareTexts($prog,oldText,newText,pg.title,wiki)){returnsetEdit();}$.extend(edit,{title:pg.title,starttimestamp:result.curtimestamp,basetimestamp:pg.revisions[0].timestamp,text:newText,token:token});if(centralToken.centralauthtoken){edit.centralauthtoken=centralToken.centralauthtoken;centralToken.centralauthtoken=0;}editNow(edit);};if(!edittoken||/^\+\\+$/.test(edittoken)){// Try get fallback tokenreturngetFbToken(editNowCB,wiki);}editNowCB(edittoken);}}setEdit();};functionrunReplacements(wiki){vartitles=chunks.shift();if(!titles){returncheckLocalFiles();}doCORSreq({prop:'info|revisions',curtimestamp:1,meta:'tokens',rvprop:'content|timestamp',titles:titles.join('|').replace(/_/g,' ')},wiki,gotPagesContents,function(r,wiki){getPageContentsFailed(wiki,titles);setQuery(wiki);});}functioncheckLocalFiles(/* wiki , titles*/){varwiki=queries.shift();if(!wiki){return;}// finishvartitles=guWiki[wiki];// Now, it's possible that the wiki has a local file with the new name,// a so-called "shadow".// In this case the replacement is most likely undesired.// Convert the edits in chunkschunks=(function(arr,cSize){varc=[];while(arr.length){c.push(arr.splice(0,cSize));}returnc;}(titles,usageThreshold));// Test shadow copydoCORSreq({list:'allimages',aifrom:nf,aito:nf},wiki,function(r){if(r&&r.query&&r.query.allimages&&r.query.allimages.length){// Skip this wiki$prog.notify('Skipping '+wiki+' because there is a shadow file with the same target name.');$prog.remaining-=titles.length;updateReplaceStatus($prog);checkLocalFiles();}else{runReplacements(wiki);}},function(r,wiki){runReplacements(wiki);});}// Then send out the queries to the Wikis// First Wikidataif('www.wikidata.org'inguWiki){chunks.push(guWiki['www.wikidata.org']);runReplacements('www.wikidata.org');deleteguWiki['www.wikidata.org'];queries=Object.keys(guWiki);}else{queries=Object.keys(guWiki);checkLocalFiles();// async}// $.each(guWiki, checkLocalFiles); // sync},uGroups=mw.config.get('wgUserGroups'),/*** @param {string} of Old file name. The old file name will be replaced with the new file name.* @param {string} nf New file name.* @param {string} sr Short reason like "file renamed". Will be prefixed to the edit summary.* @param {string} fr Full reason like "file renamed because it was offending". Will be appended to the edit summary.* @param {$.Deferred} $prog Deferred object reflecting the current progress.**/replace=function(of,nf,sr,fr,$prog){of=sanitizeFileName(of);nf=sanitizeFileName(nf);varpending=0,localResult,globalUsage=[],globalResult,sysop=uGroups.indexOf('sysop')!==-1,_getGlobalQuery=function(gucontinue){queryGET({prop:'globalusage',guprop:'namespace',gulimit:sysop?250:usageThreshold,gufilterlocal:1,gucontinue:gucontinue||'||',titles:'File:'+of},_queryGlobal);},_selectMethod=function(){globalUsage=globalUsage.concat(_firstItem(globalResult.query.pages).globalusage);varglobalUsageCount=globalUsage.length,localUsage=localResult.query?localResult.query.pages:{},usageCount=Object.keys(localUsage).length+globalUsageCount;$prog.remaining=usageCount;$prog.total=usageCount;mw.log(CORSsupported);if(!usageCount){$prog.resolve('File was not in use. Nothing replaced.');}elseif((usageCount>=usageThreshold||(CORSsupported!=='OK'&&globalUsageCount))&&!$prog.dontUseCD){$prog.notify('Instructing CommonsDelinker to replace this file');commonsDelinker(of,nf,sr,fr,$prog);}else{if(usageCount-globalUsageCount){localReplace(getFileRegEx(of,'(?:[\\n\\[\\=\\>\\|]|[\\n\\[\\=\\>\\|][Ff]ile\\:)\\s*'),localUsage,of,nf,sr,fr,$prog);}if(globalUsageCount){if('continue'inglobalResult){// eslint-disable-next-line dot-notationreturn_getGlobalQuery(globalResult['continue'].gucontinue);}globalReplace(getFileRegEx(of),globalUsage,of,nf,sr,fr,$prog);}$prog.notify('Replacing usage immediately using your user account. Do not close this window until the process is complete.');}// Finally, set a timeout that will instruct CommonsDelinker if it takes too long$prog.CDtimeout=setTimeout(function(){commonsDelinker(of,nf,sr,fr,$prog);},50000);},_queryLocal=function(result){pending--;if(result){localResult=result;}if(pending>0){return;}_selectMethod();},_queryGlobal=function(result){pending--;if(result){globalResult=result;}if(pending>0){return;}_selectMethod();};$prog.notify('Query usage and selecting replace-method');pending++;queryGET({generator:'imageusage',giufilterredir:'nonredirects',giulimit:sysop?250:usageThreshold,prop:'info|revisions',intestactions:'edit',intestactionsdetail:'full',errorformat:'none',rvprop:'content|timestamp',giuredirect:1,giutitle:'File:'+of},_queryLocal);pending++;_getGlobalQuery();pending++;testCORS(function(){pending--;if(pending>0){return;}_selectMethod();});};// Expose globally/*** @param {string} oldFile Old file name. The old file name will be replaced with the new file name.*                     Can be in any format (both "File:Abc def.png" and "Abc_def.png" work)* @param {string} newFile New file name.*                     Can be in any format (both "File:Abc def.png" and "Abc_def.png" work)* @param {string} shortReason Short reason like "file renamed". Will be prefixed to the edit summary.* @param {string} fullReason Full reason like "file renamed because it was offending". Will be appended to the edit summary.* @param {boolean} dontUseDelinker Prevents usage of CommonsDelinker (only provided for debugging/scripting)* @param {Array} notOnNamespaces Skip optional namespacenumbers* @return {$.Deferred} $prog jQuery deferred-object reflecting the current progress. See http://api.jquery.com/category/deferred-object/ for more info.* @examle See this gadget's introduction.**/mw.libs.globalReplace=function(oldFile,newFile,shortReason,fullReason,dontUseDelinker,notOnNamespaces){var$progress=$.Deferred();$progress.pendingQueries=0;$progress.dontUseCD=dontUseDelinker;$progress.notOnNs=Array.isArray(notOnNamespaces)?notOnNamespaces:false;varargs=Array.prototype.slice.call(arguments,0);// Delete optional dontUseDelinker and notOnNamespacesif(args.length>4){args.splice(4);}// Add progressargs.push($progress);replace.apply(this,args);return$progress;};mw.libs.globalReplaceDelinker=function(oldFile,newFile,reason,cb,errCb){oldFile=sanitizeFileName(oldFile);newFile=sanitizeFileName(newFile);reason=reason.replace(/\{/g,'&#123;').replace(/\}/g,'&#125;').replace(/=/g,'&#61;');varedit={cb:cb,errCb:errCb,title:'User:CommonsDelinker/commands',text:'\n{{universal replace|'+oldFile+'|'+newFile+'|reason='+reason+'}}',editType:'appendtext',watchlist:'nochange',summary:'universal replace: [[File:'+oldFile+']] → [[File:'+newFile+']]'};if(mw.config.get('wgUserGroups').indexOf('sysop')===-1){edit.title='User:CommonsDelinker/commands/filemovers';}mw.loader.using('ext.gadget.libAPI',function(){mw.libs.commons.api.editPage(edit);});};}(jQuery,mediaWiki));// </nowiki>
Retrieved from "https://commons.wikimedia.org/w/index.php?title=MediaWiki:Gadget-libGlobalReplace.js&oldid=1118108625"

[8]ページ先頭

©2009-2025 Movatter.jp