Utente:Seb35/common.js
Questa pagina definisce alcuni parametri di aspetto e comportamento generale di tutte le pagine. Per personalizzarli vedi Aiuto:Stile utente.
Nota: dopo aver salvato è necessario pulire la cache del proprio browser per vedere i cambiamenti (per le pagine globali è comunque necessario attendere qualche minuto). Per Mozilla / Firefox / Safari: fare clic su Ricarica tenendo premuto il tasto delle maiuscole, oppure premere Ctrl-F5 o Ctrl-R (Command-R su Mac); per Chrome: premere Ctrl-Shift-R (Command-Shift-R su un Mac); per Konqueror: premere il pulsante Ricarica o il tasto F5; per Opera può essere necessario svuotare completamente la cache dal menù Strumenti → Preferenze; per Internet Explorer: mantenere premuto il tasto Ctrl mentre si preme il pulsante Aggiorna o premere Ctrl-F5.
$( function () {
// High level overview:
// 1. The template [[Template:Category filters]] specifies the category filters
// 2. The basic metadata of the Wikidata properties corresponding to the filters are retrieved: type, min/max/regex constraints, authorised values if applicable
// 3. Widgets are created for each filter (dropdown lists, number input boxes…) with constraints if applicable
// 4. Depending on the number of articles in the category (<= 200 is a small category), the search strategy is 'prefetch' or 'online':
// A. Prefetch strategy:
// 1. A SPARQL request is done when the page loads, requesting these Wikidata properties thanks to SERVICE wikibase:mwapi
// 2. When the user enters the reque st, the filtering is done locally by the script by reading the SPARQL result
// 3. The logic is non-binary:
// * true = the Wikipedia article/Wikidata item matches all the filters,
// * false = it does not match at least one filter,
// * noProperty = one requested property is not present for the Wikidata item, so we are unsure if it matches the filters
// B. Online strategy:
// The SPARQL request is executed online with the specified filters
if( mw.config.get( 'wgNamespaceNumber' ) !== 14 || $( '#category-filters' ).length === 0 ) {
return;
}
categoryFiltersTranslations = {
'en': {
'title-search-filters': 'Search filters',
'placeholder-WikibaseItem': 'Select one or more values',
'type-Time-after': 'Between…',
'type-Time-before': 'and…',
'type-Quantity-after': 'Between…',
'type-Quantity-before': 'and…',
'button-search': 'Search',
'button-reset': 'Reset filters',
'switch-partial-results': 'Show also entries with undetermined values',
'total-pages-displayed': '($1 displayed after filtering)',
'tooltip-missing-wikidata': 'This article has no link to Wikidata.',
'tooltip-missing-property': 'This article misses at least one property amongst the filters.',
'no-results': 'No results',
},
'it': {
'title-search-filters': 'Filtri di ricerca',
'placeholder-WikibaseItem': 'Seleziona uno o più valori',
'type-Time-after': 'Tra…',
'type-Time-before': 'e…',
'type-Quantity-after': 'Tra…',
'type-Quantity-before': 'e…',
'button-search': 'Cerca',
'button-reset': 'Azzerare i filtri',
'switch-partial-results': 'Mostra anche voci con valori non determinati',
'total-pages-displayed': '($1 visualizzato dopo il filtraggio)',
'tooltip-missing-wikidata': 'Questo articolo non ha un collegamento a Wikidata.',
'tooltip-missing-property': 'In questo articolo manca almeno una proprietà tra i filtri.',
'no-results': 'Nessun risultato',
},
'fr': {
'title-search-filters': 'Filtres de recherche',
'placeholder-WikibaseItem': 'Sélectionnez une ou plusieurs valeurs',
'type-Time-after': 'Entre…',
'type-Time-before': 'et…',
'type-Quantity-after': 'Entre…',
'type-Quantity-before': 'et…',
'button-search': 'Rechercher',
'button-reset': 'Réinitialiser les filtres',
'switch-partial-results': 'Afficher également les entrées avec des résultats indéterminés',
'total-pages-displayed': '($1 affichées après filtrage)',
'tooltip-missing-wikidata': 'Cet article n’est pas lié à Wikidata.',
'tooltip-missing-property': 'Il manque à cet article au moins une propriété parmi les filtres.',
'no-results': 'Aucun résultat',
}
};
// General regexes
// These can be used as a very filter filter to check user-entered values
var generalRegexes = {
'wikibase-item': 'Q[0-9]+',
'time-year': '-?[0-9]+',
'time-day': '-?[0-9]+-(?:0[1-9]|1[012])-(?:0[1-9]|[12][0-9]|3[01])',
'quantity': '-?[0-9]+(?:[,.][0-9]+)?',
'quantity-integer': '-?[0-9]+',
};
// These are property metadata to be able to create selectors with a given quality level
// This object is populated a few lines below
properties = { // TODO add "var" keyword for production
/** EXAMPLES OF PROPERTIES TYPES - this is now loaded from Wikidata, but the schema (name of each characteristics and explanation) should be documented somewhere (possibly remain here)
'P21': {
// undefined|"WikibaseItem"|"Quantity"|"Time"|"Monolingualtext"|"String"|"Url"|"ExternalId" – type of the property
type: 'WikibaseItem',
// undefined|string|null – label of the property in the user language; null means we tried to get the name and we failed
label: undefined,
// 0|1|2|3|4 – quality level where:
// - 0: we don’t know anything about the property, not even its type. Nicolas: 2022-03-21 still exists ? Not present on https://it.wikipedia.org/wiki/Discussioni_progetto:Coordinamento/Categorie/Progetto_filtro_categorie - Seb: 2022-03-21: this is the initialisation value, before any request to Wikidata to know the type, see beginning of getPropertiesMetadata()
// - 1: we know the type of the property
// - 2: if applicable, we know a regex of acceptable values (for string-typed properties) or a minimum and/or maximum of acceptable values (for quantity-typed properties)
// - 3: we know all values independently of the specific category we are visiting
// - 4: we know all values present in the specific category we are visiting
level: 1,
// undefined|Number – minimum value accepted for this property
min: undefined,
// undefined|Number – maximum value accepted for this property
max: undefined,
// undefined|string – regex constraint for this property
regex: undefined,
// undefined|null|string[]|Object[] – all values of the property, either globally either for the specific category if possible
// null means we tried to get the type and we failed
// for most property types this is string[], but for wikibase-item this is Object[] (each object is { item: (string), lang: (string), label: (string) }
values: undefined,
},
'P569': { // date of birth
type: 'Time',
label: undefined,
level: 1,
},
'P19': { // place of birth
type: 'WikibaseItem',
label: undefined,
level: 1,
},
'P364': { // original language of film or TV show
type: 'WikibaseItem',
label: undefined,
level: 1,
},
'P495': { // country of origin
type: 'WikibaseItem',
label: undefined,
level: 1,
},
'P577': { // publication date
type: 'Time',
label: undefined,
level: 1,
},
/* END OF EXAMPLES OF PROPERTIES TYPES */
};
categoryFilters = {
'specFilters': $( '#category-filters-list' ).text().trim().replace( / /g, '' ),
'listFilters': $( '#category-filters-list' ).text().trim().replace( / /g, '' ).split( ',' ),
'sparqlQuery': null,
'searchStrategy': undefined,
'oouiWidgetsFilters': {},
'valuesFromWikidata': null,
'valuesFromWikidataLight': null,
'sitelinks': {},
'articlesToWikidata': null,
'allSubjects': null,
'cachePV': {},
};
var noQid = {}, // [unused] Marker for a Wikipedia article without corresponding Wikidata item
noProperty = {}; // Marker for a Wikipedia article with a corresponding Wikidata item, but one requested property is missing => the result of the request is uncertain
var userLanguage = mw.config.get( 'wgUserLanguage' ),
defaultLanguage = categoryFiltersTranslations[userLanguage] ? userLanguage : 'it',
defaultMessages = categoryFiltersTranslations[defaultLanguage],
itMessages = categoryFiltersTranslations.it,
sparqlLanguages = defaultLanguage === 'it' ? 'it,en' : defaultLanguage + ',it,en';
function msg( msgid ) {
return defaultMessages[msgid] ? defaultMessages[msgid] : ( itMessages[msgid] ? itMessages[msgid] : '⧼' + msgid + '⧽' );
}
if( !categoryFilters.specFilters.match( /^P[0-9]+(,P[0-9]+)*$/ ) ) {
console.error( 'Wrong parameter “filter”: ' + categoryFilters.specFilters );
return;
}
events = new OO.EventEmitter();
var initialised = false;
var widgetSpinner;
// Some heuristics to evaluate the strategy depending on the category size
var isBigCategory = $( '#mw-pages > a' ).length > 0; // If there is such link, it is "The following 200 pages are in this category, out of N total."
// TODO some languages don’t use figures [0-9] like Farsi, Sanskrit, Oriya,… (16 languages according to my search in MediaWiki languages files)
var numberOfArticles = $( '#mw-pages > p' ).text().replace( /\b200\b/, '' ).match( /[0-9]+(?:[., ][0-9]+)*/ );
if( Array.isArray( numberOfArticles ) ) {
numberOfArticles = Number( numberOfArticles[0].replace( /[., ]/g, '' ) );
numberOfArticles = isNaN( numberOfArticles ) ? null : numberOfArticles;
}
console.debug( 'Big category? ' + ( isBigCategory ? 'yes' : 'false' ) + ', with ' + ( numberOfArticles !== null ? numberOfArticles : 'an unknown number of' ) + ' articles' );
if( isBigCategory ) {
categoryFilters.searchStrategy = 'online';
} else {
categoryFilters.searchStrategy = 'prefetch';
}
// When this promise is resolved, all properties metadata are obtained
var promiseProperties = getPropertiesMetadata( categoryFilters.listFilters, sparqlLanguages );
var titleForSparql = mw.config.get( 'wgPageName' ).replaceAll( /\\/g, '\\\\' ).replaceAll( /"/g, '\\"' ); // escape backslashes and double-quote to prevent SPARQL injections
var filtersSparqlFields = categoryFilters.listFilters.map( function( x ) { return '?' + x + ' ?' + x + 'Label'; } ).join( ' ' );
var filtersSparqlWhere = categoryFilters.listFilters.map( function( x ) { return ' OPTIONAL { ?item wdt:' + x + ' ?' + x + ' } .'; } ).join( '\n' );
categoryFilters.sparqlQuery = 'SELECT ?item ?itemLabel ?sitelink ' + filtersSparqlFields + '\nWHERE {\n SERVICE wikibase:mwapi {\n bd:serviceParam wikibase:endpoint "it.wikipedia.org"; wikibase:api "Generator"; mwapi:generator "categorymembers"; mwapi:gcmtitle "' + titleForSparql + '"; mwapi:gcmtype "page"; mwapi:inprop "url"; .\n ?item wikibase:apiOutputItem mwapi:item .\n ?sitelink wikibase:apiOutput "@fullurl" .\n }\n FILTER BOUND (?item) .\n' + filtersSparqlWhere + '\n # OPTIONAL ONLINE QUERY\n SERVICE wikibase:label { bd:serviceParam wikibase:language "' + sparqlLanguages + '" }\n}';
console.debug( categoryFilters.sparqlQuery.replace( /\n # OPTIONAL ONLINE QUERY/, '' ) );
if( categoryFilters.searchStrategy === 'prefetch' ) {
$.getJSON( 'https://query.wikidata.org/bigdata/namespace/wdq/sparql', { query: categoryFilters.sparqlQuery.replace( /\n # OPTIONAL ONLINE QUERY/, '' ) } ).then( function( x ) {
console.log( 'Wikidata data received' ); // DEBUG
console.debug( x ); // DEBUG
categoryFilters.valuesFromWikidata = x;
categoryFilters.articlesToWikidata = {};
x.results.bindings.map( function( y ) { categoryFilters.articlesToWikidata[y.sitelink.value.substr(24)] = y.item.value.substr(31); } );
addQidToCategoryPages();
categoryFilters.valuesFromWikidataLight = x.results.bindings.map( function( y ) {
return Object.entries( y ).reduce(
function( acc, item ) {
if( item[1].type === 'uri' && item[1].value.substr( 0, 31 ) === 'http://www.wikidata.org/entity/' ) {
acc[item[0]] = item[1].value.substr( 31 );
} else {
acc[item[0]] = item[1].value;
}
return acc;
}, {} );
} );
console.debug( categoryFilters.valuesFromWikidataLight ); // DEBUG
categoryFilters.sitelinks = categoryFilters.valuesFromWikidataLight.reduce( function( acc, y ) { acc[y.item] = y.sitelink; return acc; }, {} );
categoryFilters.allSubjects = x.results.bindings.map( function( y ) { return y.item.value.substr( 31 ); } );
// Update the values of WikibaseItem-type properties with real-used values (level-4 properties)
function registerValues() {
categoryFilters.listFilters.map( function( prop ) {
if( !properties[prop] || properties[prop].type !== 'WikibaseItem' || !categoryFilters.oouiWidgetsFilters[prop] ) {
return;
}
var values = x.results.bindings.filter( function( y ) { return !!y[prop] && y[prop].type === 'uri'; } ).map( function( y ) { return { 'data': y[prop].value.substr( 31 ), 'label': y[prop+'Label'].value }; } );
categoryFilters.oouiWidgetsFilters[prop][3][0].menu.clearItems();
categoryFilters.oouiWidgetsFilters[prop][3][0].addOptions( values );
} );
$( '#category-filter-spinner' ).addClass( 'category-filter-hidden' );
}
if( initialised ) {
registerValues();
} else {
events.once( 'initialised', registerValues );
}
} );
}
mw.util.addCSS( '#category-filters { display: inline-block; border: 1px solid black; padding: 1em; margin-left: 4em; } #category-filters div.filter { margin-bottom: 1em; } #category-filters div.filter-type-time .oo-ui-numberInputWidget-field { width: 12em; } #category-filters .category-filter-hidden, #mw-pages .hidden-by-filter { display: none; } #mw-pages .category-filters-unknown-qid.hidden-by-default, #mw-pages .category-filters-unknown-property.hidden-by-default { display: none; } #category-filter-advanced { margin-top: 1em; } #mw-pages div.category-filter-wrap { display: none; }' );
mw.loader.load( '/w/index.php?title=Utente:Ypermat/vector.css&action=raw&ctype=text/css', 'text/css' );
mw.loader.using( ['oojs', 'oojs-ui'], function () {
promiseProperties.then( loadUI );
} );
function loadUI() {
console.log( properties ); // DEBUG
widgetSpinner = new OO.ui.ProgressBarWidget( { progress: 0 } );
// Similar to MenuTagMultiselectWidget but with autocomplete
MenuTagMultiselectWithAutocompleteWidget = function MenuTagMultiselectWithAutocompleteWidget( config ) {
OO.ui.MenuTagMultiselectWidget.call( this, config );
};
OO.inheritClass( MenuTagMultiselectWithAutocompleteWidget, OO.ui.MenuTagMultiselectWidget );
/**
* @inheritdoc
*/
MenuTagMultiselectWithAutocompleteWidget.prototype.onInputChange = function () {
var value = this.input.getValue(), that = this;
if( !value ) {
this.menu.toggle( false );
this.menu.clearItems();
return;
}
$.getJSON( { url: '/w/rest.php/v1/search/title?q=' + value + '&limit=10', timeout: 5000 } ).then( function( result ) {
if( result && result.pages ) {
that.menu.toggle( false );
that.menu.clearItems();
var options = result.pages.map( function( x ) { return x.title; } ).map( function( x ) { return { data: x, label: x }; } );
that.addOptions( options );
that.menu.toggle( true );
}
} );
};
var oouiWidgetsFiltersList = categoryFilters.listFilters.map( function( filter ) {
if( !( filter in properties ) ) {
throw new Error( 'The property ' + filter + ' should be known at this time in the variable "properties".' );
} else if( properties[filter].type === 'WikibaseItem' ) {
var options = [], allowArbitrary = true, multiselectWidget;
if( properties[filter].level >= 3 && properties[filter].values && properties[filter].values.length ) {
options = properties[filter].values.filter( function( x ) { return !!x; } ).map( function( x ) { return { 'data': x.item, 'label': x.label }; } );
allowArbitrary = false;
}
// Type WikibaseItem: a list of the items (multiple choices allowed)
if( allowArbitrary ) {
multiselectWidget = new MenuTagMultiselectWithAutocompleteWidget( {
placeholder: msg( 'placeholder-WikibaseItem' ),
allowArbitrary: true,
verticalPosition: 'below',
selected: []
} );
multiselectWidget.on( 'change', function( items ) {
items.forEach( function( item ) {
if( typeof item.getData() === 'string' && !item.getData().match( /^Q[0-9]+$/ ) ) {
var api = new mw.Api();
api.get( {
action: 'query',
prop: 'pageprops',
titles: item.getData(),
} ).done( function( data ) {
if( Object.keys( data.query.pages ).length === 1 ) {
var pageid = Object.keys( data.query.pages )[0];
if( data.query.pages[pageid].missing !== '' && data.query.pages[pageid].pageprops && data.query.pages[pageid].pageprops.wikibase_item ) {
item.setData( data.query.pages[pageid].pageprops.wikibase_item );
} else {
item.setData( false );
}
}
} );
}
} );
} );
} else {
multiselectWidget = new OO.ui.MenuTagMultiselectWidget( {
placeholder: msg( 'placeholder-WikibaseItem' ),
allowArbitrary: allowArbitrary,
verticalPosition: 'below',
selected: [],
options: options
} );
}
return [ filter, 'wikibase-item', [ ucFirst( properties[filter].label ) ], [ multiselectWidget ] ];
} else if( properties[filter].type === 'Time' ) {
var widgetProperties1 = {}, widgetProperties2 = {};
if( properties[filter].min && properties[filter].min.match( /^-?[0-9]+-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$/ ) ) {
widgetProperties1.min = Number( properties[filter].min.replace( /^(-?[0-9]+)-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$/, '$1' ) );
widgetProperties2.min = Number( properties[filter].min.replace( /^(-?[0-9]+)-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$/, '$1' ) );
widgetProperties1.placeholder = 'min : ' + properties[filter].min.replace( /^(-?[0-9]+)-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$/, '$1' );
} else {
widgetProperties1.placeholder = 'min : -∞ (-infinite)';
}
if( properties[filter].max && properties[filter].max.match( /^-?[0-9]+-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$/ ) ) {
widgetProperties1.max = Number( properties[filter].max.replace( /^(-?[0-9]+)-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$/, '$1' ) );
widgetProperties2.max = Number( properties[filter].max.replace( /^(-?[0-9]+)-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$/, '$1' ) );
widgetProperties2.placeholder = 'max : ' + properties[filter].max.replace( /^(-?[0-9]+)-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$/, '$1' );
} else {
widgetProperties2.placeholder = 'max : ∞ (infinite)';
}
// Type Time: two number selectors for year-min and year-max
var yearMinWidget = new OO.ui.NumberInputWidget( widgetProperties1 );
var yearMaxWidget = new OO.ui.NumberInputWidget( widgetProperties2 );
return [ filter, 'time', [ ucFirst( properties[filter].label ), msg( 'type-Time-after' ), msg( 'type-Time-before' ) ], [ yearMinWidget, yearMaxWidget ] ];
} else if( properties[filter].type === 'Quantity' ) {
var numberWidgetProperties = {};
if( properties[filter].min && properties[filter].min.match( /^-?[0-9]+\.[0-9]+$/ ) ) {
numberWidgetProperties.min = Number( properties[filter].min );
}
if( properties[filter].max && properties[filter].max.match( /^-?[0-9]+\.[0-9]+$/ ) ) {
numberWidgetProperties.max = Number( properties[filter].max );
}
var numberMinWidget = new OO.ui.NumberInputWidget( numberWidgetProperties );
var numberMaxWidget = new OO.ui.NumberInputWidget( numberWidgetProperties );
return [ filter, 'quantity', [ ucFirst( properties[filter].label ), msg( 'type-Quantity-after' ), msg( 'type-Quantity-before' ) ], [ numberMinWidget, numberMaxWidget ] ];
} else if( properties[filter].type === 'String' || properties[filter].type === 'Monolingualtext' || properties[filter].type === 'Url' || properties[filter].type === 'ExternalId' ) {
var stringWidgetProperties = {};
if( properties[filter].regex ) {
try {
stringWidgetProperties.validate = new RegExp( properties[filter].regex );
} catch( e ) {
stringWidgetProperties.validate = null;
}
}
var stringWidget = new OO.ui.TextInputWidget( stringWidgetProperties );
return [ filter, 'string', [ ucFirst( properties[filter].label ) ], [ stringWidget ] ];
}
// TODO add more widgets here for more types (and possibly subtypes if we want multiple widgets for a same type, e.g. time-year and time-day)
} ).map( function( filter ) {
categoryFilters.oouiWidgetsFilters[filter[0]] = filter;
return filter;
} );
var searchButtonWidget = new OO.ui.ButtonWidget( {
label: msg( 'button-search' ),
flags: [
'primary',
'progressive'
]
} );
searchButtonWidget.on( 'click', launchSearch );
var resetButtonWidget = new OO.ui.ButtonWidget( {
label: msg( 'button-reset' ),
icon: 'trash',
flags: [
'primary',
'destructive'
]
} );
resetButtonWidget.on( 'click', function() {
categoryFilters.listFilters.map( function( filter ) {
categoryFilters.oouiWidgetsFilters[filter][3].map( function( oouiWidget ) {
if( oouiWidget.menu ) {
oouiWidget.getMenu().selectItem();
}
oouiWidget.setValue( [] );
} );
} );
$( '#category-filters-total-filtered' ).html( '' );
if( $( '#mw-pages #category-filter-results' ).length ) {
$( '#mw-pages #category-filter-results' ).detach();
$( '#mw-pages div.category-filter-wrap > *' ).unwrap();
$( '#mw-pages h2:first-of-type' ).detach();
}
$( 'span.category-filters-unknown-property' ).detach();
$( 'span.category-filters-unknown-qid' ).detach();
$( '#mw-pages li' ).each( function() {
$( this ).removeClass( 'hidden-by-filter' );
} );
//$( '#mw-pages li .category-filters-unknown-qid' ).each( function() {
// $( this ).addClass( 'hidden-by-default' );
//} );
$( '#mw-pages div.mw-category-group' ).each( function() {
$( 'h3', this ).removeClass( 'hidden-by-filter' );
$( this ).removeClass( 'hidden-by-filter' );
} );
$( '#mw-pages > div:first-of-type' ).css( 'display', 'block' );
$( '#category-filter-all-pages' ).css( 'display', 'none' );
} );
var partialResultsSwitch = new OO.ui.ToggleSwitchWidget( { value: true } );
partialResultsSwitch.on( 'change', function( visibility ) {
$( 'li[data-wikidata]' ).each( function( i, elem ) {
if( $( '.category-filters-unknown-property', elem ).length ) {
if( visibility ) {
$( elem ).removeClass( 'hidden-by-filter' );
} else {
$( elem ).addClass( 'hidden-by-filter' );
}
}
} );
$( 'div.mw-category-group' ).each( function( i, elem ) {
if( $( 'li', elem ).length === $( 'li.hidden-by-filter', elem ).length ) {
$( 'h3', elem ).addClass( 'hidden-by-filter' );
} else {
$( 'h3', elem ).removeClass( 'hidden-by-filter' );
}
} );
} );
$( '#category-filters' ).append( '<h2>' + msg( 'title-search-filters' ) + '</h2>' );
oouiWidgetsFiltersList.map( function( widget ) {
var filter = widget[0],
type = widget[1],
label = widget[2][0],
oouiWidget = widget[3];
$( '#category-filters' ).append( '<div id="filter-' + filter + '" class="filter filter-type-' + type + '"><div class="category-filter-label"><label>' + label + '</label></div><span class="widget-filter"></span></div>' );
oouiWidget.map( function( x, i ) {
if( i+1 < widget[2].length ) {
$( '#filter-' + filter + ' span.widget-filter' ).append( widget[2][i+1] );
}
$( '#filter-' + filter + ' span.widget-filter' ).append( x.$element );
} );
} );
$( '#category-filters' ).append( searchButtonWidget.$element, resetButtonWidget.$element );
$( '#category-filters' ).append( '<span id="category-filter-spinner"></span>' );
$( '#category-filter-spinner' ).append( widgetSpinner.$element );
if( categoryFilters.searchStrategy === 'online' ) {
$( '#category-filter-spinner' ).addClass( 'category-filter-hidden' );
}
$( '#category-filters' ).append( '<div id="category-filter-advanced"></div>' );
if( !isBigCategory ) {
$( '#category-filter-advanced' ).append( partialResultsSwitch.$element );
$( '#category-filter-advanced' ).append( msg( 'switch-partial-results' ) + '<br />' );
}
$( '#category-filter-advanced' ).append( 'Advanced: <a id="category-filter-link-sparql" href="" target="_blank">SPARQL request for the whole category <img src="/w/skins/Vector/resources/common/images/link-external-small-ltr-progressive.svg" alt="External link" /></a></div>' );
$( '#category-filter-link-sparql' ).attr( 'href', 'https://query.wikidata.org/#' + encodeURIComponent( categoryFilters.sparqlQuery.replace( /\n # OPTIONAL ONLINE QUERY/, '' ) ) );
$( '#mw-pages > p' ).append( '<span id="category-filters-total-filtered"></span>' );
initialised = true;
events.emit( 'initialised' );
// Append to the wrapper
//$( '#category-filters' ).html( '<div class="wrapper" style="width: 300px"><h3>Search among biographies</h3><span id="birthYearMin">Born in (year) between </span> and (optionnal) <span id="birthYearMax"> </span> And (logical) <span id="andLogical"> </span> Born in (city) <span id="birthPlaceCountry"> </span></div>' );
//$( '#birthYearMin' ).append(birthYearMin.$element);
//$( '#birthYearMax' ).append(birthYearMax.$element);
//$( '#andLogical' ).append(switchButton.$element);
//$( '#birthPlaceCountry' ).append(birthPlaceCountry.$element);
//$( '.wrapper').append(
// searchButtonWidget.$element,
// resetButtonWidget.$element
//);
}
function launchSearch() {
console.log( 'Search' );
if( categoryFilters.searchStrategy === 'online' ) {
console.debug( 'Search online' );
var sparqlValues = categoryFilters.listFilters.map( function( filter ) {
console.debug( [ filter, properties[filter], categoryFilters.oouiWidgetsFilters[filter][3][0].getValue(), categoryFilters.oouiWidgetsFilters[filter][3].length > 1 ? categoryFilters.oouiWidgetsFilters[filter][3][1].getValue() : undefined ] );
if( properties[filter].type === 'WikibaseItem' && categoryFilters.oouiWidgetsFilters[filter][3][0].getValue().length ) {
return ' ?item wdt:' + filter + ' ?values' + filter + ' . VALUES ?values' + filter + ' { ' + categoryFilters.oouiWidgetsFilters[filter][3][0].getValue().filter( function( filter ) { return !!filter; } ).map( function( qid ) { return 'wd:' + qid; } ).join( ' ' ) + ' } .\n';
} else if( properties[filter].type === 'Time' && ( categoryFilters.oouiWidgetsFilters[filter][3][0].getValue() || categoryFilters.oouiWidgetsFilters[filter][3][1].getValue() ) ) {
var valueTime = '';
if( categoryFilters.oouiWidgetsFilters[filter][3][0].getValue().match( /^-?[0-9]+$/ ) ) {
valueTime += ' FILTER( ?' + filter + ' >= "' + categoryFilters.oouiWidgetsFilters[filter][3][0].getValue() + '-01-01T00:00:00Z"^^xsd:dateTime ) .\n';
}
if( categoryFilters.oouiWidgetsFilters[filter][3][1].getValue().match( /^-?[0-9]+$/ ) ) {
valueTime += ' FILTER( ?' + filter + ' <= "' + categoryFilters.oouiWidgetsFilters[filter][3][1].getValue() + '-01-01T00:00:00Z"^^xsd:dateTime ) .\n';
}
return valueTime;
} else if( properties[filter].type === 'Quantity' && ( categoryFilters.oouiWidgetsFilters[filter][3][0].getValue() || categoryFilters.oouiWidgetsFilters[filter][3][1].getValue() ) ) {
var valueQuantity = '';
if( categoryFilters.oouiWidgetsFilters[filter][3][0].getValue().match( /^-?[0-9]+(?:[,.][0-9]+)?$/ ) ) {
valueQuantity += ' FILTER( ?' + filter + ' >= ' + categoryFilters.oouiWidgetsFilters[filter][3][0].getValue() + ' ) .\n';
}
if( categoryFilters.oouiWidgetsFilters[filter][3][1].getValue().match( /^-?[0-9]+(?:[,.][0-9]+)?$/ ) ) {
valueQuantity += ' FILTER( ?' + filter + ' <= ' + categoryFilters.oouiWidgetsFilters[filter][3][1].getValue() + ' ) .\n';
}
return valueQuantity;
}
console.debug( 'unimplemented SPARQL datatype "' + properties[filter].type + '' );
return ''; // TODO for other types
} ).filter( function( filter ) { return !!filter; } );
if( sparqlValues.length === 0 ) {
return;
}
var sparql = categoryFilters.sparqlQuery.replace( / # OPTIONAL ONLINE QUERY/, sparqlValues.join( '\n' ) );
console.log( sparql );
$( '#category-filter-spinner' ).removeClass( 'category-filter-hidden' );
var updateTextSpinner = function( iter, end ) {
console.debug( 'Progress', end ? 100 : iter*100 / numberOfArticles );
widgetSpinner.setProgress( end ? 100 : iter*100 / numberOfArticles * 100 );
//$( '#category-filter-spinner-text' ).text( ( end ? numberOfArticles : iter*100 ) + '/' + numberOfArticles );
};
getSPARQLResultsWithProgressBar( sparql, updateTextSpinner, 100, 500, 60000 ).then( function( x ) {
console.log( x );
$( '#category-filter-spinner' ).addClass( 'category-filter-hidden' );
var pages = x.results.bindings.map( function( item ) {
var title = mw.Title.newFromText( decodeURIComponent( item.sitelink.value ).replace( /^https:\/\/[a-z-]+\.wikipedia\.org\/wiki\//, '' ) );
return '<li data-wikidata="' + item.item.value.substr( 31 ) + '"><a href="' + item.sitelink.value + '" title="' + title.getPrefixedText() + '">' + title.getPrefixedText() + '</a></li>';
} ).filter( unique );
if( $( '#mw-pages #category-filter-results' ).length ) {
$( '#category-filters-total-filtered' ).html( '' );
if( $( '#mw-pages #category-filter-results' ).length ) {
$( '#mw-pages #category-filter-results' ).detach();
$( '#mw-pages div.category-filter-wrap > *' ).unwrap();
$( '#mw-pages h2:first-of-type' ).detach();
}
}
$( '#mw-pages' ).wrapInner( '<div class="category-filter-wrap"></div>' );
$( '#mw-pages div.category-filter-wrap h2' ).clone().prependTo( $( '#mw-pages' ) );
$( '#mw-pages' ).append( '<div id="category-filter-results"><div class="mw-category mw-category-columns"><div class="mw-category-group">' + ( pages.length ? msg( 'total-pages-displayed' ).replace( '$1', pages.length ) + '<ul>' + pages.join( '' ) + '</ul>' : msg( 'no-results' ) ) + '</div></div></div>' );
} );
return;
}
subjectQids = categoryFilters.allSubjects.reduce( function( acc, x ) {
acc[x] = true;
return acc;
}, {} );
if( categoryFilters.allSubjects === null ) {
console.log( 'Too early: we did not have the Wikidata data for now. Please retry later.' );
return;
}
categoryFilters.listFilters.map( function( filter ) {
subjectQids = applyFilterFirstPass( filter, subjectQids );
} );
categoryFilters.listFilters.map( function( filter ) {
subjectQids = applyFilterSecondPass( filter, subjectQids );
} );
console.log( subjectQids );
var displayedPages = 0;
if( $( '#mw-pages > a' ).length === 0 ) {
$( '#mw-pages li' ).each( function() {
var thjs = $( this ),
qid = thjs.attr( 'data-wikidata' );
if( subjectQids[qid] === true ) {
$( 'span.category-filters-unknown-qid', thjs ).addClass( 'hidden-by-default' );
$( 'span.category-filters-unknown-property', thjs ).addClass( 'hidden-by-default' );
thjs.removeClass( 'hidden-by-filter' );
displayedPages++;
} else if( !qid ) {
$( 'span.category-filters-unknown-property', thjs ).addClass( 'hidden-by-default' );
$( 'span.category-filters-unknown-qid', thjs ).removeClass( 'hidden-by-default' );
thjs.removeClass( 'hidden-by-filter' );
displayedPages++;
} else if( subjectQids[qid] === noProperty ) {
if( !$( 'span.category-filters-unknown-property', thjs ).length ) {
$( this ).append( '<span class="category-filters-unknown-property hidden-by-default" title="' + msg( 'tooltip-missing-property' ) + '"> <img src="//upload.wikimedia.org/wikipedia/commons/2/20/Adobe_RoboInfo_5_icon_-_2.png" /></span>' );
}
$( 'span.category-filters-unknown-qid', thjs ).addClass( 'hidden-by-default' );
$( 'span.category-filters-unknown-property', thjs ).removeClass( 'hidden-by-default' );
thjs.removeClass( 'hidden-by-filter' );
displayedPages++;
} else {
thjs.addClass( 'hidden-by-filter' );
}
} );
$( '#category-filters-total-filtered' ).html( ' ' + msg( 'total-pages-displayed' ).replace( '$1', displayedPages ) );
$( '#mw-pages div.mw-category-group' ).each( function() {
var total = $( 'li', this ).length,
hidden = $( 'li.hidden-by-filter', this ).length;
if( total === hidden ) {
$( this ).addClass( 'hidden-by-filter' );
} else {
$( this ).removeClass( 'hidden-by-filter' );
}
} );
} else {
$( '#mw-pages > div:first-of-type' ).css( 'display', 'none' );
if( $( '#category-filter-all-pages' ).length === 0 ) {
//var headerRegex = new RegExp( '^([ \n]*<h2.*?</h2>)(.*?)(<div dir="[^"]*" class="[^"]*" lang="[^"]*"><div class="mw-category">)', 's' );
//$( '#mw-pages' ).html( $( '#mw-pages' ).html().replace( headerRegex, '$1<div class="category-filters-intro-category">$2</div>$3' ) );
$( '#mw-pages' ).append( '<div id="category-filter-all-pages" class="mw-content-ltr" dir="ltr" lang="it"></div>' );
}
$( '.category-filters-intro-category' ).css( 'display', 'none' );
var items = Object.keys( subjectQids ).map( function( x ) {
if( subjectQids[x] === true ) {
return '<li data-wikidata="' + x + '"><a href="' + categoryFilters.sitelinks[x] + '">' + decodeURI( categoryFilters.sitelinks[x] ).replace( /^https?:\/\/[a-z-]+\.wikipedia\.org\/wiki\//, '' ).replace( /_/g, ' ' ).replace( /%3F/g, '?' ) + '</a></li>';
} else if( subjectQids[x] === noProperty ) {
return '<li data-wikidata="' + x + '"><a href="' + categoryFilters.sitelinks[x] + '">' + decodeURI( categoryFilters.sitelinks[x] ).replace( /^https?:\/\/[a-z-]+\.wikipedia\.org\/wiki\//, '' ).replace( /_/g, ' ' ).replace( /%3F/g, '?' ) + '</a><span class="category-filters-unknown-property" title="' + msg( 'tooltip-missing-property' ) + '"> <img src="//upload.wikimedia.org/wikipedia/commons/2/20/Adobe_RoboInfo_5_icon_-_2.png" /></span></li>';
}
} );
$( '#category-filter-all-pages' ).html( '<div class="mw-category"><div class="mw-category-group"><ul>' + items.join( '' ) + '</ul></div></div>' );
$( '#category-filter-all-pages' ).css( 'display', 'block' );
}
return false;
}
/**
* Add the attribute data-wikidata to <li> tag in the pages of the category.
*
* It requires the object categoryFilters.articlesToWikidata to be populated beforehand.
*/
function addQidToCategoryPages() {
if( !categoryFilters.articlesToWikidata || Object.values( categoryFilters.articlesToWikidata ) === 0 ) {
return;
}
$( '#mw-pages li' ).each( function() {
var a = $( 'a', this ),
href = a.attr( 'href' );
if( categoryFilters.articlesToWikidata[href] ) {
$( this ).attr( 'data-wikidata', categoryFilters.articlesToWikidata[href] );
} else {
$( this ).attr( 'data-wikidata', '' ).append( '<span class="category-filters-unknown-qid hidden-by-default" title="' + msg( 'tooltip-missing-wikidata' ) + '"> <img src="//upload.wikimedia.org/wikipedia/commons/6/6b/Adobe_RoboInfo_5_icon_-_2-red.png" /></span>' );
}
} );
}
/**
* Apply the first pass of one filter to a set of pages, which adds the status "noProperty" to pages if the filter is filled but the page has no corresponding property.
*/
function applyFilterFirstPass( filter, subjectQids ) {
var property = categoryFilters.oouiWidgetsFilters[filter][0],
type = categoryFilters.oouiWidgetsFilters[filter][1],
oouiWidgets = categoryFilters.oouiWidgetsFilters[filter][3];
if( !categoryFilters.cachePV[property] ) {
categoryFilters.cachePV[property] = {};
}
if( oouiWidgets.length === 1 && type === 'wikibase-item' ) {
var oouiWidget = oouiWidgets[0],
values = oouiWidget.getValue();
if( values.length === 0 ) {
return subjectQids;
}
} else if( oouiWidgets.length === 2 && type === 'time' ) { // year
var minYear = oouiWidgets[0].getValue() ? parseInt( oouiWidgets[0].getValue() ) : null,
maxYear = oouiWidgets[1].getValue() ? parseInt( oouiWidgets[1].getValue() ) : null;
if( ( minYear === null || isNaN( minYear ) ) && ( maxYear === null || isNaN( maxYear ) ) ) {
return subjectQids;
}
} else if( oouiWidgets.length === 2 && type === 'quantity' ) { // quantity
var minQuantity = oouiWidgets[0].getValue() ? parseFloat( oouiWidgets[0].getValue() ) : null,
maxQuantity = oouiWidgets[1].getValue() ? parseFloat( oouiWidgets[1].getValue() ) : null;
if( ( minQuantity === null || isNaN( minQuantity ) ) && ( maxQuantity === null || isNaN( maxQuantity ) ) ) {
return subjectQids;
}
} else if( oouiWidgets.length === 1 && type === 'string' ) { // string
var stringValue = oouiWidgets[0].getValue();
if( !stringValue ) {
return subjectQids;
}
} else {
console.debug( 'unimplemented applyFilterFirstPass() unknown type' );
return subjectQids;
}
// Add items for which the property is not present: we cannot filter on this property so they have the status "noProperty"
if( !categoryFilters.cachePV[property]['no-value'] ) {
categoryFilters.cachePV[property]['no-value'] = categoryFilters.valuesFromWikidataLight.filter( function( x ) {
return !x[property];
} ).map( function( x ) {
return x.item;
} );
}
categoryFilters.cachePV[property]['no-value'].map( function( x ) {
subjectQids[x] = noProperty;
} );
return subjectQids;
}
/**
* Apply the second pass of one filter to a set of pages, which removes pages if the filter is filled and the corresponding property is declared for the page and the page does not have some searched value.
*/
function applyFilterSecondPass( filter, subjectQids ) {
var property = categoryFilters.oouiWidgetsFilters[filter][0],
type = categoryFilters.oouiWidgetsFilters[filter][1],
oouiWidgets = categoryFilters.oouiWidgetsFilters[filter][3],
matchingItems = [];
if( !categoryFilters.cachePV[property] ) {
categoryFilters.cachePV[property] = {};
}
if( oouiWidgets.length === 1 && type === 'wikibase-item' ) {
var oouiWidget = oouiWidgets[0],
values = oouiWidget.getValue();
if( values.length === 0 ) {
return subjectQids;
}
values.map( function( value ) {
if( !categoryFilters.cachePV[property][value] ) {
categoryFilters.cachePV[property][value] = categoryFilters.valuesFromWikidataLight.filter( function( x ) {
return x[property] === value;
} ).map( function( x ) {
return x.item;
} );
}
matchingItems = matchingItems.concat( categoryFilters.cachePV[property][value] );
} );
} else if( oouiWidgets.length === 2 && type === 'time' ) { // year
var minYear = oouiWidgets[0].getValue() ? parseInt( oouiWidgets[0].getValue() ) : null,
maxYear = oouiWidgets[1].getValue() ? parseInt( oouiWidgets[1].getValue() ) : null;
if( ( minYear === null || isNaN( minYear ) ) && ( maxYear === null || isNaN( maxYear ) ) ) {
return subjectQids;
}
matchingItems = categoryFilters.valuesFromWikidataLight.filter( function( x ) {
if( !x[property] ) {
return false;
}
var year = parseInt( x[property].substr( 0, 4 ) );
return ( minYear === null || minYear <= year ) && ( maxYear === null || year <= maxYear );
} ).map( function( x ) {
return x.item;
} );
} else if( oouiWidgets.length === 2 && type === 'quantity' ) { // quantity
var minQuantity = oouiWidgets[0].getValue() ? parseFloat( oouiWidgets[0].getValue() ) : null,
maxQuantity = oouiWidgets[1].getValue() ? parseFloat( oouiWidgets[1].getValue() ) : null;
if( ( minQuantity === null || isNaN( minQuantity ) ) && ( maxQuantity === null || isNaN( maxQuantity ) ) ) {
return subjectQids;
}
matchingItems = categoryFilters.valuesFromWikidataLight.filter( function( x ) {
if( !x[property] ) {
return false;
}
var quantity = parseFloat( x[property] );
return ( minQuantity === null || minQuantity <= quantity ) && ( maxQuantity === null || quantity <= maxQuantity );
} ).map( function( x ) {
return x.item;
} );
} else if( oouiWidgets.length === 1 && type === 'string' ) { // string
var stringValue = oouiWidgets[0].getValue() ? oouiWidgets[0].getValue() : null;
if( !stringValue ) {
return subjectQids;
}
matchingItems = categoryFilters.valuesFromWikidataLight.filter( function( x ) {
if( !x[property] ) {
return false;
}
return x[property].toLowerCase().includes( stringValue.toLowerCase() );
} ).map( function( x ) {
return x.item;
} );
} else {
console.debug( 'unimplemented applyFilterSecondPass() unknown type' );
return subjectQids;
}
// We remove from the result the items which does not match this filter AND have the corresponding property
// Because if the corresponding property is not present, it cannot vote and the item must keep its value "noProperty"
Object.keys( subjectQids ).map( function( x ) {
if( !matchingItems.includes( x ) && !categoryFilters.cachePV[property]['no-value'].includes( x ) ) {
delete subjectQids[x];
}
} );
return subjectQids;
}
/**
* Retrieve SPARQL results with a progress bar.
*
* @param string query SPARQL query
* @param callback clb Function to call when intermediary results are obtained, its prototype is function( Number iter, bool end)
* @param Number? bucketSize Number of articles in each bucket (1000 by default, max 1000)
* @param Number? maxContinuations Hard limit of number of continuations (50 by default, max 50)
* @param Number? timeout Timeout of each request in milliseconds (60 seconds by default, max 60 seconds)
* @return Promise<Object> When the promise is solved, SPARQL result
*/
function getSPARQLResultsWithProgressBar( query, clb, bucketSize, maxContinuations, timeout ) {
maxContinuations = maxContinuations === undefined || maxContinuations > 500 ? 500 : maxContinuations;
bucketSize = bucketSize === undefined || bucketSize > 1000 ? 1000 : bucketSize;
timeout = timeout === undefined || timeout > 60000 ? 60000 : timeout;
query = query.replace( /\?itemLabel/, '$& ?continuation' ).replace( /mwapi:inprop "url";/, '$& wikibase:limit "once"; mwapi:gcmlimit ' + bucketSize + '; mwapi:gcmcontinue "#PLACEHOLDER-CONTINUE#"' ).replace( / \?sitelink wikibase:apiOutput "@fullurl"/, '$& .\n ?continuation wikibase:apiOutput "/api/continue/@gcmcontinue"' );
var resultHead, resultBindings = [];
var promiseResult = function( continuation, iterContinuations ) {
clb( iterContinuations, false );
var thisquery = query.replace( /#PLACEHOLDER-CONTINUE#/, continuation.replaceAll( /\\/g, '\\\\' ).replaceAll( /"/g, '\\"' ) ); // escape backslashes and double-quote to prevent SPARQL injections
return $.getJSON( { url: 'https://query.wikidata.org/bigdata/namespace/wdq/sparql', timeout: timeout }, { query: thisquery } ).then( function( result ) {
if( !result || !result.head || !result.results || !result.results.bindings ) {
clb( iterContinuations, true );
return { head: resultHead, results: { bindings: resultBindings } };
} else {
if( result.head.vars && Array.isArray( result.head.vars ) ) {
result.head.vars = result.head.vars.filter( function( x ) {
return x !== 'continuation';
} );
}
resultHead = result.head;
}
if( result.results.bindings.length === 0 || !result.results.bindings[0] ) {
// SPARQL-MWAPI did not return any result, so we don’t know what is the
// continuation value and we are unable to continue the search, except
// by issuing a dedicated API call outside of SPARQL-MWAPI just to obtain
// this continuation value. This is a quite bad work-around but I did not
// find a better solution.
var api = new mw.Api();
return api.get( {
action: 'query',
generator: 'categorymembers',
gcmtitle: mw.config.get( 'wgPageName' ),
gcmlimit: bucketSize,
gcmcontinue: continuation,
} ).then( function( data ) {
if( data && data.continue && data.continue.gcmcontinue ) {
var newContinuation = data.continue.gcmcontinue;
return promiseResult( newContinuation, iterContinuations+1 );
} else {
return { head: resultHead, results: { bindings: resultBindings } };
}
} );
}
var newContinuation = result.results.bindings[0].continuation && result.results.bindings[0].continuation.value ? result.results.bindings[0].continuation.value : false;
resultBindings = resultBindings.concat( result.results.bindings.map( function( x ) {
delete x.continuation;
return x;
} ) );
if( !newContinuation || iterContinuations > maxContinuations ) {
clb( iterContinuations, true );
return { head: resultHead, results: { bindings: resultBindings } };
}
return promiseResult( newContinuation, iterContinuations+1 );
} );
};
return promiseResult( '', 0 );
}
/**
* Retrieve basic metadata for properties.
*
* Security: this function handles sanitisation of the parameters.
* Global variables: isBigCategory, properties
* Used functions: getPropertyMetadata, getAuthorisedValues
*
* @param string[] listProperties List of properties, each one must be ^P[0-9]+$
* @param string languages List of language codes (the latters are fallbacks of the formers), e.g. "hi,en,it"
* @return Promise<void> When the promise is solved, all metadata for the properties were put in the global object `properties`
*/
function getPropertiesMetadata( listProperties, languages ) {
listProperties = listProperties.filter( function( property ) {
return /^P[0-9]+$/.test( property );
} );
return Promise.all( listProperties.map( function( property ) {
// initialise the description of the property, but do not override if already defined
if( !properties[property] ) {
properties[property] = {
type: undefined,
label: undefined,
level: 0,
};
}
// if the category already reached the maximum level, do not recompute it (it is the case if the definition of `properties` statically defines the values)
if( properties[property].level === 4 ) {
return;
}
// TODO discuss and evaluate what should be considered a “big category” (the current condition says 200 articles)
if( properties[property].level === 3 && isBigCategory ) {
return;
}
return getPropertyMetadata( property, languages ).then( function( x ) {
properties[property] = {
type: x.type,
label: x.label,
level: 1,
};
if( x.type === 'String' || x.type === 'Monolingualtext' || x.type === 'Url' || x.type === 'ExternalId' ) {
if( x.regex !== undefined ) {
properties[property].level = 2;
properties[property].regex = x.regex;
}
} else if( x.type === 'Quantity' || x.type === 'Time' ) {
if( x.min !== undefined ) {
properties[property].level = 2;
properties[property].min = x.min;
}
if( x.max !== undefined ) {
properties[property].level = 2;
properties[property].max = x.max;
}
} else if( x.type === 'WikibaseItem' && x.imageCard && !properties[property].values ) {
return getAuthorisedValues( property, languages ).then( function( y ) {
// do not override level-4 values if they became available
if( !properties[property].values ) {
properties[property].level = 3;
properties[property].values = y;
}
} ).fail( function( y ) {
if( !properties[property].values ) {
properties[property].values = null;
}
} );
}
} );
} ) );
}
/**
* Get authorised values of a property with a “one-of” constraint.
*
* Security warning: the parameters are not escaped, do proper escaping before use.
* Global variables: -
* Used functions: $.getJSON
*
* TODO return the values of the qualifier “exception to contraint” (P2303), very rare but currently used on P91, P517, P853, P9971
*
* @param string Pid Property ID, e.g. "P21" or "P577"
* @param string languages List of language codes (the latters are fallbacks of the formers), e.g. "hi,en,it"
* @return Promise<string[][]|null> Promise returning a list of authorised values or null if there is no contraint “one-of”; each value is an object { item: (string), lang: (string), label: (string) ]
*/
function getAuthorisedValues( Pid, languages ) {
var sparql = 'SELECT ?item ?itemLabel WHERE { wd:' + Pid + ' wikibase:propertyType wikibase:WikibaseItem ; p:P2302 [ ps:P2302 wd:Q21510859 ; pq:P2305 ?item ] . SERVICE wikibase:label { bd:serviceParam wikibase:language "' + languages + '" } }';
return $.getJSON( 'https://query.wikidata.org/bigdata/namespace/wdq/sparql', { query: sparql } ).then( function( x ) {
if( !x || !x.results || !x.results.bindings || !Array.isArray( x.results.bindings ) ) {
return null; // TODO return error?
}
if( x.results.bindings.length === 0 ) {
return null;
}
return x.results.bindings.map( function( y ) {
if( !y || !y.item || !y.itemLabel || y.item.type !== 'uri' || y.itemLabel.type !== 'literal' || y.item.value.substr( 0, 31 ) !== 'http://www.wikidata.org/entity/' ) {
return null;
}
return { item: y.item.value.substr( 31 ), lang: y.itemLabel['xml:lang'], label: y.itemLabel.value };
} ).filter( function( y ) {
return !!y;
} );
} );
}
/**
* Try to get all values of a property (the image of the property).
*
* Security warning: the parameters are not escaped, do proper escaping before use.
* Warning: this can be very expansive and long for most properties: only launch this function when you are confident enough there are only a few values (e.g. chemical elements (P246)).
* Global variables: -
* Used functions: $.getJSON
*
* @param string Pid Property ID, e.g. "P21" or "P577"
* @param Number timeout Timeout for the request in milliseconds; there is a hard limit to 5 seconds
* @return Promise<string[]|null> Promise returning a list of all values or null if we cannot obtain this list
*/
function getAllValues( Pid, timeout ) {
var sparql = 'SELECT ?value WHERE { [] wdt:' + Pid + ' ?value }';
if( !timeout || timeout <= 0 || timeout >= 5000 ) {
timeout = 5000;
}
return $.getJSON( { url: 'https://query.wikidata.org/bigdata/namespace/wdq/sparql', timeout: timeout }, { query: sparql } ).then( function( x ) {
if( !x || !x.results || !x.results.bindings || !Array.isArray( x.results.bindings ) ) {
return null; // TODO return error?
}
return x.results.bindings.map( function( y ) {
// TODO should we manage "unknown value"?
if( !y || !y.value ) {
return null;
}
return y.value.value;
} ).filter( function( y ) {
return !!y;
} );
} );
}
/**
* Obtain basic metadata about a property.
*
* Security warning: the parameters are not escaped, do proper escaping before use.
* Global variables: -
* Used functions: $.getJSON
*
* @param string Pid Property ID, e.g. "P21" or "P577"
* @param string languages List of language codes (the latters are fallbacks of the formers), e.g. "hi,en,it"
* @return Promise<Object> Promise returning an object with keys "name" (type string|null) and "type" (type string|null)
*/
function getPropertyMetadata( Pid, languages ) {
// TODO should we enforce use of non-deprecated constraints? see for instance https://www.wikidata.org/wiki/Property:P364#P2302 with “one-of constraint”: currently the list of elements is returned
var sparql = 'SELECT ?propertyLabel ?type ?min ?max ?regex (COUNT(?value) AS ?imageCard) WHERE { BIND( wd:' + Pid + ' AS ?property ) . ?property wikibase:propertyType ?type . OPTIONAL { ?property p:P2302 [ ps:P2302 wd:Q21510860 ; pq:P2313 ?min ] } . OPTIONAL { ?property p:P2302 [ ps:P2302 wd:Q21510860 ; pq:P2310 ?min ] } . OPTIONAL { ?property p:P2302 [ ps:P2302 wd:Q21510860 ; pq:P2312 ?max ] } . OPTIONAL { ?property p:P2302 [ ps:P2302 wd:Q21510860 ; pq:P2311 ?max ] } . OPTIONAL { ?property p:P2302 [ ps:P2302 wd:Q21502404 ; pq:P1793 ?regex ] } . OPTIONAL { ?property p:P2302 [ ps:P2302 wd:Q21510859 ; pq:P2305 ?value ] } . SERVICE wikibase:label { bd:serviceParam wikibase:language "' + languages + '" } } GROUP BY ?propertyLabel ?type ?min ?max ?regex';
return $.getJSON( 'https://query.wikidata.org/bigdata/namespace/wdq/sparql', { query: sparql } ).then( function( x ) {
if( !x || !x.results || !x.results.bindings || !Array.isArray( x.results.bindings ) ) {
return { type: undefined, label: undefined };
}
if( x.results.bindings.length === 0 ) {
return { type: undefined, label: undefined };
}
var metadata = x.results.bindings[0];
if( metadata.type.type !== 'uri' || metadata.propertyLabel.type !== 'literal' || metadata.type.value.substr( 0, 26 ) !== 'http://wikiba.se/ontology#' ) {
return { type: undefined, label: undefined };
}
return {
type: metadata.type.value.substr( 26 ),
label: metadata.propertyLabel ? metadata.propertyLabel.value : undefined,
min: metadata.min && metadata.min.type === 'literal' ? metadata.min.value : undefined,
max: metadata.max && metadata.max.type === 'literal' ? metadata.max.value : undefined,
regex: metadata.regex && metadata.regex.type === 'literal' ? metadata.regex.value : undefined,
imageCard: metadata.imageCard ? Number( metadata.imageCard.value ) : undefined,
};
} );
}
function ucFirst( str ) {
return str.substring( 0, 1 ).toUpperCase() + str.substring( 1 );
}
function unique( value, index, self ) {
return self.indexOf( value ) === index;
}
window.globalApplyFilterFirstPass = applyFilterFirstPass; // DEBUG
window.globalApplyFilterSecondPass = applyFilterSecondPass; // DEBUG
window.globalgetSPARQLResultsWithProgressBar = getSPARQLResultsWithProgressBar;
} );