MediaWiki:Gadget-twinklebatchdelete.js: Difference between revisions

From Sarkarverse
Jump to navigation Jump to search
m (1 revision imported)
 
(Localise)
 
(4 intermediate revisions by 2 users not shown)
Line 10: Line 10:
  ****************************************
  ****************************************
  * Mode of invocation:    Tab ("D-batch")
  * Mode of invocation:    Tab ("D-batch")
  * Active on:              Existing and non-existing non-articles, and Special:PrefixIndex
  * Active on:              Existing non-articles, and Special:PrefixIndex
  * Config directives in:  TwinkleConfig
  * Config directives in:  TwinkleConfig
  */
  */


Twinkle.batchdelete = function twinklebatchdelete() {
Twinkle.batchdelete = function twinklebatchdelete() {
if( Morebits.userIsInGroup( 'sysop' ) && (mw.config.get( 'wgNamespaceNumber' ) > 0 || mw.config.get( 'wgCanonicalSpecialPageName' ) === 'Prefixindex') ) {
if(
Morebits.userIsInGroup( 'sysop' ) && (
( mw.config.get( 'wgCurRevisionId' ) && mw.config.get( 'wgNamespaceNumber' ) > 0 ) ||
mw.config.get( 'wgCanonicalSpecialPageName' ) === 'Prefixindex'
)
) {
Twinkle.addPortletLink( Twinkle.batchdelete.callback, "D-batch", "tw-batch", "Delete pages found in this category/on this page" );
Twinkle.addPortletLink( Twinkle.batchdelete.callback, "D-batch", "tw-batch", "Delete pages found in this category/on this page" );
}
}
Line 26: Line 30:
Window.setTitle( "Batch deletion" );
Window.setTitle( "Batch deletion" );
Window.setScriptName( "Twinkle" );
Window.setScriptName( "Twinkle" );
Window.addFooterLink( "Twinkle help", "WP:TW/DOC#batchdelete" );
Window.addFooterLink( "Twinkle help", "SV:TW/DOC#batchdelete" );


var form = new Morebits.quickForm( Twinkle.batchdelete.callback.evaluate );
var form = new Morebits.quickForm( Twinkle.batchdelete.callback.evaluate );
Line 36: Line 40:
name: 'delete_page',
name: 'delete_page',
value: 'delete',
value: 'delete',
checked: true
checked: true,
subgroup: {
type: 'checkbox',
list: [
{
label: 'Delete associated talk pages (except user talk pages)',
name: 'delete_talk',
value: 'delete_talk',
checked: true
},
{
label: 'Delete redirects to deleted pages',
name: 'delete_redirects',
value: 'delete_redirects',
checked: true
}
]
}
},
},
{
{
label: 'Remove backlinks to the page',
label: 'Unlink backlinks to each page (in Main and Portal namespaces only)',
name: 'unlink_page',
name: 'unlink_page',
value: 'unlink',
value: 'unlink',
checked: true
checked: false
},
},
{
{
label: 'Delete redirects to deleted pages',
label: 'Remove usages of each file (in all namespaces)',
name: 'delete_redirects',
name: 'unlink_file',
value: 'delete_redirects',
value: 'unlink_file',
checked: true
checked: true
}
}
Line 59: Line 80:
} );
} );


var query;
var query = {
'action': 'query',
'prop': 'revisions|info|imageinfo',
'inprop': 'protection',
'rvprop': 'size|user'
};
if( mw.config.get( 'wgNamespaceNumber' ) === 14 ) {  // Category:
if( mw.config.get( 'wgNamespaceNumber' ) === 14 ) {  // Category:
 
query.generator = 'categorymembers';
query = {
query.gcmtitle = mw.config.get('wgPageName');
'action': 'query',
query.gcmlimit = Twinkle.getPref('batchMax'); // the max for sysops
'generator': 'categorymembers',
'gcmtitle': mw.config.get( 'wgPageName' ),
'gcmlimit' : Twinkle.getPref('batchMax'), // the max for sysops
'prop': [ 'categories', 'revisions' ],
'rvprop': [ 'size' ]
};
} else if( mw.config.get( 'wgCanonicalSpecialPageName' ) === 'Prefixindex' ) {
} else if( mw.config.get( 'wgCanonicalSpecialPageName' ) === 'Prefixindex' ) {


var gapnamespace, gapprefix;
query.generator = 'allpages';
query.gaplimit = Twinkle.getPref('batchMax'); // the max for sysops
if(Morebits.queryString.exists( 'prefix' ) )
if(Morebits.queryString.exists( 'prefix' ) )
{
{
gapnamespace = Morebits.queryString.get( 'namespace' );
query.gapnamespace = Morebits.queryString.get( 'namespace' );
gapprefix = Morebits.string.toUpperCaseFirstChar( Morebits.queryString.get( 'prefix' ) );
query.gapprefix = Morebits.string.toUpperCaseFirstChar( Morebits.queryString.get( 'prefix' ) );
}
}
else
else
Line 85: Line 106:
}
}
var titleSplit = pathSplit[3].split(':');
var titleSplit = pathSplit[3].split(':');
gapnamespace = mw.config.get("wgNamespaceIds")[titleSplit[0].toLowerCase()];
query.gapnamespace = mw.config.get("wgNamespaceIds")[titleSplit[0].toLowerCase()];
if ( titleSplit.length < 2 || typeof gapnamespace === 'undefined' )
if ( titleSplit.length < 2 || typeof query.gapnamespace === 'undefined' )
{
{
gapnamespace = 0;  // article namespace
query.gapnamespace = 0;  // article namespace
gapprefix = pathSplit.splice(3).join('/');
query.gapprefix = pathSplit.splice(3).join('/');
}
}
else
else
Line 95: Line 116:
pathSplit = pathSplit.splice(4);
pathSplit = pathSplit.splice(4);
pathSplit.splice(0,0,titleSplit.splice(1).join(':'));
pathSplit.splice(0,0,titleSplit.splice(1).join(':'));
gapprefix = pathSplit.join('/');
query.gapprefix = pathSplit.join('/');
}
}
}
}
query = {
'action': 'query',
'generator': 'allpages',
'gapnamespace': gapnamespace ,
'gapprefix': gapprefix,
'gaplimit' : Twinkle.getPref('batchMax'), // the max for sysops
'prop' : 'revisions|info',
'inprop': 'protection',
'rvprop': 'size'
};
} else {
} else {
query = {
query.generator = 'links';
'action': 'query',
query.titles = mw.config.get('wgPageName');
'generator': 'links',
query.gpllimit = Twinkle.getPref('batchMax'); // the max for sysops
'titles': mw.config.get( 'wgPageName' ),
'gpllimit' : Twinkle.getPref('batchMax'), // the max for sysops
'prop': 'revisions|info',
'inprop': 'protection',
'rvprop': 'size'
};
}
}


Line 130: Line 134:
var wikipedia_api = new Morebits.wiki.api( 'loading...', query, function( apiobj ) {
var wikipedia_api = new Morebits.wiki.api( 'loading...', query, function( apiobj ) {
var xml = apiobj.responseXML;
var xml = apiobj.responseXML;
var $pages = $(xml).find('page').filter(':not([missing])');
var $pages = $(xml).find('page').filter(':not([missing])'); // :not([imagerepository="shared"])
var list = [];
var list = [];
$pages.each(function(index, page) {
$pages.each(function(index, page) {
var $page = $(page);
var $page = $(page);
var ns = $page.attr('ns');
var title = $page.attr('title');
var title = $page.attr('title');
var isRedir = $page.attr('redirect') === "";
var isRedir = $page.attr('redirect') === "";
var $editprot = $page.find('pr[type="edit"][level="sysop"]');
var $editprot = $page.find('pr[type="edit"][level="sysop"]');
var protected = $editprot.length > 0;
var isProtected = $editprot.length > 0;
var size = $page.find('rev').attr('size');
var size = $page.find('rev').attr('size');


Line 144: Line 149:
metadata.push("redirect");
metadata.push("redirect");
}
}
if (protected) {
if (isProtected) {
metadata.push("fully protected" +  
metadata.push("fully protected" +
($editprot.attr('expiry') === 'infinity' ? ' indefinitely' : (', expires ' + $editprot.attr('expiry'))));
($editprot.attr('expiry') === 'infinity' ? ' indefinitely' : (', expires ' + $editprot.attr('expiry'))));
}
}
metadata.push(size + " bytes");
if (ns === "6") {  // mimic what delimages used to show for files
metadata.push("uploader: " + $page.find('ii').attr('user'));
metadata.push("last edit from: " + $page.find('rev').attr('user'));
} else {
metadata.push(size + " bytes");
}
list.push({
list.push({
label: title + (metadata.length ? (' (' + metadata.join('; ') + ')') : ''),
label: title + (metadata.length ? (' (' + metadata.join('; ') + ')') : ''),
value: title,
value: title,
checked: true,
checked: true,
style: (protected ? 'color:red' : '')
style: (isProtected ? 'color:red' : '')
});
});
});
});
Line 189: Line 199:
};
};


Twinkle.batchdelete.currentDeleteCounter = 0;
Twinkle.batchdelete.currentUnlinkCounter = 0;
Twinkle.batchdelete.currentdeletor = 0;
Twinkle.batchdelete.callback.evaluate = function twinklebatchdeleteCallbackEvaluate(event) {
Twinkle.batchdelete.callback.evaluate = function twinklebatchdeleteCallbackEvaluate(event) {
Morebits.wiki.actionCompleted.notice = 'Status';
Morebits.wiki.actionCompleted.notice = 'Status';
Line 206: Line 213:
var reason = event.target.reason.value;
var reason = event.target.reason.value;
var delete_page = event.target.delete_page.checked;
var delete_page = event.target.delete_page.checked;
var delete_talk = event.target.delete_talk && event.target.delete_talk.checked;
var delete_redirects = event.target.delete_redirects && event.target.delete_redirects.checked;
var unlink_page = event.target.unlink_page.checked;
var unlink_page = event.target.unlink_page.checked;
var delete_redirects = event.target.delete_redirects.checked;
var unlink_file = event.target.unlink_file.checked;
if( ! reason ) {
if( ! reason ) {
alert("You need to give a reason, you cabal crony!");
alert("You need to give a reason, you cabal crony!");
Line 219: Line 228:
}
}


function toCall( work ) {
var pageDeleter = new Morebits.batchOperation(delete_page ? "Deleting pages" : "Initiating requested tasks");
if( work.length === 0 &&  Twinkle.batchdelete.currentDeleteCounter <= 0 && Twinkle.batchdelete.currentUnlinkCounter <= 0 ) {
pageDeleter.setOption("chunkSize", Twinkle.getPref('batchdeleteChunks'));
window.clearInterval( Twinkle.batchdelete.currentdeletor );
// we only need the initial status lines if we're deleting the pages in the pages array
Morebits.wiki.removeCheckpoint();
pageDeleter.setOption("preserveIndividualStatusLines", delete_page);
return;
pageDeleter.setPageList(pages);
} else if( work.length !== 0 && ( Twinkle.batchdelete.currentDeleteCounter <= Twinkle.getPref('batchDeleteMinCutOff') || Twinkle.batchdelete.currentUnlinkCounter <= Twinkle.getPref('batchDeleteMinCutOff') ) ) {
pageDeleter.run(function(pageName) {
Twinkle.batchdelete.unlinkCache = []; // Clear the cache
var params = {
var pages = work.shift();
page: pageName,
Twinkle.batchdelete.currentDeleteCounter += pages.length;
delete_page: delete_page,
Twinkle.batchdelete.currentUnlinkCounter += pages.length;
delete_talk: delete_talk,
for( var i = 0; i < pages.length; ++i ) {
delete_redirects: delete_redirects,
var page = pages[i];
unlink_page: unlink_page,
var params = { page:page, reason:reason };
unlink_file: unlink_file && /^(File|Image)\:/i.test(pageName),
reason: reason,
var query, wikipedia_api;
pageDeleter: pageDeleter
if( unlink_page ) {
};
query = {
'action': 'query',
'list': 'backlinks',
'blfilterredir': 'nonredirects',
'blnamespace': [0, 100], // main space and portal space only
'bltitle': page,
'bllimit': Morebits.userIsInGroup( 'sysop' ) ? 5000 : 500 // 500 is max for normal users, 5000 for bots and sysops
};
wikipedia_api = new Morebits.wiki.api( 'Grabbing backlinks', query, Twinkle.batchdelete.callbacks.unlinkBacklinksMain );
wikipedia_api.params = params;
wikipedia_api.post();
} else {
--Twinkle.batchdelete.currentUnlinkCounter;
}
if( delete_page ) {
if (delete_redirects)
{
query = {
'action': 'query',
'list': 'backlinks',
'blfilterredir': 'redirects',
'bltitle': page,
'bllimit': Morebits.userIsInGroup( 'sysop' ) ? 5000 : 500 // 500 is max for normal users, 5000 for bots and sysops
};
wikipedia_api = new Morebits.wiki.api( 'Grabbing redirects', query, Twinkle.batchdelete.callbacks.deleteRedirectsMain );
wikipedia_api.params = params;
wikipedia_api.post();
}


var wikipedia_page = new Morebits.wiki.page( page, 'Deleting page ' + page );
var wikipedia_page = new Morebits.wiki.page( pageName, 'Deleting page ' + pageName );
wikipedia_page.setEditSummary(reason + Twinkle.getPref('deletionSummaryAd'));
wikipedia_page.setCallbackParameters(params);
wikipedia_page.suppressProtectWarning();
if( delete_page ) {
wikipedia_page.deletePage(function( apiobj ) {
wikipedia_page.setEditSummary(reason + Twinkle.getPref('deletionSummaryAd'));
--Twinkle.batchdelete.currentDeleteCounter;
wikipedia_page.suppressProtectWarning();
var link = document.createElement( 'a' );
wikipedia_page.deletePage(Twinkle.batchdelete.callbacks.doExtras, pageDeleter.workerFailure);
var innerPage = apiobj.parent.getPageName();
} else {
link.setAttribute( 'href', mw.util.getUrl( innerPage ) );
Twinkle.batchdelete.callbacks.doExtras(wikipedia_page);
link.setAttribute( 'title', innerPage );
link.appendChild( document.createTextNode( innerPage ) );
apiobj.getStatusElement().info( [ 'completed (' , link , ')' ] );
} );
} else {
--Twinkle.batchdelete.currentDeleteCounter;
}
}
}
}
}
});
var work = Morebits.array.chunk( pages, Twinkle.getPref('batchdeleteChunks') );
Morebits.wiki.addCheckpoint();
Twinkle.batchdelete.currentdeletor = window.setInterval( toCall, 1000, work );
};
};


Twinkle.batchdelete.callbacks = {
Twinkle.batchdelete.callbacks = {
deleteRedirectsMain: function( self ) {
// this stupid parameter name is a temporary thing until I implement an overhaul
var xmlDoc = self.responseXML;
// of Morebits.wiki.* callback parameters
var snapshot = xmlDoc.evaluate('//backlinks/bl/@title', xmlDoc, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null );
doExtras: function( thingWithParameters ) {
var params = thingWithParameters.parent ? thingWithParameters.parent.getCallbackParameters() :
thingWithParameters.getCallbackParameters();
// the initial batch operation's job is to delete the page, and that has
// succeeded by now
params.pageDeleter.workerSuccess(thingWithParameters);


var total = snapshot.snapshotLength;
var query, wikipedia_api;


if( snapshot.snapshotLength === 0 ) {
if( params.unlink_page ) {
return;
Twinkle.batchdelete.unlinkCache = {};
query = {
'action': 'query',
'list': 'backlinks',
'blfilterredir': 'nonredirects',
'blnamespace': [0, 100], // main space and portal space only
'bltitle': params.page,
'bllimit': 5000  // 500 is max for normal users, 5000 for bots and sysops
};
wikipedia_api = new Morebits.wiki.api( 'Grabbing backlinks', query, Twinkle.batchdelete.callbacks.unlinkBacklinksMain );
wikipedia_api.params = params;
wikipedia_api.post();
}
}


var statusIndicator = new Morebits.status('Deleting redirects for ' + self.params.page, '0%');
if( params.unlink_file ) {
query = {
'action': 'query',
'list': 'imageusage',
'iutitle': params.page,
'iulimit': 5000  // 500 is max for normal users, 5000 for bots and sysops
};
wikipedia_api = new Morebits.wiki.api( 'Grabbing file links', query, Twinkle.batchdelete.callbacks.unlinkImageInstancesMain );
wikipedia_api.params = params;
wikipedia_api.post();
}


var onsuccess = function( self ) {
if( params.delete_page ) {
var obj = self.params.obj;
if ( params.delete_redirects ) {
var total = self.params.total;
query = {
var now = parseInt( 100 * ++(self.params.current)/total, 10 ) + '%';
'action': 'query',
obj.update( now );
'titles': params.page,
self.statelem.unlink();
'prop': 'redirects',
if( self.params.current >= total ) {
'rdlimit': 5000  // 500 is max for normal users, 5000 for bots and sysops
obj.info( now + ' (completed)' );
};
Morebits.wiki.removeCheckpoint();
wikipedia_api = new Morebits.wiki.api( 'Grabbing redirects', query, Twinkle.batchdelete.callbacks.deleteRedirectsMain );
wikipedia_api.params = params;
wikipedia_api.post();
}
if ( params.delete_talk ) {
var pageTitle = mw.Title.newFromText(params.page);
if (pageTitle && pageTitle.namespace % 2 === 0 && pageTitle.namespace !== 2) {
pageTitle.namespace++;  // now pageTitle is the talk page title!
query = {
'action': 'query',
'titles': pageTitle.toText()
};
wikipedia_api = new Morebits.wiki.api( 'Checking whether talk page exists', query, Twinkle.batchdelete.callbacks.deleteTalk );
wikipedia_api.params = params;
wikipedia_api.params.talkPage = pageTitle.toText();
wikipedia_api.post();
}
}
}
};
}
 
},
 
deleteRedirectsMain: function( apiobj ) {
Morebits.wiki.addCheckpoint();
var xml = apiobj.responseXML;
if( snapshot.snapshotLength === 0 ) {
var pages = $(xml).find('rd').map(function() { return $(this).attr('title'); }).get();
statusIndicator.info( '100% (completed)' );
if (!pages.length) {
Morebits.wiki.removeCheckpoint();
return;
return;
}
}


var params = $.extend({}, self.params);
var redirectDeleter = new Morebits.batchOperation("Deleting redirects to " + apiobj.params.page);
params.current = 0;
redirectDeleter.setOption("chunkSize", Twinkle.getPref('batchdeleteChunks'));
params.total = total;
redirectDeleter.setPageList(pages);
params.obj = statusIndicator;
redirectDeleter.run(function(pageName) {
 
var wikipedia_page = new Morebits.wiki.page(pageName, "Deleting " + pageName);
 
wikipedia_page.setEditSummary('[[SV:CSD#G8|G8]]: Redirect to deleted page "' + apiobj.params.page + '"' + Twinkle.getPref('deletionSummaryAd'));
for ( var i = 0; i < snapshot.snapshotLength; ++i ) {
wikipedia_page.deletePage(redirectDeleter.workerSuccess, redirectDeleter.workerFailure);
var title = snapshot.snapshotItem(i).value;
});
var wikipedia_page = new Morebits.wiki.page( title, "Deleting " + title );
wikipedia_page.setEditSummary('[[WP:CSD#G8|G8]]: Redirect to deleted page "' + self.params.page + '"' + Twinkle.getPref('deletionSummaryAd'));
wikipedia_page.setCallbackParameters(params);
wikipedia_page.deletePage(onsuccess);
}
},
},
unlinkBacklinksMain: function( self ) {
deleteTalk: function( apiobj ) {
var xmlDoc = self.responseXML;
var xml = apiobj.responseXML;
var snapshot = xmlDoc.evaluate('//backlinks/bl/@title', xmlDoc, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null );
var exists = $(xml).find('page:not([missing])').length > 0;


if( snapshot.snapshotLength === 0 ) {
if( !exists ) {
--Twinkle.batchdelete.currentUnlinkCounter;
// no talk page; forget about it
return;
return;
}
}


var statusIndicator = new Morebits.status('Unlinking backlinks to ' + self.params.page, '0%');
var page = new Morebits.wiki.page(apiobj.params.talkPage, "Deleting talk page of article " + apiobj.params.page);
 
page.setEditSummary("[[SV:CSD#G8|G8]]: [[Help:Talk page|Talk page]] of deleted page \"" + apiobj.params.page + "\"" + Twinkle.getPref('deletionSummaryAd'));
var total = snapshot.snapshotLength * 2;
page.deletePage();
 
},
var onsuccess = function( self ) {
unlinkBacklinksMain: function( apiobj ) {
var obj = self.params.obj;
var xml = apiobj.responseXML;
var total = self.params.total;
var pages = $(xml).find('bl').map(function() { return $(this).attr('title'); }).get();
var now = parseInt( 100 * ++(self.params.current)/total, 10 ) + '%';
if (!pages.length) {
obj.update( now );
self.statelem.unlink();
if( self.params.current >= total ) {
obj.info( now + ' (completed)' );
--Twinkle.batchdelete.currentUnlinkCounter;
Morebits.wiki.removeCheckpoint();
}
};
 
Morebits.wiki.addCheckpoint();
if( snapshot.snapshotLength === 0 ) {
statusIndicator.info( '100% (completed)' );
--Twinkle.batchdelete.currentUnlinkCounter;
Morebits.wiki.removeCheckpoint();
return;
return;
}
}
self.params.total = total;
self.params.obj = statusIndicator;
self.params.current =  0;


for ( var i = 0; i < snapshot.snapshotLength; ++i ) {
var unlinker = new Morebits.batchOperation("Unlinking backlinks to " + apiobj.params.page);
var title = snapshot.snapshotItem(i).value;
unlinker.setOption("chunkSize", Twinkle.getPref('batchdeleteChunks'));
var wikipedia_page = new Morebits.wiki.page( title, "Unlinking on " + title );
unlinker.setPageList(pages);
var params = $.extend( {}, self.params );
unlinker.run(function(pageName) {
params.title = title;
var wikipedia_page = new Morebits.wiki.page(pageName, "Unlinking on " + pageName);
params.onsuccess = onsuccess;
var params = $.extend({}, apiobj.params);
params.title = pageName;
params.unlinker = unlinker;
wikipedia_page.setCallbackParameters(params);
wikipedia_page.setCallbackParameters(params);
wikipedia_page.load(Twinkle.batchdelete.callbacks.unlinkBacklinks);
wikipedia_page.load(Twinkle.batchdelete.callbacks.unlinkBacklinks);
}
});
},
},
unlinkBacklinks: function( pageobj ) {
unlinkBacklinks: function( pageobj ) {
Line 385: Line 376:
if( ! pageobj.exists() ) {
if( ! pageobj.exists() ) {
// we probably just deleted it, as a recursive backlink
// we probably just deleted it, as a recursive backlink
params.onsuccess( { params: params, statelem: pageobj.getStatusElement() } );
params.unlinker.workerSuccess(pageobj);
Morebits.wiki.actionCompleted();
return;
return;
}
}
var text;
var text;
if( params.title in Twinkle.batchdelete.unlinkCache ) {
if( params.title in Twinkle.batchdelete.unlinkCache ) {
text = Twinkle.batchdelete.unlinkCache[ params.title ];
text = Twinkle.batchdelete.unlinkCache[ params.title ];
Line 404: Line 394:
if( text === old_text ) {
if( text === old_text ) {
// Nothing to do, return
// Nothing to do, return
params.onsuccess( { params: params, statelem: pageobj.getStatusElement() } );
params.unlinker.workerSuccess(pageobj);
Morebits.wiki.actionCompleted();
return;
return;
}
}
Line 411: Line 400:
pageobj.setPageText(text);
pageobj.setPageText(text);
pageobj.setCreateOption('nocreate');
pageobj.setCreateOption('nocreate');
pageobj.save(params.onsuccess);
pageobj.setMaxConflictRetries(10);
pageobj.save(params.unlinker.workerSuccess, params.unlinker.workerFailure);
},
unlinkImageInstancesMain: function( apiobj ) {
var xml = apiobj.responseXML;
var pages = $(xml).find('iu').map(function() { return $(this).attr('title'); }).get();
if (!pages.length) {
return;
}
 
var unlinker = new Morebits.batchOperation("Unlinking backlinks to " + apiobj.params.page);
unlinker.setOption("chunkSize", Twinkle.getPref('batchdeleteChunks'));
unlinker.setPageList(pages);
unlinker.run(function(pageName) {
var wikipedia_page = new Morebits.wiki.page(pageName, "Removing file usages on " + pageName);
var params = $.extend({}, apiobj.params);
params.title = pageName;
params.unlinker = unlinker;
wikipedia_page.setCallbackParameters(params);
wikipedia_page.load(Twinkle.batchdelete.callbacks.unlinkImageInstances);
});
},
unlinkImageInstances: function( pageobj ) {
var params = pageobj.getCallbackParameters();
if( ! pageobj.exists() ) {
// we probably just deleted it, as a recursive backlink
params.unlinker.workerSuccess(pageobj);
return;
}
 
var image = params.image.replace( /^(?:Image|File):/, '' );
var text;
if( params.title in Twinkle.batchdelete.unlinkCache ) {
text = Twinkle.batchdelete.unlinkCache[ params.title ];
} else {
text = pageobj.getPageText();
}
var old_text = text;
var wikiPage = new Morebits.wikitext.page( text );
wikiPage.commentOutImage( image , 'Commented out because image was deleted' );
 
text = wikiPage.getText();
Twinkle.batchdelete.unlinkCache[ params.title ] = text;
if( text === old_text ) {
pageobj.getStatusElement().error( 'failed to unlink image ' + image + ' from ' + pageobj.getPageName() );
params.unlinker.workerFailure(pageobj);
return;
}
pageobj.setEditSummary('Removing instance of file ' + image + " that has been deleted because \"" + params.reason + "\")" + Twinkle.getPref('deletionSummaryAd'));
pageobj.setPageText(text);
pageobj.setCreateOption('nocreate');
pageobj.setMaxConflictRetries(10);
pageobj.save(params.unlinker.workerSuccess, params.unlinker.workerFailure);
}
}
};
};

Latest revision as of 21:51, 22 December 2018

//<nowiki>


(function($){


/*
 ****************************************
 *** twinklebatchdelete.js: Batch delete module (sysops only)
 ****************************************
 * Mode of invocation:     Tab ("D-batch")
 * Active on:              Existing non-articles, and Special:PrefixIndex
 * Config directives in:   TwinkleConfig
 */

Twinkle.batchdelete = function twinklebatchdelete() {
	if(
		Morebits.userIsInGroup( 'sysop' ) && (
			( mw.config.get( 'wgCurRevisionId' ) && mw.config.get( 'wgNamespaceNumber' ) > 0 ) ||
			mw.config.get( 'wgCanonicalSpecialPageName' ) === 'Prefixindex'
		)
	) {
		Twinkle.addPortletLink( Twinkle.batchdelete.callback, "D-batch", "tw-batch", "Delete pages found in this category/on this page" );
	}
};

Twinkle.batchdelete.unlinkCache = {};
Twinkle.batchdelete.callback = function twinklebatchdeleteCallback() {
	var Window = new Morebits.simpleWindow( 600, 400 );
	Window.setTitle( "Batch deletion" );
	Window.setScriptName( "Twinkle" );
	Window.addFooterLink( "Twinkle help", "SV:TW/DOC#batchdelete" );

	var form = new Morebits.quickForm( Twinkle.batchdelete.callback.evaluate );
	form.append( {
			type: 'checkbox',
			list: [
				{
					label: 'Delete pages',
					name: 'delete_page',
					value: 'delete',
					checked: true,
					subgroup: {
						type: 'checkbox',
						list: [
							{
								label: 'Delete associated talk pages (except user talk pages)',
								name: 'delete_talk',
								value: 'delete_talk',
								checked: true
							},
							{
								label: 'Delete redirects to deleted pages',
								name: 'delete_redirects',
								value: 'delete_redirects',
								checked: true
							}
						]
					}
				},
				{
					label: 'Unlink backlinks to each page (in Main and Portal namespaces only)',
					name: 'unlink_page',
					value: 'unlink',
					checked: false
				},
				{
					label: 'Remove usages of each file (in all namespaces)',
					name: 'unlink_file',
					value: 'unlink_file',
					checked: true
				}
			]
		} );
	form.append( {
			type: 'input',
			name: 'reason',
			label: 'Reason: ',
			size: 60
		} );

	var query = {
		'action': 'query',
		'prop': 'revisions|info|imageinfo',
		'inprop': 'protection',
		'rvprop': 'size|user'
	};
	if( mw.config.get( 'wgNamespaceNumber' ) === 14 ) {  // Category:
		query.generator = 'categorymembers';
		query.gcmtitle = mw.config.get('wgPageName');
		query.gcmlimit = Twinkle.getPref('batchMax'); // the max for sysops
	} else if( mw.config.get( 'wgCanonicalSpecialPageName' ) === 'Prefixindex' ) {

		query.generator = 'allpages';
		query.gaplimit = Twinkle.getPref('batchMax'); // the max for sysops
		if(Morebits.queryString.exists( 'prefix' ) )
		{
			query.gapnamespace = Morebits.queryString.get( 'namespace' );
			query.gapprefix = Morebits.string.toUpperCaseFirstChar( Morebits.queryString.get( 'prefix' ) );
		}
		else
		{
			var pathSplit = decodeURIComponent(location.pathname).split('/');
			if (pathSplit.length < 3 || pathSplit[2] !== "Special:PrefixIndex") {
				return;
			}
			var titleSplit = pathSplit[3].split(':');
			query.gapnamespace = mw.config.get("wgNamespaceIds")[titleSplit[0].toLowerCase()];
			if ( titleSplit.length < 2 || typeof query.gapnamespace === 'undefined' )
			{
				query.gapnamespace = 0;  // article namespace
				query.gapprefix = pathSplit.splice(3).join('/');
			}
			else
			{
				pathSplit = pathSplit.splice(4);
				pathSplit.splice(0,0,titleSplit.splice(1).join(':'));
				query.gapprefix = pathSplit.join('/');
			}
		}
	} else {
		query.generator = 'links';
		query.titles = mw.config.get('wgPageName');
		query.gpllimit = Twinkle.getPref('batchMax'); // the max for sysops
	}

	var statusdiv = document.createElement( 'div' );
	statusdiv.style.padding = '15px';  // just so it doesn't look broken
	Window.setContent(statusdiv);
	Morebits.status.init(statusdiv);
	Window.display();

	var statelem = new Morebits.status("Grabbing list of pages");
	var wikipedia_api = new Morebits.wiki.api( 'loading...', query, function( apiobj ) {
			var xml = apiobj.responseXML;
			var $pages = $(xml).find('page').filter(':not([missing])');  // :not([imagerepository="shared"])
			var list = [];
			$pages.each(function(index, page) {
				var $page = $(page);
				var ns = $page.attr('ns');
				var title = $page.attr('title');
				var isRedir = $page.attr('redirect') === "";
				var $editprot = $page.find('pr[type="edit"][level="sysop"]');
				var isProtected = $editprot.length > 0;
				var size = $page.find('rev').attr('size');

				var metadata = [];
				if (isRedir) {
					metadata.push("redirect");
				}
				if (isProtected) {
					metadata.push("fully protected" +
						($editprot.attr('expiry') === 'infinity' ? ' indefinitely' : (', expires ' + $editprot.attr('expiry'))));
				}
				if (ns === "6") {  // mimic what delimages used to show for files
					metadata.push("uploader: " + $page.find('ii').attr('user'));
					metadata.push("last edit from: " + $page.find('rev').attr('user'));
				} else {
					metadata.push(size + " bytes");
				}
				list.push({
					label: title + (metadata.length ? (' (' + metadata.join('; ') + ')') : ''),
					value: title,
					checked: true,
					style: (isProtected ? 'color:red' : '')
				});
			});

			apiobj.params.form.append({ type: 'header', label: 'Pages to delete' });
			apiobj.params.form.append({
					type: 'button',
					label: "Select All",
					event: function(e) {
						$(Morebits.quickForm.getElements(e.target.form, "pages")).prop('checked', true);
					}
				});
			apiobj.params.form.append({
					type: 'button',
					label: "Deselect All",
					event: function(e) {
						$(Morebits.quickForm.getElements(e.target.form, "pages")).prop('checked', false);
					}
				});
			apiobj.params.form.append( {
					type: 'checkbox',
					name: 'pages',
					list: list
				} );
			apiobj.params.form.append( { type:'submit' } );

			var result = apiobj.params.form.render();
			apiobj.params.Window.setContent( result );

			Morebits.checkboxShiftClickSupport(Morebits.quickForm.getElements(result, 'pages'));
		}, statelem );

	wikipedia_api.params = { form:form, Window:Window };
	wikipedia_api.post();
};

Twinkle.batchdelete.callback.evaluate = function twinklebatchdeleteCallbackEvaluate(event) {
	Morebits.wiki.actionCompleted.notice = 'Status';
	Morebits.wiki.actionCompleted.postfix = 'batch deletion is now complete';

	var numProtected = $(Morebits.quickForm.getElements(event.target, 'pages')).filter(function(index, element) {
		return element.checked && element.nextElementSibling.style.color === 'red';
	}).length;
	if (numProtected > 0 && !confirm("You are about to delete " + numProtected + " fully protected page(s). Are you sure?")) {
		return;
	}

	var pages = event.target.getChecked( 'pages' );
	var reason = event.target.reason.value;
	var delete_page = event.target.delete_page.checked;
	var delete_talk = event.target.delete_talk && event.target.delete_talk.checked;
	var delete_redirects = event.target.delete_redirects && event.target.delete_redirects.checked;
	var unlink_page = event.target.unlink_page.checked;
	var unlink_file = event.target.unlink_file.checked;
	if( ! reason ) {
		alert("You need to give a reason, you cabal crony!");
		return;
	}
	Morebits.simpleWindow.setButtonsEnabled( false );
	Morebits.status.init( event.target );
	if( !pages ) {
		Morebits.status.error( 'Error', 'nothing to delete, aborting' );
		return;
	}

	var pageDeleter = new Morebits.batchOperation(delete_page ? "Deleting pages" : "Initiating requested tasks");
	pageDeleter.setOption("chunkSize", Twinkle.getPref('batchdeleteChunks'));
	// we only need the initial status lines if we're deleting the pages in the pages array
	pageDeleter.setOption("preserveIndividualStatusLines", delete_page);
	pageDeleter.setPageList(pages);
	pageDeleter.run(function(pageName) {
		var params = {
			page: pageName,
			delete_page: delete_page,
			delete_talk: delete_talk,
			delete_redirects: delete_redirects,
			unlink_page: unlink_page,
			unlink_file: unlink_file && /^(File|Image)\:/i.test(pageName),
			reason: reason,
			pageDeleter: pageDeleter
		};

		var wikipedia_page = new Morebits.wiki.page( pageName, 'Deleting page ' + pageName );
		wikipedia_page.setCallbackParameters(params);
		if( delete_page ) {
			wikipedia_page.setEditSummary(reason + Twinkle.getPref('deletionSummaryAd'));
			wikipedia_page.suppressProtectWarning();
			wikipedia_page.deletePage(Twinkle.batchdelete.callbacks.doExtras, pageDeleter.workerFailure);
		} else {
			Twinkle.batchdelete.callbacks.doExtras(wikipedia_page);
		}
	});
};

Twinkle.batchdelete.callbacks = {
	// this stupid parameter name is a temporary thing until I implement an overhaul
	// of Morebits.wiki.* callback parameters
	doExtras: function( thingWithParameters ) {
		var params = thingWithParameters.parent ? thingWithParameters.parent.getCallbackParameters() :
			thingWithParameters.getCallbackParameters();
		// the initial batch operation's job is to delete the page, and that has
		// succeeded by now
		params.pageDeleter.workerSuccess(thingWithParameters);

		var query, wikipedia_api;

		if( params.unlink_page ) {
			Twinkle.batchdelete.unlinkCache = {};
			query = {
				'action': 'query',
				'list': 'backlinks',
				'blfilterredir': 'nonredirects',
				'blnamespace': [0, 100], // main space and portal space only
				'bltitle': params.page,
				'bllimit': 5000  // 500 is max for normal users, 5000 for bots and sysops
			};
			wikipedia_api = new Morebits.wiki.api( 'Grabbing backlinks', query, Twinkle.batchdelete.callbacks.unlinkBacklinksMain );
			wikipedia_api.params = params;
			wikipedia_api.post();
		}

		if( params.unlink_file ) {
			query = {
				'action': 'query',
				'list': 'imageusage',
				'iutitle': params.page,
				'iulimit': 5000  // 500 is max for normal users, 5000 for bots and sysops
			};
			wikipedia_api = new Morebits.wiki.api( 'Grabbing file links', query, Twinkle.batchdelete.callbacks.unlinkImageInstancesMain );
			wikipedia_api.params = params;
			wikipedia_api.post();
		}

		if( params.delete_page ) {
			if ( params.delete_redirects ) {
				query = {
					'action': 'query',
					'titles': params.page,
					'prop': 'redirects',
					'rdlimit': 5000  // 500 is max for normal users, 5000 for bots and sysops
				};
				wikipedia_api = new Morebits.wiki.api( 'Grabbing redirects', query, Twinkle.batchdelete.callbacks.deleteRedirectsMain );
				wikipedia_api.params = params;
				wikipedia_api.post();
			}
			if ( params.delete_talk ) {
				var pageTitle = mw.Title.newFromText(params.page);
				if (pageTitle && pageTitle.namespace % 2 === 0 && pageTitle.namespace !== 2) {
					pageTitle.namespace++;  // now pageTitle is the talk page title!
					query = {
						'action': 'query',
						'titles': pageTitle.toText()
					};
					wikipedia_api = new Morebits.wiki.api( 'Checking whether talk page exists', query, Twinkle.batchdelete.callbacks.deleteTalk );
					wikipedia_api.params = params;
					wikipedia_api.params.talkPage = pageTitle.toText();
					wikipedia_api.post();
				}
			}
		}
	},
	deleteRedirectsMain: function( apiobj ) {
		var xml = apiobj.responseXML;
		var pages = $(xml).find('rd').map(function() { return $(this).attr('title'); }).get();
		if (!pages.length) {
			return;
		}

		var redirectDeleter = new Morebits.batchOperation("Deleting redirects to " + apiobj.params.page);
		redirectDeleter.setOption("chunkSize", Twinkle.getPref('batchdeleteChunks'));
		redirectDeleter.setPageList(pages);
		redirectDeleter.run(function(pageName) {
			var wikipedia_page = new Morebits.wiki.page(pageName, "Deleting " + pageName);
			wikipedia_page.setEditSummary('[[SV:CSD#G8|G8]]: Redirect to deleted page "' + apiobj.params.page + '"' + Twinkle.getPref('deletionSummaryAd'));
			wikipedia_page.deletePage(redirectDeleter.workerSuccess, redirectDeleter.workerFailure);
		});
	},
	deleteTalk: function( apiobj ) {
		var xml = apiobj.responseXML;
		var exists = $(xml).find('page:not([missing])').length > 0;

		if( !exists ) {
			// no talk page; forget about it
			return;
		}

		var page = new Morebits.wiki.page(apiobj.params.talkPage, "Deleting talk page of article " + apiobj.params.page);
		page.setEditSummary("[[SV:CSD#G8|G8]]: [[Help:Talk page|Talk page]] of deleted page \"" + apiobj.params.page + "\"" + Twinkle.getPref('deletionSummaryAd'));
		page.deletePage();
	},
	unlinkBacklinksMain: function( apiobj ) {
		var xml = apiobj.responseXML;
		var pages = $(xml).find('bl').map(function() { return $(this).attr('title'); }).get();
		if (!pages.length) {
			return;
		}

		var unlinker = new Morebits.batchOperation("Unlinking backlinks to " + apiobj.params.page);
		unlinker.setOption("chunkSize", Twinkle.getPref('batchdeleteChunks'));
		unlinker.setPageList(pages);
		unlinker.run(function(pageName) {
			var wikipedia_page = new Morebits.wiki.page(pageName, "Unlinking on " + pageName);
			var params = $.extend({}, apiobj.params);
			params.title = pageName;
			params.unlinker = unlinker;
			wikipedia_page.setCallbackParameters(params);
			wikipedia_page.load(Twinkle.batchdelete.callbacks.unlinkBacklinks);
		});
	},
	unlinkBacklinks: function( pageobj ) {
		var params = pageobj.getCallbackParameters();
		if( ! pageobj.exists() ) {
			// we probably just deleted it, as a recursive backlink
			params.unlinker.workerSuccess(pageobj);
			return;
		}

		var text;
		if( params.title in Twinkle.batchdelete.unlinkCache ) {
			text = Twinkle.batchdelete.unlinkCache[ params.title ];
		} else {
			text = pageobj.getPageText();
		}
		var old_text = text;
		var wikiPage = new Morebits.wikitext.page( text );
		wikiPage.removeLink( params.page );

		text = wikiPage.getText();
		Twinkle.batchdelete.unlinkCache[ params.title ] = text;
		if( text === old_text ) {
			// Nothing to do, return
			params.unlinker.workerSuccess(pageobj);
			return;
		}
		pageobj.setEditSummary('Removing link(s) to deleted page ' + params.page + Twinkle.getPref('deletionSummaryAd'));
		pageobj.setPageText(text);
		pageobj.setCreateOption('nocreate');
		pageobj.setMaxConflictRetries(10);
		pageobj.save(params.unlinker.workerSuccess, params.unlinker.workerFailure);
	},
	unlinkImageInstancesMain: function( apiobj ) {
		var xml = apiobj.responseXML;
		var pages = $(xml).find('iu').map(function() { return $(this).attr('title'); }).get();
		if (!pages.length) {
			return;
		}

		var unlinker = new Morebits.batchOperation("Unlinking backlinks to " + apiobj.params.page);
		unlinker.setOption("chunkSize", Twinkle.getPref('batchdeleteChunks'));
		unlinker.setPageList(pages);
		unlinker.run(function(pageName) {
			var wikipedia_page = new Morebits.wiki.page(pageName, "Removing file usages on " + pageName);
			var params = $.extend({}, apiobj.params);
			params.title = pageName;
			params.unlinker = unlinker;
			wikipedia_page.setCallbackParameters(params);
			wikipedia_page.load(Twinkle.batchdelete.callbacks.unlinkImageInstances);
		});
	},
	unlinkImageInstances: function( pageobj ) {
		var params = pageobj.getCallbackParameters();
		if( ! pageobj.exists() ) {
			// we probably just deleted it, as a recursive backlink
			params.unlinker.workerSuccess(pageobj);
			return;
		}

		var image = params.image.replace( /^(?:Image|File):/, '' );
		var text;
		if( params.title in Twinkle.batchdelete.unlinkCache ) {
			text = Twinkle.batchdelete.unlinkCache[ params.title ];
		} else {
			text = pageobj.getPageText();
		}
		var old_text = text;
		var wikiPage = new Morebits.wikitext.page( text );
		wikiPage.commentOutImage( image , 'Commented out because image was deleted' );

		text = wikiPage.getText();
		Twinkle.batchdelete.unlinkCache[ params.title ] = text;
		if( text === old_text ) {
			pageobj.getStatusElement().error( 'failed to unlink image ' + image + ' from ' + pageobj.getPageName() );
			params.unlinker.workerFailure(pageobj);
			return;
		}
		pageobj.setEditSummary('Removing instance of file ' + image + " that has been deleted because \"" + params.reason + "\")" + Twinkle.getPref('deletionSummaryAd'));
		pageobj.setPageText(text);
		pageobj.setCreateOption('nocreate');
		pageobj.setMaxConflictRetries(10);
		pageobj.save(params.unlinker.workerSuccess, params.unlinker.workerFailure);
	}
};
})(jQuery);


//</nowiki>