MediaWiki:Common.js/Clases/UploadValidator.js
Nota: Después de publicar, quizás necesite actualizar la caché de su navegador para ver los cambios.
- Firefox/Safari: Mantenga presionada la tecla Shift mientras pulsa el botón Actualizar, o presiona Ctrl+F5 o Ctrl+R (⌘+R en Mac)
- Google Chrome: presione Ctrl+Shift+R (⌘+Shift+R en Mac)
- Internet Explorer/Edge: mantenga presionada Ctrl mientras pulsa Actualizar, o presione Ctrl+F5
- Opera: Presiona Ctrl+F5.
/* <pre>
* UploadValidator v4.1: Realiza validaciones en el momento de subir archivos, proporcionando sugerencias de nombrado si
* es posible, categorización o licencia.
* Copyright (c) 2010 - 2014 Jesús Martínez (User:Ciencia_Al_Poder)
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version
*
* @requires: jquery.ui.dialog
*/
window.UploadValidator = (function($, mw) {
'use strict';
var _reTemplates = /\{\{\s*([^\{\}\|]+)\s*(\|[^\}]+)?\}\}/g,
_reCategories = null,
_reTitleWhiteSpace = /[ _]+/g,
_validators = [],
_results = null,
_currentIndex = 0,
_validationParams = null,
_messages = {},
_dlg = null,
_closing = false,
_init = function() {
_initRegExp();
},
// Logging utility
_log = function(text) {
if (window.console && window.console.log) {
window.console.log('UploadValidator: ' + text);
}
},
// Inits category regexp to find categories present on the description text
_initRegExp = function() {
var catNS = 'Category';
if (mw.config.get('wgContentLanguage') != 'en' && mw.config.get('wgFormattedNamespaces')) {
catNS += '|' + mw.config.get('wgFormattedNamespaces')['14'];
}
_log('catNS: '+catNS);
_reCategories = new RegExp('\\[\\[\\s*('+catNS+')\\s*:\\s*([^\\|\\[\\]]+)\\]\\]', 'gi');
},
_validate = function(params) {
var done;
_validationParams = params;
_log('Start validation.');
// Nothing to do if there are no validators registered, or no sources were provided
if (_validators.length === 0 || !params.sources || params.sources.length === 0) {
_log('No validators registered, or no sources provided.');
_endValidation(true);
return true;
}
_sortValidators();
_results = [];
_currentIndex = 0;
// Runs validators for each source
done = _runValidators();
if (done) {
return _checkFinalStatus();
}
},
// Checks if it needs to display validation issues or end the validation
_checkFinalStatus = function() {
// There were no async validations
if (_results.length === 0) {
_endValidation(true);
return true;
}
_log('Displaying validation issues.');
mw.loader.using('jquery.ui.dialog', function() {
// Display first validation
_showDlg(_results[_currentIndex]);
});
return false;
},
_makeResumeCallback = function(resume_file, resume_validation, resume_originaltitle) {
return function(vi) {
var done;
_log('Async validation ended. Resuming validations.');
done = _runValidators(resume_file, resume_validation, resume_originaltitle, vi);
if (done) {
_checkFinalStatus();
}
};
},
// Run validators against upload information
_runValidators = function(resume_file, resume_validation, resume_originaltitle, resume_vi) {
var ui, validator, validationinfo, originaltitle;
// Runs validators for each source
for (var i = (resume_file || 0); i < _validationParams.sources.length; i++) {
_log('Validating file '+i);
ui = _getUploadInfoToValidate(i);
// Hard validations: It must contain a title and file
if (!ui.title) {
// Clear any existing validations
_results = [];
_results.push(_getMsg('notitle'));
return true;
} else if (!ui.filename) {
// Clear any existing validations
_results = [];
_results.push(_getMsg('nofilename', [ ui.title ]));
return true;
}
originaltitle = resume_originaltitle || ui.title;
for (var vPos = (resume_validation || 0); vPos < _validators.length; vPos++) {
validator = _validators[vPos];
if (typeof resume_validation != 'undefined') {
validationinfo = resume_vi;
resume_file = undefined;
resume_validation = undefined;
resume_originaltitle = undefined;
resume_vi = undefined;
} else {
validationinfo = validator.validate(ui);
}
if (!validationinfo) {
continue;
}
// Check for async validators
if (typeof validationinfo == 'function') {
_log('Performing async validation.');
// They return a function. Call it providing a callback
validationinfo(_makeResumeCallback(i, vPos, originaltitle));
return false;
}
// Validators with negative priority perform only minor name corrections
if (validator.priority < 0) {
if (validationinfo.title) {
ui.title = validationinfo.title;
}
} else {
// If upload is disallowed, display dialog here and abort
if (validationinfo.disallow) {
// We set this property to keep the original title to display to the user
validationinfo.originaltitle = originaltitle;
validationinfo.sourceindex = i;
// Clear any existing validations
_results = [];
_results.push(validationinfo);
return true;
}
// If there's any change, return it
if (validationinfo.title || validationinfo.description || validationinfo.added_categories || validationinfo.removed_categories ||
validationinfo.added_templates || validationinfo.removed_templates || validationinfo.license || validationinfo.note) {
// We set this property to keep the original title to display to the user
validationinfo.originaltitle = originaltitle;
// Set source index and save to results
validationinfo.sourceindex = i;
_results.push(validationinfo);
// Skip further validators
break;
}
}
}
if (originaltitle != ui.title) {
// Perform title autocorrection
_validationParams.sources[i].inputName.val(ui.title);
}
}
return true;
},
// Generates an object with all the information of an upload to run the validators
_getUploadInfoToValidate = function(index) {
var ui = {title: null, filename: null, description: null, categories: [], templates: [], license: null}, arTestRE;
if (_validationParams.sources[index].inputName) {
ui.title = _normalizeTitle($(_validationParams.sources[index].inputName).val());
}
if (_validationParams.sources[index].inputFile) {
ui.filename = $(_validationParams.sources[index].inputFile).val();
}
if (_validationParams.sources[index].inputDesc) {
ui.description = $(_validationParams.sources[index].inputDesc).val();
}
if (_validationParams.commonDesc) {
ui.description += (ui.description ? '\n' : '') + $(_validationParams.commonDesc).val();
}
if (ui.description) {
// Reset regexp match
_reTemplates.lastIndex = 0;
// Find templates
while ((arTestRE = _reTemplates.exec(ui.description)) !== null) {
ui.templates[ui.templates.length] = _normalizeTitle(arTestRE[1]);
}
// Reset regexp match
_reCategories.lastIndex = 0;
// Find categories
while ((arTestRE = _reCategories.exec(ui.description)) !== null) {
ui.categories[ui.categories.length] = _normalizeTitle(arTestRE[2]);
}
}
if (_validationParams.license) {
ui.license = $(_validationParams.license).val();
}
return ui;
},
// Normalizes a MediaWiki title
_normalizeTitle = function(title) {
// Sets first character to uppercase, and replaces underscores by spaces
return title.substr(0,1).toUpperCase() + title.substr(1).replace(_reTitleWhiteSpace, ' ');
},
// Sorts the validator list by priority
_sortValidators = function() {
var priority, reorder;
if (_validators.length === 0) {
return;
}
// Check first if they're already ordered, so we don't need to reorder them again if the list of validators hasn't changed
reorder = false;
priority = _validators[0];
for (var i = 1; i < _validators.length; i++) {
if (_validators[i].priority < priority) {
reorder = true;
break;
}
priority = _validators[i].priority;
}
if (reorder) {
_validators.sort(_validatorComparePriority);
}
},
// Compares priority between 2 validators. Used as a sort callback function
_validatorComparePriority = function(a, b) {
if (a.priority == b.priority) {
return 0;
}
return a.priority > b.priority ? 1 : -1;
},
// registers one validator
_registerSingleValidator = function(validator) {
if (!validator.name || typeof validator.validate != 'function') return;
for (var i = 0; i < _validators.length; i++) {
if (_validators[i].name === validator.name) {
_validators[i] = validator;
return;
}
}
_validators[_validators.length] = validator;
},
// Registers validators (single or array)
_registerValidators = function(validators) {
if (validators instanceof Array) {
for (var i = 0; i < validators.length; i++) {
_registerSingleValidator(validators[i]);
}
} else {
_registerSingleValidator(validators);
}
},
/*
* Sets the list of messages used in dialogs, etc
* */
_setMessages = function(msgs) {
if (msgs) {
_messages = msgs;
}
},
/*
* Displays a modal dialog
* @param validationresult [object/string]: validation result or string with text to display
* */
_showDlg = function(vi) {
var body, buttons = {};
if (typeof vi == 'string') {
body = $('<div>').text(vi);
buttons[_getMsg('backtoform')] = _closeDlg;
} else if (vi.disallow) {
body = $('<div>').text(_getMsg('disallowed', [vi.originaltitle])).append($('<span>').text(vi.disallow));
buttons[_getMsg('backtoform')] = _closeDlg;
} else {
body = _buildValidationForm(vi);
if (vi.title || vi.license || vi.description || (vi.added_categories && vi.added_categories.length) ||
(vi.removed_categories && vi.removed_categories.length) || (vi.added_templates && vi.added_templates.length) ||
(vi.removed_templates && vi.removed_templates.length)
) {
buttons[_getMsg('acceptproposal')] = _acceptProposal;
buttons[_getMsg('declineproposal')] = _declineProposal;
} else {
// If nothing to do, just display the note with no choice to accept/decline
buttons[_getMsg('continueupload')] = _declineProposal;
}
buttons[_getMsg('backtoform')] = _closeDlg;
}
_closing = false;
if (_dlg) {
_dlg.empty().append(body).dialog('open').dialog('option', {
height: 'auto',
position: 'center',
buttons: buttons
});
} else {
_dlg = $('<div id="UploadValidatorDlg"></div>').append(body).appendTo(document.body).dialog({
modal: true,
buttons: buttons,
title: _getMsg('dialogtitle'),
width: 750,
close: function() {
if (!_closing) {
_endValidation(false);
}
}
});
}
},
_closeDlg = function() {
if (_dlg) {
_dlg.dialog('close');
}
},
_buildValidationForm = function(vi) {
var $body = $('<div>'), $ul = $('<ul>'), i;
if (vi.title) {
$ul.append(_buildUIItem(_getMsg('titlechange', [vi.title]), 'title'));
}
if (vi.license) {
$ul.append(_buildUIItem(_getMsg('licensechange', [vi.license]), 'license'));
}
if (vi.description) {
$ul.append(_buildUIItem(_getMsg('descriptionchange', [vi.description]), 'description'));
} else {
if (vi.added_categories) {
for (i = 0; i < vi.added_categories.length; i++) {
$ul.append(_buildUIItem(_getMsg('categoryaddchange', [vi.added_categories[i]]), 'category-add-'+i.toString()));
}
}
if (vi.removed_categories) {
for (i = 0; i < vi.removed_categories.length; i++) {
$ul.append(_buildUIItem(_getMsg('categoryremchange', [vi.removed_categories[i]]), 'category-rem-'+i.toString()));
}
}
if (vi.added_templates) {
for (i = 0; i < vi.added_templates.length; i++) {
$ul.append(_buildUIItem(_getMsg('templateaddchange', [vi.added_templates[i]]), 'template-add-'+i.toString()));
}
}
if (vi.removed_templates) {
for (i = 0; i < vi.removed_templates.length; i++) {
$ul.append(_buildUIItem(_getMsg('templateremchange', [vi.removed_templates[i]]), 'template-rem-'+i.toString()));
}
}
}
if ($ul.children().length) {
$body.append($('<p>').text(_getMsg('filetypedescr', [vi.originaltitle, vi.filetype])));
$body.append($ul);
if (vi.note) {
$body.append($('<p>').text(_getMsg('note', [vi.note])));
}
} else if (vi.note) {
$body.append($('<p>').text(_getMsg('noteonly', [vi.originaltitle, vi.filetype, vi.note])));
}
return $body;
},
_buildUIItem = function(text, prop) {
var $li = $('<li>'), $label = $('<label>'), $input = $('<input type="checkbox" checked="checked">');
$label.text(text).appendTo($li);
$input.attr({'name': prop}).prependTo($label);
return $li;
},
/*
* Stores on results, in a accepted property, what checkboxes are active on the form
* */
_storeUserChoice = function() {
var res = _results[_currentIndex], $inputs = _dlg.find('input'), nameparts, field;
res.accepted = { added_categories: [], removed_categories: [], added_templates: [], removed_templates: [] };
for (var i = 0; i < $inputs.length; i++) {
if (!$inputs[i].checked) {
continue;
}
nameparts = $inputs[i].name.split('-');
if (nameparts[0] == 'title' || nameparts[0] == 'license' || nameparts[0] == 'description') {
res.accepted[nameparts[0]] = true;
} else {
field = nameparts[1] == 'add' ? 'added_' : 'removed_';
field += nameparts[0] == 'category' ? 'categories' : 'templates';
res.accepted[field][parseInt(nameparts[2], 10)] = true;
}
}
},
/*
* Accepts the proposed changes. If there are more files to validate, goes to the next one. Otherwise, apply changes
* */
_acceptProposal = function() {
_storeUserChoice();
if (_currentIndex < _results.length - 1) {
_currentIndex++;
_showDlg(_results[_currentIndex]);
} else {
_closing = true;
_closeDlg();
_applyChanges();
}
},
/*
* Declines the proposed changes. If there are more files to validate, goes to the next one. Otherwise, apply changes
* */
_declineProposal = function() {
_results[_currentIndex].accepted = { added_categories: [], removed_categories: [], added_templates: [], removed_templates: [] };
if (_currentIndex < _results.length - 1) {
_currentIndex++;
_showDlg(_results[_currentIndex]);
} else {
_closing = true;
_closeDlg();
_applyChanges();
}
},
/*
* Apply changes and calls the validation callback
* */
_applyChanges = function() {
var commonLicense = null, tmpLicense, currentCommonLicense, res, desc, arTestRE, needsIndividualDescriptions = false, hasValidation;
// Check license in case we could set a common one
currentCommonLicense = _validationParams.license.val();
for (var i = 0; i < _results.length; i++) {
res = _results[i];
// We start with the common license
tmpLicense = currentCommonLicense;
// If a validation changes license, use that
if (res.license && res.accepted.license) {
tmpLicense = res.license;
// If there are unmodified files, that license won't be changed, so we can't set the common license if it changes for some files
if (_results.length != _validationParams.sources.length) {
commonLicense = null;
break;
}
}
if (commonLicense === null) {
// First time we pass here: set our current "common" license
commonLicense = tmpLicense;
} else if (commonLicense != tmpLicense) {
// If there are different licenses, we can't specify a common one
commonLicense = null;
break;
}
}
// If there's no common license, unset it
if (commonLicense === null) {
_validationParams.license.val('');
needsIndividualDescriptions = true;
}
if (_validationParams.commonDesc && _validationParams.commonDesc.val()) {
if (!needsIndividualDescriptions) {
// Check if there are template, category or description changes. That will require individual descriptions,
// so we don't have to keep track of changes in common description and individual descriptions
for (var ir = 0; ir < _results.length; ir++) {
res = _results[ir];
if (res.description) {
if (res.accepted.description) {
needsIndividualDescriptions = true;
break;
}
} else {
// added_* properties aren't checked because they don't alter the common description
if (res.removed_categories && res.accepted.removed_categories) {
for (var irc1 = 0; irc1 < res.removed_categories.length; irc1++) {
if (res.accepted.removed_categories[irc1]) {
needsIndividualDescriptions = true;
break;
}
}
}
if (!needsIndividualDescriptions && res.removed_templates && res.accepted.removed_templates) {
for (var irt1 = 0; irt1 < res.removed_templates.length; irt1++) {
if (res.accepted.removed_templates[irt1]) {
needsIndividualDescriptions = true;
break;
}
}
}
}
}
}
if (needsIndividualDescriptions) {
// In that case, copy the common description to each individual description
for (var is = 0; is < _validationParams.sources.length; is++) {
_validationParams.sources[is].inputDesc.val(_validationParams.sources[is].inputDesc.val() + '\n' + _validationParams.commonDesc.val());
}
_validationParams.commonDesc.val('');
}
}
for (var ir2 = 0; ir2 < _results.length; ir2++) {
res = _results[ir2];
if (res.title) {
if (res.accepted.title) {
_validationParams.sources[res.sourceindex].inputName.val(res.title);
} else {
_appendToTextField(_validationParams.sources[res.sourceindex].inputDesc, _getMsg('titlechangedeclined', [ res.title ]));
}
}
if (res.license && res.accepted.license && commonLicense === null) {
_appendToTextField(_validationParams.sources[res.sourceindex].inputDesc, '\n' + _getMsg('licenseInsertText', [ '{{' + res.license + '}}' ]));
} else if (commonLicense === null && currentCommonLicense) {
// Restore license for this file if we're clearing the common license
_appendToTextField(_validationParams.sources[res.sourceindex].inputDesc, '\n' + _getMsg('licenseInsertText', [ '{{' + currentCommonLicense + '}}' ]));
}
if (res.description) {
if (res.accepted.description) {
_validationParams.sources[res.sourceindex].inputDesc.val(res.description);
}
} else {
if (res.removed_categories) {
for (var irc = 0; irc < res.removed_categories.length; irc++) {
if (res.accepted.removed_categories[irc]) {
// Reset regexp match
_reCategories.lastIndex = 0;
// Find categories
desc = _validationParams.sources[res.sourceindex].inputDesc.val();
while ((arTestRE = _reCategories.exec(desc)) !== null) {
if (_normalizeTitle(arTestRE[2]) == res.removed_categories[irc]) {
desc = desc.substr(0, _reCategories.lastIndex - arTestRE[0].length) + desc.substr(_reCategories.lastIndex);
_reCategories.lastIndex -= arTestRE[0].length;
}
}
_validationParams.sources[res.sourceindex].inputDesc.val(desc);
} else {
_appendToTextField(_validationParams.sources[res.sourceindex].inputDesc, _getMsg('categoryremdeclined', [ res.removed_categories[irc] ]));
}
}
}
if (res.added_categories) {
for (var iac = 0; iac < res.added_categories.length; iac++) {
if (res.accepted.added_categories[iac]) {
_appendToTextField(_validationParams.sources[res.sourceindex].inputDesc, '[[' + mw.config.get('wgFormattedNamespaces')['14'] + ':' + res.added_categories[iac] + ']]');
} else {
_appendToTextField(_validationParams.sources[res.sourceindex].inputDesc, _getMsg('categoryadddeclined', [ res.added_categories[iac] ]));
}
}
}
if (res.removed_templates) {
for (var irt = 0; irt < res.removed_templates.length; irt++) {
if (res.accepted.removed_templates[irt]) {
// Reset regexp match
_reTemplates.lastIndex = 0;
// Find templates
desc = _validationParams.sources[res.sourceindex].inputDesc.val();
while ((arTestRE = _reTemplates.exec(desc)) !== null) {
if (_normalizeTitle(arTestRE[1]) == res.removed_templates[irt]) {
desc = desc.substr(0, _reTemplates.lastIndex - arTestRE[0].length) + desc.substr(_reTemplates.lastIndex);
_reCategories.lastIndex -= arTestRE[0].length;
}
}
_validationParams.sources[res.sourceindex].inputDesc.val(desc);
} else {
_appendToTextField(_validationParams.sources[res.sourceindex].inputDesc, _getMsg('templateremdeclined', [ res.removed_templates[irt] ]));
}
}
}
if (res.added_templates) {
for (var iat = 0; iat < res.added_templates.length; iat++) {
if (res.accepted.added_templates[iat]) {
_appendToTextField(_validationParams.sources[res.sourceindex].inputDesc, '{{' + res.added_templates[iat] + '}}');
} else {
_appendToTextField(_validationParams.sources[res.sourceindex].inputDesc, _getMsg('templateadddeclined', [ res.added_templates[iat] ]));
}
}
}
}
}
if (commonLicense) {
// Apply common license
_validationParams.license.val(commonLicense);
} else if (commonLicense === null && currentCommonLicense) {
// Restore license for files not in validation if we're clearing the common license
for (var is2 = 0; is2 < _validationParams.sources.length; is2++) {
hasValidation = false;
for (var jr2 = 0; jr2 < _results.length; jr2++) {
if (_results[jr2].sourceindex == is2) {
hasValidation = true;
break;
}
}
if (!hasValidation) {
_appendToTextField(_validationParams.sources[i].inputDesc, '\n' + _getMsg('licenseInsertText', [ '{{' + currentCommonLicense + '}}' ]));
}
}
}
_endValidation(true);
},
/*
* Appends text to a text field
* */
_appendToTextField = function($field, newtext) {
var text = $field.val();
if (newtext) {
if (text) {
$field.val(text + newtext);
} else {
$field.val(newtext);
}
}
},
/*
* Sends the form when done
* */
_endValidation = function(success) {
_log('End validation (' + success + ')');
if (_validationParams.callback) {
_validationParams.callback(success);
}
},
/*
* Gets the message contents
* */
_getMsg = function(msg, vars, htmlencode) {
var text;
if (!(msg in _messages)) {
text = '<' + msg + '>';
} else {
text = _messages[msg];
}
if (vars) {
for (var i = 0; i < vars.length; i++) {
text = text.replace('$' + (i+1).toString(), vars[i]);
}
}
if (htmlencode) {
text = text.replace(/</g, '<').replace(/>/g, '>:').replace(/"/g, '"');
}
return text;
};
$(_init);
return {
registerValidators: _registerValidators,
setMessages: _setMessages,
validate: _validate
};
})(jQuery, mw);
window.UploadValidator.setMessages({
dialogtitle: 'Validación de subida de archivos',
disallowed: 'El archivo $1 no puede subirse por el siguiente motivo: ',
notitle: 'Debes indicar el nombre del archivo destino.',
nofilename: 'Debes seleccionar un archivo para subir $1.',
backtoform: 'Cancelar',
filetypedescr: 'El sistema ha detectado que el archivo $1 es de tipo $2 y propone realizar las siguientes correcciones de forma automática:',
note: 'También se realiza la siguiente observación: $1',
noteonly: 'El sistema ha detectado que el archivo $1 es de tipo $2. Se realiza la siguiente observación: $3.',
titlechange: 'Cambiar el nombre por $1',
descriptionchange: 'Cambiar la descripción por $1',
licensechange: 'Cambiar la licencia por $1',
categoryaddchange: 'Agregar la categoría $1',
categoryremchange: 'Quitar la categoría $1',
templateaddchange: 'Agregar la plantilla $1',
templateremchange: 'Quitar la plantilla $1',
acceptproposal: 'Subir con los cambios propuestos',
declineproposal: 'Subir sin realizar cambios',
continueupload: 'Subir archivo',
licenseInsertText: '== Licencia ==\n$1',
titlechangedeclined: '\n<!-- Se ha sugerido el cambio de nombre a $1 pero se ha omitido -->',
categoryadddeclined: '\n<!-- Se ha sugerido agregar la categoría $1 pero se ha omitido -->',
templateadddeclined: '\n<!-- Se ha sugerido agregar la plantilla $1 pero se ha omitido -->',
categoryremdeclined: '\n<!-- Se ha sugerido eliminar la categoría $1 pero se ha omitido -->',
templateremdeclined: '\n<!-- Se ha sugerido eliminar la plantilla $1 pero se ha omitido -->'
});
// </pre>