/*
* WPCleaner: A tool to help on Wikipedia maintenance tasks.
* Copyright (C) 2014 Nicolas Vervelle
*
* See README.txt file for licensing information.
*/
package org.wikipediacleaner.gui.swing.worker;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wikipediacleaner.api.API;
import org.wikipediacleaner.api.APIException;
import org.wikipediacleaner.api.APIFactory;
import org.wikipediacleaner.api.MediaWiki;
import org.wikipediacleaner.api.constants.EnumWikipedia;
import org.wikipediacleaner.api.constants.WPCConfiguration;
import org.wikipediacleaner.api.constants.WPCConfigurationBoolean;
import org.wikipediacleaner.api.constants.WPCConfigurationString;
import org.wikipediacleaner.api.constants.WPCConfigurationStringList;
import org.wikipediacleaner.api.data.DataManager;
import org.wikipediacleaner.api.data.Namespace;
import org.wikipediacleaner.api.data.Page;
import org.wikipediacleaner.api.data.PageAnalysis;
import org.wikipediacleaner.api.data.PageElementTemplate;
import org.wikipediacleaner.api.data.QueryResult;
import org.wikipediacleaner.api.data.Section;
import org.wikipediacleaner.api.data.User;
import org.wikipediacleaner.gui.swing.basic.BasicWindow;
import org.wikipediacleaner.gui.swing.basic.BasicWorker;
import org.wikipediacleaner.gui.swing.basic.Utilities;
import org.wikipediacleaner.i18n.GT;
import org.wikipediacleaner.utils.Configuration;
import org.wikipediacleaner.utils.ConfigurationValueString;
/**
* Tools for updating warnings.
*/
public abstract class UpdateWarningTools {
private final static Log log = LogFactory.getLog(UpdateWarningTools.class);
/** Wiki. */
protected final EnumWikipedia wiki;
/** Wiki configuration. */
protected final WPCConfiguration configuration;
/** Worker. */
protected final BasicWorker worker;
/** Window. */
protected final BasicWindow window;
/** True for allowing warning creation. */
protected final boolean createWarning;
/** True if this is an automatic edit. */
protected final boolean automaticEdit;
/** Force use of section 0 in the talk page. */
protected final boolean section0;
/** MediaWiki API. */
protected final API api;
/** True if contents is already available in pages. */
private boolean contentsAvailable;
/** True if purge page cache should be attempted when errors are not found. */
private boolean usePurge;
/** True if this is a simulation. */
private boolean simulation;
/** Map for errors. */
private Map<String, List<String>> errorsMap;
/** List of articles titles supposed to have the error. */
private Set<String> articles;
/**
* @param wiki Wiki.
* @param worker Worker.
* @param window Window.
* @param createWarning Create warning if necessary.
* @param automaticEdit True if the edits are automatic.
*/
protected UpdateWarningTools(
EnumWikipedia wiki,
BasicWorker worker, BasicWindow window,
boolean createWarning, boolean automaticEdit) {
this.wiki = wiki;
this.configuration = wiki.getConfiguration();
this.worker = worker;
this.window = window;
this.createWarning = createWarning;
this.automaticEdit = automaticEdit;
this.usePurge = false;
this.section0 = useSection0();
this.api = APIFactory.getAPI();
}
/**
* @param available True if contents is already available in pages.
*/
public void setContentsAvailable(boolean available) {
this.contentsAvailable = available;
}
/**
* @return True if contents is already available in pages.
*/
public boolean getContentsAvailable() {
return contentsAvailable;
}
/**
* @param purge True if purge page cache should be attempted.
*/
public void setUsePurge(boolean purge) {
this.usePurge = purge;
}
/**
* @param simulation True if this is a simulation.
*/
public void setSimulation(boolean simulation) {
this.simulation = simulation;
}
/**
* Initialize the errors map.
*/
public void prepareErrorsMap() {
this.errorsMap = new HashMap<String, List<String>>();
}
/**
* @return Errors map.
*/
public Map<String, List<String>> getErrorsMap() {
return errorsMap;
}
// ==========================================================================
// Warning management
// ==========================================================================
/**
* Update warning for a list of pages.
*
* @param pages List of pages.
* @param creators For each page title, user who has created the page.
* @param modifiers For each page title, users who have modified the page.
* @param stats Statistics.
* @throws APIException
*/
public void updateWarning(
List<Page> pages,
Map<String, String> creators,
Map<String, List<String>> modifiers,
Stats stats) throws APIException {
if ((pages == null) || (pages.isEmpty())) {
return;
}
// Retrieve information in the pages
if (!retrievePageInformation(pages)) {
return;
}
// Deal with non encyclopedic pages
manageNonEncyclopedicPages(pages);
// Load talk pages and "To do" sub pages
Map<Page, Page> mapTalkPages = new HashMap<Page, Page>();
Map<Page, Page> mapTodoSubpages = new HashMap<Page, Page>();
for (Page page : pages) {
Page talkPage = page.getTalkPage();
mapTalkPages.put(page, talkPage);
String todoSubpageAttr = configuration.getString(WPCConfigurationString.TODO_SUBPAGE);
if (todoSubpageAttr != null) {
Page todoSubpage = talkPage.getSubPage(todoSubpageAttr);
mapTodoSubpages.put(page, todoSubpage);
}
}
if (canUpdateWarning()) {
MediaWiki mw = MediaWiki.getMediaWikiAccess(worker);
if (section0) {
mw.retrieveSectionContents(wiki, mapTalkPages.values(), 0, false);
} else {
mw.retrieveContents(wiki, mapTalkPages.values(), false, false, false, false);
}
mw.retrieveContents(wiki, mapTodoSubpages.values(), true, false, false, false);
if (mw.shouldStop()) {
return;
}
}
// Update warning
for (Page page : pages) {
PageAnalysis pageAnalysis = page.getAnalysis(page.getContents(), true);
boolean updated = updateWarning(
pageAnalysis, page.getRevisionId(),
mapTalkPages.get(page),
mapTodoSubpages.get(page),
(creators != null) ? creators.get(page.getTitle()) : null,
(modifiers != null) ? modifiers.get(page.getTitle()) : null,
stats);
if (updated) {
// log.debug("Page " + page.getTitle() + " has been updated.");
}
if (stats != null) {
stats.addAnalyzedPage(page);
if (updated) {
stats.addUpdatedPage(page);
}
}
}
return;
}
/**
* Manage talk pages present in the list.
*
* @param pages List of pages.
* @throws APIException
*/
private void manageNonEncyclopedicPages(List<Page> pages)
throws APIException {
if (pages == null) {
return;
}
Iterator<Page> itPage = pages.iterator();
List<Integer> encyclopedicNamespaces = configuration.getEncyclopedicNamespaces();
while (itPage.hasNext()) {
Page page = itPage.next();
if (!page.isArticle() ||
!encyclopedicNamespaces.contains(page.getNamespace())) {
itPage.remove();
if (!simulation) {
PageAnalysis analysis = page.getAnalysis(page.getContents(), true);
Collection<String> elements = constructWarningElements(analysis, null, null);
if ((elements == null) || (elements.isEmpty())) {
purgePage(page);
}
}
}
}
}
/**
* @return true if warnings can be updated.
*/
public boolean canUpdateWarning() {
List<String> todoTemplates = configuration.getStringList(WPCConfigurationStringList.TODO_TEMPLATES);
if ((todoTemplates == null) ||
(todoTemplates.isEmpty())) {
return false;
}
String warningTemplate = configuration.getString(getWarningTemplate());
if ((warningTemplate == null) || (warningTemplate.trim().length() == 0)) {
return false;
}
return true;
}
/**
* Update warning for a page.
*
* @param pageAnalysis Page analysis (must have enough information to compute the elements for the warning).
* @param pageRevId Page revision id.
* @param talkPage (Optional) Talk page with contents of section 0.
* @param todoSubpage (Optional) To do sub-page with contents.
* @param creator User who has created the page.
* @param modifiers Users who have modified the page.
* @param stats Statistics.
* @return True if the warning has been updated.
* @throws APIException
*/
public boolean updateWarning(
PageAnalysis pageAnalysis, Integer pageRevId,
Page talkPage, Page todoSubpage,
String creator, List<String> modifiers,
Stats stats) throws APIException {
if ((pageAnalysis == null) ||
(pageAnalysis.getPage() == null) ||
!pageAnalysis.getPage().isArticle()) {
return false;
}
List<String> todoTemplates = configuration.getStringList(WPCConfigurationStringList.TODO_TEMPLATES);
if ((todoTemplates == null) ||
(todoTemplates.isEmpty())) {
return false;
}
String warningTemplate = configuration.getString(getWarningTemplate());
if ((warningTemplate == null) || (warningTemplate.trim().length() == 0)) {
if (!simulation) {
return false;
}
}
Page page = pageAnalysis.getPage();
// Retrieving talk page contents
if (talkPage == null) {
talkPage = page.getTalkPage();
setText(GT._("Retrieving page contents - {0}", talkPage.getTitle()));
if (section0) {
api.retrieveSectionContents(wiki, talkPage, 0);
} else {
api.retrieveContents(wiki, Collections.singletonList(talkPage), false, false);
}
}
// "To do" sub-page
String todoSubpageAttr = configuration.getString(WPCConfigurationString.TODO_SUBPAGE);
if (todoSubpageAttr != null) {
// Retrieving "To do" sub-page contents
if (todoSubpage == null) {
todoSubpage = talkPage.getSubPage(todoSubpageAttr);
setText(GT._("Retrieving page contents - {0}", todoSubpage.getTitle()));
api.retrieveContents(wiki, Collections.singletonList(todoSubpage), false, false);
}
// If we force the use of "To do" sub-page, the warning must be on it
if ((page.getNamespace() != null) &&
(page.getNamespace().intValue() == Namespace.MAIN)) {
if (configuration.getBoolean(WPCConfigurationBoolean.TODO_SUBPAGE_FORCE)) {
return manageWarningOnTodoSubpage(
pageAnalysis, pageRevId, todoSubpage, talkPage,
creator, modifiers, stats);
}
} else if (configuration.getBoolean(WPCConfigurationBoolean.TODO_SUBPAGE_FORCE_OTHER)) {
return manageWarningOnTodoSubpage(
pageAnalysis, pageRevId, todoSubpage, talkPage,
creator, modifiers, stats);
}
// If "To do" sub-page exists, the warning must be on it
if (Boolean.TRUE.equals(todoSubpage.isExisting())) {
return manageWarningOnTodoSubpage(
pageAnalysis, pageRevId, todoSubpage, talkPage,
creator, modifiers, stats);
}
// If talk page has a template linking to the "To do" sub-page,
// the warning must be on the "To do" sub-page
PageElementTemplate templateTodoLink = getExistingTemplateTodoLink(talkPage, talkPage.getContents());
if (templateTodoLink != null) {
return manageWarningOnTodoSubpage(
pageAnalysis, pageRevId, todoSubpage, talkPage,
creator, modifiers, stats);
}
// If talk page has a link to the "To do" sub-page,
// the warning must be on the "To do" sub-page
/*api.retrieveLinks(wikipedia, talkPage, talkPage.getNamespace());
if (talkPage.getLinks() != null) {
for (Page link : talkPage.getLinks()) {
if (Page.areSameTitle(link.getTitle(), todoSubpage.getTitle())) {
return manageWarningOnTodoSubpage(pageAnalysis, pageRevId, todoSubpage, talkPage);
}
}
}*/
}
return manageWarningOnTalkPage(
pageAnalysis, pageRevId, talkPage,
creator, modifiers, stats);
}
/**
* Update warning on the "To do" sub-page.
*
* @param pageAnalysis Page analysis (must have enough information to compute the elements for the warning).
* @param pageRevId Page revision id.
* @param todoSubpage "To do" sub-page.
* @param talkPage Talk page.
* @param creator User who has created the page.
* @param modifiers Users who have modified the page.
* @param stats Statistics.
* @return True if the warning has been updated.
* @throws APIException
*/
private boolean manageWarningOnTodoSubpage(
PageAnalysis pageAnalysis, Integer pageRevId,
Page todoSubpage, Page talkPage,
String creator, List<String> modifiers,
Stats stats) throws APIException {
Collection<String> elements = constructWarningElements(pageAnalysis, talkPage, todoSubpage);
boolean result = false;
if ((elements == null) || (elements.isEmpty())) {
if (!simulation) {
boolean resultTodo = removeWarningOnTodoSubpage(todoSubpage);
boolean resultTalk = removeWarningOnTalkPage(talkPage);
result = resultTodo || resultTalk;
if (result) {
if (!resultTalk) {
purgePage(talkPage);
}
if (stats != null) {
stats.addRemovedWarning(pageAnalysis.getPage());
}
} else {
purgeArticle(pageAnalysis.getPage());
}
}
} else {
if (!simulation) {
result |= updateWarningOnTodoSubpage(
pageRevId, todoSubpage, elements, creator, modifiers);
if (createWarning) {
result |= cleanWarningOnTalkPage(talkPage, elements);
}
}
if (stats != null) {
stats.addLinks(pageAnalysis.getPage(), elements.size());
}
}
return result;
}
/**
* Update warning on the talk page.
*
* @param pageAnalysis Page analysis (must have enough information to compute the elements for the warning).
* @param pageRevId Page revision id.
* @param talkPage Talk page.
* @param creator User who has created the page.
* @param modifiers Users who have modified the page.
* @param stats Statistics.
* @return True if the warning has been updated.
* @throws APIException
*/
private boolean manageWarningOnTalkPage(
PageAnalysis pageAnalysis, Integer pageRevId, Page talkPage,
String creator, List<String> modifiers,
Stats stats) throws APIException {
Collection<String> elements = constructWarningElements(pageAnalysis, talkPage, null);
boolean result = false;
if ((elements == null) || (elements.isEmpty())) {
if (!simulation) {
result = removeWarningOnTalkPage(talkPage);
if (result) {
if (stats != null) {
stats.addRemovedWarning(pageAnalysis.getPage());
}
} else {
purgeArticle(pageAnalysis.getPage());
}
}
} else {
if (!simulation) {
result |= updateWarningOnTalkPage(
pageAnalysis, pageRevId, talkPage, elements, creator, modifiers);
}
if (stats != null) {
stats.addLinks(pageAnalysis.getPage(), elements.size());
}
}
return result;
}
/**
* Create/update warning on the "To do" sub-page.
*
* @param pageRevId Page revision id.
* @param todoSubpage "To do" sub-page.
* @param elements Elements for the warning.
* @param creator User who has created the page.
* @param modifiers Users who have modified the page.
* @return True if the warning has been updated.
* @throws APIException
*/
private boolean updateWarningOnTodoSubpage(
Integer pageRevId, Page todoSubpage, Collection<String> elements,
String creator, List<String> modifiers) throws APIException {
if ((todoSubpage == null) || (elements == null)) {
return false;
}
// Search warning in the "To do" sub-page
String contents = todoSubpage.getContents();
if (contents == null) {
contents = "";
}
PageAnalysis analysis = todoSubpage.getAnalysis(contents, true);
PageElementTemplate templateWarning = getFirstWarningTemplate(analysis);
// If warning is missing, add it
if (templateWarning == null) {
if (!createWarning) {
return false;
}
setText(getMessageUpdateWarning(todoSubpage.getTitle()));
StringBuilder tmp = new StringBuilder(contents);
if ((tmp.length() > 0) && (tmp.charAt(tmp.length() - 1) != '\n')) {
tmp.append('\n');
}
tmp.append("* ");
addWarning(tmp, pageRevId, elements);
tmp.append('\n');
updatePage(
todoSubpage, tmp.toString(),
getWarningComment(elements),
false);
// Inform creator and modifiers of the page
informContributors(analysis, elements, creator, modifiers);
return true;
}
// Check if modifications are needed
if (isModified(elements, templateWarning)) {
StringBuilder tmp = new StringBuilder();
int index = templateWarning.getBeginIndex();
while ((index > 0) && (contents.charAt(index) != '\n')) {
index--;
}
if (index > 0) {
tmp.append(contents.substring(0, index));
tmp.append('\n');
}
tmp.append("* ");
addWarning(tmp, pageRevId, elements);
tmp.append('\n');
index = templateWarning.getEndIndex();
while ((index < contents.length()) && (contents.charAt(index) != '\n')) {
index++;
}
index++;
if (index < contents.length()) {
tmp.append(contents.substring(index));
}
api.updatePage(
wiki, todoSubpage, tmp.toString(),
getWarningComment(elements),
automaticEdit, false);
return true;
}
return false;
}
/**
* Create/update warning on the talk page.
*
* @param analysis Page analysis.
* @param pageRevId Page revision id.
* @param talkPage Talk page.
* @param elements Elements for the warning.
* @param creator User who has created the page.
* @param modifiers Users who have modified the page.
* @return True if the warning has been updated.
* @throws APIException
*/
private boolean updateWarningOnTalkPage(
PageAnalysis analysis, Integer pageRevId,
Page talkPage, Collection<String> elements,
String creator, List<String> modifiers) throws APIException {
if ((talkPage == null) || (elements == null)) {
return false;
}
// Search "To do" template in the talk page
String contents = talkPage.getContents();
if (contents == null) {
contents = "";
}
PageAnalysis talkAnalysis = talkPage.getAnalysis(contents, true);
PageElementTemplate templateTodo = null;
List<String> todoTemplates = configuration.getStringList(WPCConfigurationStringList.TODO_TEMPLATES);
if ((todoTemplates == null) ||
(todoTemplates.isEmpty())) {
return false;
}
for (String todoTemplate : todoTemplates) {
List<PageElementTemplate> templates = talkAnalysis.getTemplates(todoTemplate);
PageElementTemplate templateTmp = (templates != null) && (templates.size() > 0) ?
templates.get(0) : null;
if (templateTmp != null) {
if ((templateTodo == null) || (templateTmp.getBeginIndex() < templateTodo.getBeginIndex())) {
templateTodo = templateTmp;
}
}
}
// If "To do" template is missing, add it
if (templateTodo == null) {
if (!createWarning) {
return false;
}
// Search where to add "To do" template
PageElementTemplate templatePrevious = null;
List<String> warningAfterTemplates = configuration.getStringList(
WPCConfigurationStringList.WARNING_AFTER_TEMPLATES);
if (warningAfterTemplates != null) {
for (String previousTemplate : warningAfterTemplates) {
Collection<PageElementTemplate> templates = talkAnalysis.getTemplates(previousTemplate);
for (PageElementTemplate templateTmp : templates) {
if ((templatePrevious == null) ||
(templateTmp.getEndIndex() > templatePrevious.getEndIndex())) {
templatePrevious = templateTmp;
}
}
}
}
int indexStart = (templatePrevious != null) ? templatePrevious.getEndIndex() : 0;
if ((indexStart == 0) && (talkPage.isRedirect())) {
indexStart = contents.length();
}
// Add warning
setText(getMessageUpdateWarning(talkPage.getTitle()));
StringBuilder tmp = new StringBuilder();
if (indexStart > 0) {
tmp.append(contents.substring(0, indexStart));
if (tmp.charAt(tmp.length() - 1) != '\n') {
tmp.append("\n");
}
}
tmp.append("{{");
tmp.append(todoTemplates.get(0));
tmp.append("|* ");
addWarning(tmp, pageRevId, elements);
tmp.append("}}");
if (indexStart < contents.length()) {
if (contents.charAt(indexStart) != '\n') {
tmp.append("\n");
}
tmp.append(contents.substring(indexStart));
}
String comment = getWarningComment(elements);
updateTalkPage(talkPage, tmp.toString(), comment);
// Inform creator and modifiers of the page
informContributors(analysis, elements, creator, modifiers);
return true;
}
// Search warning in the "To do" parameter
String parameter = templateTodo.getParameterValue("1");
if (parameter == null) {
parameter = "";
}
PageAnalysis parameterAnalysis = talkPage.getAnalysis(parameter, false);
PageElementTemplate templateWarning = getFirstWarningTemplate(parameterAnalysis);
if (templateWarning == null) {
StringBuilder tmp = new StringBuilder();
int indexStart = templateTodo.getBeginIndex();
if (indexStart > 0) {
tmp.append(contents.substring(0, indexStart));
if (tmp.charAt(tmp.length() - 1) != '\n') {
tmp.append("\n");
}
}
StringBuilder tmpParameter = new StringBuilder(parameter);
if ((tmpParameter.length() == 0) ||
(tmpParameter.charAt(tmpParameter.length() - 1) != '\n')) {
tmpParameter.append("\n");
}
tmpParameter.append("* ");
addWarning(tmpParameter, pageRevId, elements);
tmpParameter.append("\n");
tmp.append(templateTodo.getParameterReplacement("1", tmpParameter.toString(), null));
int indexEnd = templateTodo.getEndIndex();
if (indexEnd < contents.length()) {
if ((tmp.charAt(tmp.length() - 1) != '\n') &&
(contents.charAt(indexEnd) != '\n')) {
tmp.append("\n");
}
tmp.append(contents.substring(indexEnd));
}
String comment = getWarningComment(elements);
updateTalkPage(talkPage, tmp.toString(), comment);
return true;
}
// Update warning if necessary
if (isModified(elements, templateWarning)) {
StringBuilder tmp = new StringBuilder();
tmp.append(contents.substring(0, templateTodo.getBeginIndex()));
StringBuilder tmpParameter = new StringBuilder();
if (templateWarning.getBeginIndex() > 0) {
tmpParameter.append(parameter.substring(0, templateWarning.getBeginIndex()));
}
addWarning(tmpParameter, pageRevId, elements);
int endIndex = parameter.indexOf('\n', templateWarning.getEndIndex());
if ((endIndex >= 0) && (endIndex < parameter.length())) {
tmpParameter.append(parameter.substring(endIndex));
}
tmp.append(templateTodo.getParameterReplacement("1", tmpParameter.toString(), null));
if (templateTodo.getEndIndex() < contents.length()) {
tmp.append(contents.substring(templateTodo.getEndIndex()));
}
String comment = getWarningComment(elements);
updateTalkPage(talkPage, tmp.toString(), comment);
return true;
}
return false;
}
/**
* Remove warning on the "To do" sub-page.
*
* @param todoSubpage "To do" sub-page.
* @return True if the warning has been updated.
* @throws APIException
*/
private boolean removeWarningOnTodoSubpage(Page todoSubpage) throws APIException {
// Check if page is already empty
if ((todoSubpage == null) || (Boolean.FALSE.equals(todoSubpage.isExisting()))) {
return false;
}
String contents = todoSubpage.getContents();
if ((contents == null) || (contents.trim().equals(""))) {
return false;
}
PageAnalysis analysis = todoSubpage.getAnalysis(contents, true);
// Search warning in the "To do" sub-page
PageElementTemplate template = getFirstWarningTemplate(analysis);
if (template == null) {
return false;
}
// Analyze text to remove the warning
setText(getMessageRemoveWarning(todoSubpage.getTitle()));
StringBuilder tmp = new StringBuilder();
int index = template.getBeginIndex();
while ((index > 0) && (contents.charAt(index) != '\n')) {
index--;
}
if (index > 0) {
tmp.append(contents.substring(0, index));
}
index = template.getEndIndex();
while ((index < contents.length()) && (contents.charAt(index) != '\n')) {
index++;
}
if (index < contents.length()) {
if (tmp.length() > 0) {
tmp.append('\n');
}
tmp.append(contents.substring(index));
}
// Remove the warning
String newContents = tmp.toString();
String reason = getWarningCommentDone();
if ((newContents.trim().length() == 0) &&
(wiki.getConnection().getUser() != null) &&
(wiki.getConnection().getUser().hasRight(User.RIGHT_DELETE))) {
api.deletePage(wiki, todoSubpage, reason, automaticEdit);
} else {
if (newContents.trim().length() == 0) {
String delete = configuration.getString(WPCConfigurationString.TODO_SUBPAGE_DELETE);
if ((delete != null) && (delete.trim().length() > 0)) {
newContents = delete;
}
}
updatePage(todoSubpage, newContents, reason, false);
}
return true;
}
/**
* Remove warning on the talk page.
*
* @param talkPage Talk page.
* @param elements Elements for the warning.
* @return True if the warning has been updated.
* @throws APIException
*/
private boolean cleanWarningOnTalkPage(
Page talkPage, Collection<String> elements) throws APIException {
// Check if page exists
if (talkPage == null) {
return false;
}
List<String> todoTemplates = configuration.getStringList(WPCConfigurationStringList.TODO_TEMPLATES);
if ((todoTemplates == null) || (todoTemplates.isEmpty())) {
return false;
}
if (Boolean.FALSE.equals(talkPage.isExisting())) {
String comment = getWarningComment(elements);
String newContents = "{{" + todoTemplates.get(0) + "}}";
updateTalkPage(talkPage, newContents, comment);
return true;
}
String contents = talkPage.getContents();
if (contents == null) {
return false;
}
PageAnalysis analysis = talkPage.getAnalysis(contents, true);
// Search "To do" in the talk page
PageElementTemplate templateTodo = null;
for (String templateName : todoTemplates) {
List<PageElementTemplate> templates = analysis.getTemplates(templateName);
if ((templates != null) && (templates.size() > 0)) {
templateTodo = templates.get(0);
}
}
// If template is missing, verify that a link to the "To do" sub-page exists
if (templateTodo == null) {
// If link exists, nothing more to do
PageElementTemplate templateTodoLink = getExistingTemplateTodoLink(talkPage, contents);
if (templateTodoLink != null) {
return false;
}
// Search where to add "To do" template
PageElementTemplate templatePrevious = null;
List<String> warningAfterTemplates = configuration.getStringList(
WPCConfigurationStringList.WARNING_AFTER_TEMPLATES);
if (warningAfterTemplates != null) {
for (String previousTemplate : warningAfterTemplates) {
Collection<PageElementTemplate> templates = analysis.getTemplates(previousTemplate);
for (PageElementTemplate templateTmp : templates) {
if ((templatePrevious == null) ||
(templateTmp.getEndIndex() > templatePrevious.getEndIndex())) {
templatePrevious = templateTmp;
}
}
}
}
// Add warning
setText(getMessageUpdateWarning(talkPage.getTitle()));
StringBuilder tmp = new StringBuilder();
int indexStart = (templatePrevious != null) ? templatePrevious.getEndIndex() : 0;
if (indexStart > 0) {
tmp.append(contents.substring(0, indexStart));
if (tmp.charAt(tmp.length() - 1) != '\n') {
tmp.append("\n");
}
}
tmp.append("{{");
tmp.append(todoTemplates.get(0));
tmp.append("}}");
if (indexStart < contents.length()) {
if (contents.charAt(indexStart) != '\n') {
tmp.append("\n");
}
tmp.append(contents.substring(indexStart));
}
String comment = getWarningComment(elements);
updateTalkPage(talkPage, tmp.toString(), comment);
return true;
}
if (templateTodo.getParameterValue("1") == null) {
return false;
}
// Search warning in the "To do" parameter
String parameter = templateTodo.getParameterValue("1");
PageAnalysis parameterAnalysis = talkPage.getAnalysis(parameter, false);
PageElementTemplate templateWarning = getFirstWarningTemplate(parameterAnalysis);
if (templateWarning != null) {
setText(getMessageRemoveWarning(talkPage.getTitle()));
StringBuilder tmp = new StringBuilder();
if (templateTodo.getBeginIndex() > 0) {
tmp.append(contents.substring(0, templateTodo.getBeginIndex()));
}
String tmpParameter = "";
int index = templateWarning.getBeginIndex();
while ((index > 0) && (parameter.charAt(index) != '\n')) {
index--;
}
if (index > 0) {
tmpParameter += parameter.substring(0, index);
}
index = templateWarning.getEndIndex();
while ((index < parameter.length()) && (parameter.charAt(index) != '\n')) {
index++;
}
if (index < parameter.length()) {
if (tmpParameter.length() > 0) {
tmpParameter += "\n";
}
tmpParameter += parameter.substring(index);
}
if (tmpParameter.length() > 0) {
if ((tmp.length() > 0) && (tmp.charAt(tmp.length() - 1) != '\n')) {
tmp.append('\n');
}
tmp.append(templateTodo.getParameterReplacement("1", tmpParameter, null));
} else {
// Search "To do" link
PageElementTemplate templateTodoLink = null;
List<String> todoLinkTemplates = configuration.getStringList(WPCConfigurationStringList.TODO_LINK_TEMPLATES);
if (todoLinkTemplates != null) {
for (String templateName : todoLinkTemplates) {
List<PageElementTemplate> tmpTemplates = analysis.getTemplates(templateName);
if ((tmpTemplates != null) && (tmpTemplates.size() > 0)) {
templateTodoLink = tmpTemplates.get(0);
}
}
}
if (templateTodoLink == null) {
if ((tmp.length() > 0) && (tmp.charAt(tmp.length() - 1) != '\n')) {
tmp.append('\n');
}
tmp.append(templateTodo.getParameterReplacement("1", null, null));
}
}
if (templateTodo.getEndIndex() < contents.length()) {
if ((tmp.length() > 0) && (tmp.charAt(tmp.length() - 1) != '\n')) {
if (contents.charAt(templateTodo.getEndIndex()) != '\n') {
tmp.append('\n');
}
}
tmp.append(contents.substring(templateTodo.getEndIndex()));
}
String comment = getWarningComment(elements);
updateTalkPage(talkPage, tmp.toString(), comment);
return true;
}
return false;
}
/**
* Remove warning on the talk page.
*
* @param talkPage Talk page.
* @return True if the warning has been updated.
* @throws APIException
*/
private boolean removeWarningOnTalkPage(
Page talkPage) throws APIException {
// Check if page is already empty
if ((talkPage == null) || (Boolean.FALSE.equals(talkPage.isExisting()))) {
return false;
}
String contents = talkPage.getContents();
if (contents == null) {
return false;
}
PageAnalysis analysis = talkPage.getAnalysis(contents, true);
// Search "To do" in the talk page
PageElementTemplate templateTodo = null;
List<String> todoTemplates = configuration.getStringList(WPCConfigurationStringList.TODO_TEMPLATES);
if (todoTemplates != null) {
for (String templateName : todoTemplates) {
List<PageElementTemplate> templates = analysis.getTemplates(templateName);
if ((templates != null) && (templates.size() > 0)) {
templateTodo = templates.get(0);
}
}
}
if ((templateTodo != null) && (templateTodo.getParameterValue("1") != null)) {
// Search warning in the "To do" parameter
int parameterIndex = templateTodo.getParameterIndex("1");
String parameter = templateTodo.getParameterValue(parameterIndex);
int parameterStartIndex = templateTodo.getParameterValueStartIndex(parameterIndex);
PageElementTemplate templateWarning = getFirstWarningTemplate(analysis);
if (templateWarning != null) {
setText(getMessageRemoveWarning(talkPage.getTitle()));
StringBuilder tmp = new StringBuilder();
if (templateTodo.getBeginIndex() > 0) {
tmp.append(contents.substring(0, templateTodo.getBeginIndex()));
}
String tmpParameter = "";
int index = templateWarning.getBeginIndex() - parameterStartIndex;
while ((index > 0) && (parameter.charAt(index) != '\n')) {
index--;
}
if (index > 0) {
tmpParameter += parameter.substring(0, index);
}
index = templateWarning.getEndIndex() - parameterStartIndex;
while ((index < parameter.length()) && (parameter.charAt(index) != '\n')) {
index++;
}
if (index < parameter.length()) {
if (tmpParameter.length() > 0) {
tmpParameter += "\n";
}
tmpParameter += parameter.substring(index);
}
if (tmpParameter.length() > 0) {
tmp.append(templateTodo.getParameterReplacement("1", tmpParameter, null));
} else {
//
}
if (templateTodo.getEndIndex() < contents.length()) {
tmp.append(contents.substring(templateTodo.getEndIndex()));
}
String comment = getWarningCommentDone();
updateTalkPage(talkPage, tmp.toString(), comment);
return true;
}
}
return false;
}
/**
* Tell if the template should be modified.
*
* @param params List of parameters for the warning template.
* @param template Template.
* @return True if the template should be modified.
*/
private boolean isModified(Collection<String> params, PageElementTemplate template) {
// Check that parameters in template are still useful
int paramNum = 1;
while (template.getParameterValue(Integer.toString(paramNum)) != null) {
String param = template.getParameterValue(Integer.toString(paramNum)).trim();
if (!params.contains(param)) {
return true;
}
paramNum++;
}
// Check that current parameters are already in the template
for (String param : params) {
boolean found = false;
paramNum = 1;
while ((found == false) && (template.getParameterValue(Integer.toString(paramNum)) != null)) {
if (param.equals(template.getParameterValue(Integer.toString(paramNum)))) {
found = true;
}
paramNum++;
}
if (!found) {
return true;
}
}
return false;
}
/**
* Add a warning in a text.
*
* @param talkText Text in which the warning should be added.
* @param pageRevId Page revision id.
* @param params List of parameters for the warning template.
*/
private void addWarning(
StringBuilder talkText,
Integer pageRevId, Collection<String> params) {
talkText.append("{{ ");
talkText.append(configuration.getString(getWarningTemplate()));
if (pageRevId != null) {
talkText.append(" | revisionid=");
talkText.append(pageRevId);
}
for (String param : params) {
talkText.append(" | ");
talkText.append(param);
}
talkText.append(" }} -- ~~~~~");
String comment = configuration.getString(getWarningTemplateComment());
if (comment != null) {
talkText.append(" <!-- ");
talkText.append(comment);
talkText.append(" -->");
}
}
// ==========================================================================
// Page analysis
// ==========================================================================
/**
* Retrieve information in the pages to construct the warning.
*
* @param pages List of pages.
* @return True if information was retrieved.
* @throws APIException
*/
protected abstract boolean retrievePageInformation(
List<Page> pages) throws APIException;
/**
* Construct elements for the warning.
*
* @param analysis Page analysis.
* @param talkPage Talk page.
* @param todoSubpage to do sub-page.
* @return Warning elements.
*/
protected abstract Collection<String> constructWarningElements(
PageAnalysis analysis, Page talkPage, Page todoSubpage);
/**
* Memorize an error.
*
* @param error Error to memorize.
* @param title Page title in which the error is present.
*/
protected void memorizeError(String error, String title) {
if ((errorsMap == null) || (error == null) || (title == null)) {
return;
}
List<String> titles = errorsMap.get(error);
if (titles == null) {
titles = new ArrayList<String>();
errorsMap.put(error, titles);
}
titles.add(title);
}
/**
* @param talkPage Talk page
* @param contents Talk page contents.
* @return Template containing a list to the to do sub-page.
*/
private PageElementTemplate getExistingTemplateTodoLink(Page talkPage, String contents) {
PageElementTemplate templateTodoLink = null;
List<String> todoLinkTemplates = configuration.getStringList(WPCConfigurationStringList.TODO_LINK_TEMPLATES);
if (todoLinkTemplates != null) {
PageAnalysis analysis = talkPage.getAnalysis(contents, true);
for (String todoLink : todoLinkTemplates) {
List<PageElementTemplate> templates = analysis.getTemplates(todoLink);
if ((templates != null) && (templates.size() > 0)) {
templateTodoLink = templates.get(0);
}
}
}
return templateTodoLink;
}
/**
* @param analysis Page analysis.
* @return First warning template in the page.
*/
private final PageElementTemplate getFirstWarningTemplate(PageAnalysis analysis) {
PageElementTemplate template = null;
if (analysis != null) {
List<PageElementTemplate> templates = analysis.getTemplates(
configuration.getString(getWarningTemplate()));
if ((templates != null) && (!templates.isEmpty())) {
template = templates.get(0);
}
}
return template;
}
// ==========================================================================
// Configuration
// ==========================================================================
/**
* @return Configuration parameter for the warning template.
*/
protected abstract WPCConfigurationString getWarningTemplate();
/**
* @return Configuration parameter for the warning template comment.
*/
protected abstract WPCConfigurationString getWarningTemplateComment();
/**
* @return Configuration parameter for the title for a message for a new article.
*/
protected WPCConfigurationString getMessageTitleNewArticle() {
return null;
}
/**
* @return Configuration parameter for the title for a message for a new article.
*/
protected WPCConfigurationString getMessageTitleNewArticleModified() {
return null;
}
/**
* @return Configuration parameter for the title for a message for a new article.
*/
protected WPCConfigurationString getMessageTitleNewArticleModifier() {
return null;
}
/**
* @return Configuration parameter for the template for a message for a new article.
*/
protected WPCConfigurationString getMessageTemplateNewArticle() {
return null;
}
/**
* @return Configuration parameter for the template for a message for a new article.
*/
protected WPCConfigurationString getMessageTemplateNewArticleModified() {
return null;
}
/**
* @return Configuration parameter for the template for a message for a new article.
*/
protected WPCConfigurationString getMessageTemplateNewArticleModifier() {
return null;
}
/**
* @return True if section 0 of the talk page should be used.
*/
protected abstract boolean useSection0();
/**
* @return Comment when warning is removed.
*/
protected abstract String getWarningCommentDone();
/**
* @param elements Message elements.
* @return Comment when warning is added or updated.
*/
protected abstract String getWarningComment(Collection<String> elements);
/**
* @param title Page title.
* @return Message displayed when removing the warning from the page.
*/
protected abstract String getMessageRemoveWarning(String title);
/**
* @param title Page title.
* @return Message displayed when updating the warning from the page.
*/
protected abstract String getMessageUpdateWarning(String title);
// ==========================================================================
// Contributors management
// ==========================================================================
/**
* Inform page contributors.
*
* @param analysis Page analysis.
* @param msgElements Message elements.
* @param creator User who has created the page.
* @param modifiers Other contributors to the page.
*/
private void informContributors(
PageAnalysis analysis,
Collection<String> msgElements,
String creator,
List<String> modifiers) {
if (analysis == null) {
return;
}
if (creator != null) {
if ((modifiers == null) || (modifiers.isEmpty())) {
addMessage(
analysis, msgElements, creator,
getMessageTitleNewArticle(),
getMessageTemplateNewArticle());
} else {
addMessage(
analysis, msgElements, creator,
getMessageTitleNewArticleModified(),
getMessageTemplateNewArticleModified());
}
}
if (modifiers != null) {
for (String modifier : modifiers) {
addMessage(
analysis, msgElements, modifier,
getMessageTitleNewArticleModifier(),
getMessageTemplateNewArticleModifier());
}
}
}
/**
* Add a message on user talk page.
*
* @param analysis Page analysis.
* @param msgElements Message elements.
* @param user User to inform.
* @param titleParam Parameter for the title of the new section.
* @param templateParam Parameter for the template used to inform.
*/
private void addMessage(
PageAnalysis analysis, Collection<String> msgElements,
String user,
WPCConfigurationString titleParam,
WPCConfigurationString templateParam) {
if ((analysis == null) || (user == null)) {
return;
}
String article = analysis.getPage().getTitle();
WPCConfiguration wpcConfig = analysis.getWPCConfiguration();
// Prepare elements
String message = createMessage(article, msgElements, wpcConfig, templateParam);
if ((message == null) || (message.trim().length() == 0)) {
return;
}
String globalListTemplate = wpcConfig.getString(WPCConfigurationString.MSG_GLOBAL_LIST_TEMPLATE);
String globalTemplate = wpcConfig.getString(WPCConfigurationString.MSG_GLOBAL_TEMPLATE);
String globalTitle = wpcConfig.getString(WPCConfigurationString.MSG_GLOBAL_TITLE);
String title = wpcConfig.getString(titleParam);
if (title != null) {
try {
title = MessageFormat.format(title, article);
} catch (IllegalArgumentException e) {
log.warn("Parameter " + titleParam.getAttributeName() + " has an incorrect format");
}
}
Configuration config = Configuration.getConfiguration();
String signature = config.getString(null, ConfigurationValueString.SIGNATURE);
// Retrieve user talk page name
Namespace userTalkNS = wiki.getWikiConfiguration().getNamespace(Namespace.USER_TALK);
String userTalk = userTalkNS.getTitle() + ":" + user;
Page userTalkPage = DataManager.getPage(analysis.getWikipedia(), userTalk, null, null, null);
// Add message
try {
if (globalTitle != null) {
// Check if global title already exists in the talk page
List<Section> sections = api.retrieveSections(wiki, userTalkPage);
Section section = null;
if (sections != null) {
for (Section tmpSection : sections) {
if (globalTitle.equals(tmpSection.getLine())) {
section = tmpSection;
}
}
}
if (section == null) {
// Add the title
StringBuilder fullMessage = new StringBuilder();
if ((globalTemplate != null) && (globalTemplate.trim().length() > 0)) {
fullMessage.append("{{");
fullMessage.append(globalTemplate.trim());
fullMessage.append("}}\n");
if ((signature != null) && (signature.trim().length() > 0)) {
fullMessage.append(signature.trim());
fullMessage.append("\n\n");
}
}
if ((globalListTemplate != null) && (globalListTemplate.trim().length() > 0)) {
fullMessage.append("{{");
fullMessage.append(globalListTemplate.trim());
fullMessage.append("}}\n");
}
if (title != null) {
fullMessage.append("== ");
fullMessage.append(title);
fullMessage.append(" ==\n");
}
fullMessage.append(message);
api.addNewSection(
wiki, userTalkPage, globalTitle,
fullMessage.toString(), automaticEdit, false);
} else {
// Add the message in the existing title
Integer revisionId = userTalkPage.getRevisionId();
api.retrieveSectionContents(wiki, userTalkPage, section.getIndex());
if (revisionId.equals(userTalkPage.getRevisionId())) {
StringBuilder fullMessage = new StringBuilder();
fullMessage.append(userTalkPage.getContents());
if (fullMessage.charAt(fullMessage.length() - 1) != '\n') {
fullMessage.append("\n");
}
fullMessage.append(message);
api.updateSection(
wiki, userTalkPage, globalTitle, section.getIndex(),
fullMessage.toString(), automaticEdit, false);
} else {
System.err.println("Page " + userTalk + " has been modified between two requests");
}
}
} else {
if (title != null) {
api.addNewSection(
wiki, userTalkPage, title,
message, automaticEdit, false);
} else {
// TODO: No global title, no title => Should append the message at the end
log.warn("Should add " + message + " in " + userTalk);
}
}
} catch (APIException e) {
//
}
}
/**
* Create a message that should be added on user talk page.
*
* @param article Article.
* @param msgElements Message elements.
* @param wpcConfig Configuration.
* @param templateParam Parameter for the template used to inform.
*/
private String createMessage(
String article, Collection<String> msgElements,
WPCConfiguration wpcConfig,
WPCConfigurationString templateParam) {
String[] templateElements = wpcConfig.getStringArray(templateParam);
if ((templateElements == null) ||
(templateElements.length == 0) ||
(templateElements[0].trim().length() == 0)) {
return null;
}
StringBuilder message = new StringBuilder();
message.append("{{");
message.append(templateElements[0].trim());
if ((templateElements.length > 1) && (templateElements[1].trim().length() > 0)) {
message.append("|");
message.append(templateElements[1].trim());
message.append("=");
message.append(article);
}
if ((templateElements.length > 2) && (templateElements[2].trim().length() > 0)) {
String wpcUser = wpcConfig.getString(WPCConfigurationString.USER);
if ((wpcUser != null) && (wpcUser.trim().length() > 0)) {
message.append("|");
message.append(templateElements[2].trim());
message.append("=");
message.append(wpcUser);
}
}
if (msgElements != null) {
for (String msgElement : msgElements) {
message.append("|");
message.append(msgElement);
}
}
message.append("}}");
return message.toString();
}
// ==========================================================================
// Utility methods
// ==========================================================================
/**
* Update a talk page on Wiki.
*
* @param page Page.
* @param newContents New contents to use.
* @param comment Comment.
* @return Result of the command.
* @throws APIException
*/
protected QueryResult updateTalkPage(
Page page, String newContents,
String comment) throws APIException {
if (section0) {
return updateSection(page, comment, 0, newContents, false);
}
return updatePage(page, newContents, comment, false);
}
/**
* Update a page on Wiki.
*
* @param page Page.
* @param newContents New contents to use.
* @param comment Comment.
* @param forceWatch Force watching the page.
* @return Result of the command.
* @throws APIException
*/
protected QueryResult updatePage(
Page page,
String newContents, String comment,
boolean forceWatch) throws APIException {
return api.updatePage(
wiki, page, newContents,
comment, automaticEdit, forceWatch);
}
/**
* Update a section in a page.
*
* @param page Page.
* @param title Title of the new section.
* @param section Section.
* @param contents Contents.
* @param forceWatch Force watching the page.
* @return Result of the command.
* @throws APIException
*/
protected QueryResult updateSection(
Page page, String title, int section,
String contents, boolean forceWatch) throws APIException {
return api.updateSection(
wiki, page, title, section,
contents, automaticEdit, forceWatch);
}
/**
* Purge a page cache.
*
* @param page Page.
* @throws APIException
*/
protected void purgePage(Page page) throws APIException {
api.purgePageCache(wiki, page);
}
/**
* @param articles List of articles.
*/
void setArticles(Set<String> articles) {
this.articles = new HashSet<String>();
if (articles != null) {
this.articles.addAll(articles);
}
}
/**
* @param article Article.
*/
void addArticle(String article) {
if (articles == null) {
articles = new HashSet<String>();
}
articles.add(article);
}
/**
* Purge an article when error is not found but article is still listed.
*
* @param page Article to purge.
* @throws APIException
*/
protected void purgeArticle(Page page) throws APIException {
if (!usePurge) {
return;
}
if ((page == null) || (articles == null)) {
return;
}
if (articles.contains(page.getTitle())) {
api.purgePageCache(wiki, page);
}
}
/**
* @return True if the analyze should stop.
*/
protected boolean shouldStop() {
if (window == null) {
return false;
}
if ((window.getParentComponent() == null) ||
(window.getParentComponent().isDisplayable() == false)) {
return true;
}
return false;
}
/**
* Display text.
*
* @param text Text to display.
*/
protected void setText(String text) {
if (worker != null) {
worker.setText(text);
}
}
/**
* Extract a sub list of pages from a list.
*
* @param list List (extracted pages are removed from the list).
* @param max Maximum number of pages.
* @param talkPages True if talk pages should be included.
* @return Sub list of pages.
*/
public List<Page> extractSublist(LinkedList<Page> list, int max, boolean talkPages) {
if (list == null) {
return null;
}
List<Page> sublist = new ArrayList<Page>(Math.min(max, list.size()));
while ((sublist.size() < max) && !list.isEmpty()) {
Page page = list.removeFirst();
if (talkPages || page.isArticle()) {
sublist.add(page);
}
}
return sublist;
}
// ==========================================================================
// Statistics
// ==========================================================================
/** Bean for holding statistics. */
public static class Stats {
/**
* Count of analyzed pages.
*/
private int analyzedPagesCount;
/**
* List of updated pages.
*/
private List<Page> updatedPages;
/**
* Count of warnings that have been removed.
*/
private int removedWarningsCount;
/**
* Count of links.
*/
private int linksCount;
public Stats() {
analyzedPagesCount = 0;
updatedPages = new ArrayList<Page>();
}
void addAnalyzedPage(Page page) {
if (page != null) {
analyzedPagesCount++;
}
}
public int getAnalyedPagesCount() {
return analyzedPagesCount;
}
void addUpdatedPage(Page page) {
if (page != null) {
updatedPages.add(page);
}
}
public List<Page> getUpdatedPages() {
return updatedPages;
}
public int getUpdatedPagesCount() {
return (updatedPages != null) ? updatedPages.size() : 0;
}
public int getRemovedWarningsCount() {
return removedWarningsCount;
}
void addRemovedWarning(Page page) {
if (page != null) {
removedWarningsCount++;
}
}
public int getLinksCount() {
return linksCount;
}
void addLinks(Page page, int count) {
if (page != null) {
linksCount += count;
}
}
}
/**
* Display statistics.
*
* @param window Window.
* @param stats Statistics.
* @param startTime Start time.
*/
public static void displayStats(
BasicWindow window,
Stats stats, long startTime) {
if (window == null) {
return;
}
long endTime = System.currentTimeMillis();
StringBuilder message = new StringBuilder();
message.append(GT.__(
"{0} page has been analyzed.",
"{0} pages have been analyzed.",
stats.getAnalyedPagesCount(), Integer.toString(stats.getAnalyedPagesCount())));
message.append("\n");
message.append(GT.__(
"Warning has been updated on {0} page.",
"Warnings have been updated on {0} pages.",
stats.getUpdatedPagesCount(), Integer.toString(stats.getUpdatedPagesCount())));
message.append("\n");
message.append(GT.__(
"Warning has been removed on {0} page.",
"Warnings have been removed on {0} pages.",
stats.getRemovedWarningsCount(), Integer.toString(stats.getRemovedWarningsCount())));
message.append("\n");
message.append(GT.__(
"{0} still needs to be fixed.",
"{0} still need to be fixed.",
stats.getLinksCount(), Integer.toString(stats.getLinksCount())));
message.append("\n");
long time = (endTime - startTime) / 1000;
message.append(GT.__(
"It took {0} second", "It took {0} seconds", time, Long.toString(time)));
Utilities.displayInformationMessage(
window.getParentComponent(), message.toString());
}
}