Yurik has uploaded a new change for review.
https://gerrit.wikimedia.org/r/297343
Change subject: Enable open() event
......................................................................
Enable open() event
Change-Id: Ia1facb619eb91e4af1d1829c104849f87c596153
---
M lib/graph2.compiled.js
M lib/vega2/vega.js
2 files changed, 165 insertions(+), 95 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Graph
refs/changes/43/297343/1
diff --git a/lib/graph2.compiled.js b/lib/graph2.compiled.js
index 38b0137..d1e2d93 100644
--- a/lib/graph2.compiled.js
+++ b/lib/graph2.compiled.js
@@ -296,118 +296,161 @@
// In some cases we may receive a badly formed URL in a form
customprotocol:https://...
opt.url = opt.url.replace(/^([a-z]+:)https?:\/\//, '$1//');
- var urlParts = this.parseUrl(opt);
+ var decodedPathname,
+ urlParts = this.parseUrl(opt),
+ sanitizedHost = this.sanitizeHost(urlParts.host),
+ isRelativeProtocol = !urlParts.protocol;
- var sanitizedHost = this.sanitizeHost(urlParts.host);
if (!sanitizedHost) {
throw new Error('URL hostname is not whitelisted: ' + opt.url);
}
urlParts.host = sanitizedHost.host;
- if (!urlParts.protocol) {
+ if (isRelativeProtocol) {
// Update protocol-relative URLs
urlParts.protocol = sanitizedHost.protocol;
}
// Save original procotol to post-process the data
opt.graphProtocol = urlParts.protocol;
- switch (urlParts.protocol) {
- case 'http':
- case 'https':
- if (!this.isTrusted) {
- throw new Error('HTTP and HTTPS protocols are not supported
for untrusted graphs.\n' +
- 'Use wikiraw:, wikiapi:, wikirest:, wikirawupload:, and
other protocols.\n' +
- 'See
https://www.mediawiki.org/wiki/Extension:Graph#External_data');
- }
- // keep the original URL
- break;
- case 'wikiapi':
- // wikiapi:///?action=query&list=allpages
- // Call to api.php - ignores the path parameter, and only uses the
query
- urlParts.query = this.objExtender(urlParts.query, {format: 'json',
formatversion: '2'});
- urlParts.pathname = '/w/api.php';
- urlParts.protocol = sanitizedHost.protocol;
- break;
+ if (opt.type === 'open') {
+ // Trim the value here because mediawiki will do it anyway, so we
might as well save on redirect
+ decodedPathname = decodeURIComponent(urlParts.pathname).trim();
- case 'wikirest':
- // wikirest:///api/rest_v1/page/...
- // Call to RESTbase api - requires the path to start with "/api/"
- // The /api/... path is safe for GET requests
- if (!/^\/api\//.test(urlParts.pathname)) {
- throw new Error('wikirest: protocol must begin with the /api/
prefix');
- }
- // keep urlParts.query
- // keep urlParts.pathname
- urlParts.protocol = sanitizedHost.protocol;
- break;
+ switch (urlParts.protocol) {
+ case 'http':
+ case 'https':
+ // The default protocol for the open action is wikititle, so
if isRelativeProtocol is set,
+ // we treat the whole pathname as title (without the '/'
prefix).
+ if (!isRelativeProtocol) {
+ // If we get http:// and https:// protocol hardcoded,
remove the '/wiki/' prefix instead
+ if (!/^\/wiki\/.+$/.test(decodedPathname)) {
+ throw new Error('wikititle: http(s) links must begin
with /wiki/ prefix');
+ }
+ decodedPathname =
decodedPathname.substring('/wiki'.length);
+ }
+ opt.graphProtocol = 'wikititle';
+ // fall-through
- case 'wikiraw':
- // wikiraw:///MyPage/data
- // Get raw content of a wiki page, where the path is the title
- // of the page with an additional leading '/' which gets removed.
- // Uses mediawiki api, and extract the content after the request
- // Query value must be a valid MediaWiki title string, but we only
ensure
- // there is no pipe symbol, the rest is handlered by the api.
- urlParts.pathname = decodeURIComponent(urlParts.pathname);
- if (!/^\/[^|]+$/.test(urlParts.pathname)) {
- throw new Error('wikiraw: invalid title');
- }
- urlParts.query = {
- format: 'json',
- formatversion: '2',
- action: 'query',
- prop: 'revisions',
- rvprop: 'content',
- titles: urlParts.pathname.substring(1)
- };
- urlParts.pathname = '/w/api.php';
- urlParts.protocol = sanitizedHost.protocol;
- break;
+ case 'wikititle':
+ // wikititle:///My_page or
wikititle://en.wikipedia.org/My_page
+ // open() at this point may only be used to link to a Wiki
page, as it may be invoked
+ // without a click, thus potentially causing a privacy issue.
+ if (Object.keys(urlParts.query).length !== 0) {
+ throw new Error('wikititle: query parameters are not
allowed');
+ }
+ if (!/^\/[^|]+$/.test(decodedPathname)) {
+ throw new Error('wikititle: invalid title');
+ }
+ urlParts.pathname = '/wiki/' +
encodeURIComponent(decodedPathname.substring(1).replace(' ', '_'));
+ urlParts.protocol = sanitizedHost.protocol;
+ break;
- case 'wikifile':
- // wikifile:///Einstein_1921.jpg
- // Get an image for the graph, e.g. from commons, by using
Special:Redirect
- urlParts.pathname = '/wiki/Special:Redirect/file' +
urlParts.pathname;
- urlParts.protocol = sanitizedHost.protocol;
- // keep urlParts.query
- break;
+ default:
+ throw new Error('"open()" action only allows links with
wikititle protocol, e.g. wikititle:///');
+ }
+ } else {
- case 'wikirawupload':
- //
wikirawupload://upload.wikimedia.org/wikipedia/commons/3/3e/Einstein_1921.jpg
- // Get an image for the graph, e.g. from commons
- // This tag specifies any content from the uploads.* domain,
without query params
- this._validateExternalService(urlParts, sanitizedHost, opt.url);
- urlParts.query = {};
- // keep urlParts.pathname
- break;
+ switch (urlParts.protocol) {
+ case 'http':
+ case 'https':
+ if (!this.isTrusted) {
+ throw new Error('HTTP and HTTPS protocols are not
supported for untrusted graphs.\n' +
+ 'Use wikiraw:, wikiapi:, wikirest:, wikirawupload:,
and other protocols.\n' +
+ 'See
https://www.mediawiki.org/wiki/Extension:Graph#External_data');
+ }
+ // keep the original URL
+ break;
- case 'wikidatasparql':
- // wikidatasparql:///?query=<QUERY>
- // Runs a SPARQL query, converting it to
- //
https://query.wikidata.org/bigdata/namespace/wdq/sparql?format=json&query=...
- this._validateExternalService(urlParts, sanitizedHost, opt.url);
- if (!urlParts.query || !urlParts.query.query) {
- throw new Error('wikidatasparql: missing query parameter in: '
+ opt.url);
- }
- urlParts.query = { format: 'json', query: urlParts.query.query };
- urlParts.pathname = '/bigdata/namespace/wdq/sparql';
- break;
+ case 'wikiapi':
+ // wikiapi:///?action=query&list=allpages
+ // Call to api.php - ignores the path parameter, and only uses
the query
+ urlParts.query = this.objExtender(urlParts.query, {format:
'json', formatversion: '2'});
+ urlParts.pathname = '/w/api.php';
+ urlParts.protocol = sanitizedHost.protocol;
+ break;
- case 'geoshape':
- // geoshape:///?ids=Q16,Q30
- // Get geo shapes data from OSM database by supplying Wikidata IDs
- // https://maps.wikimedia.org/shape?ids=Q16,Q30
- this._validateExternalService(urlParts, sanitizedHost, opt.url);
- if (!urlParts.query || (!urlParts.query.ids &&
!urlParts.query.query)) {
- throw new Error('geoshape: missing ids or query parameter in:
' + opt.url);
- }
- // the query object is not modified
- urlParts.pathname = '/shape';
- break;
+ case 'wikirest':
+ // wikirest:///api/rest_v1/page/...
+ // Call to RESTbase api - requires the path to start with
"/api/"
+ // The /api/... path is safe for GET requests
+ if (!/^\/api\//.test(urlParts.pathname)) {
+ throw new Error('wikirest: protocol must begin with the
/api/ prefix');
+ }
+ // keep urlParts.query
+ // keep urlParts.pathname
+ urlParts.protocol = sanitizedHost.protocol;
+ break;
- default:
- throw new Error('Unknown protocol ' + opt.url);
+ case 'wikiraw':
+ // wikiraw:///MyPage/data
+ // Get raw content of a wiki page, where the path is the title
+ // of the page with an additional leading '/' which gets
removed.
+ // Uses mediawiki api, and extract the content after the
request
+ // Query value must be a valid MediaWiki title string, but we
only ensure
+ // there is no pipe symbol, the rest is handlered by the api.
+ decodedPathname = decodeURIComponent(urlParts.pathname);
+ if (!/^\/[^|]+$/.test(decodedPathname)) {
+ throw new Error('wikiraw: invalid title');
+ }
+ urlParts.query = {
+ format: 'json',
+ formatversion: '2',
+ action: 'query',
+ prop: 'revisions',
+ rvprop: 'content',
+ titles: decodedPathname.substring(1)
+ };
+ urlParts.pathname = '/w/api.php';
+ urlParts.protocol = sanitizedHost.protocol;
+ break;
+
+ case 'wikifile':
+ // wikifile:///Einstein_1921.jpg
+ // Get an image for the graph, e.g. from commons, by using
Special:Redirect
+ urlParts.pathname = '/wiki/Special:Redirect/file' +
urlParts.pathname;
+ urlParts.protocol = sanitizedHost.protocol;
+ // keep urlParts.query
+ break;
+
+ case 'wikirawupload':
+ //
wikirawupload://upload.wikimedia.org/wikipedia/commons/3/3e/Einstein_1921.jpg
+ // Get an image for the graph, e.g. from commons
+ // This tag specifies any content from the uploads.* domain,
without query params
+ this._validateExternalService(urlParts, sanitizedHost,
opt.url);
+ urlParts.query = {};
+ // keep urlParts.pathname
+ break;
+
+ case 'wikidatasparql':
+ // wikidatasparql:///?query=<QUERY>
+ // Runs a SPARQL query, converting it to
+ //
https://query.wikidata.org/bigdata/namespace/wdq/sparql?format=json&query=...
+ this._validateExternalService(urlParts, sanitizedHost,
opt.url);
+ if (!urlParts.query || !urlParts.query.query) {
+ throw new Error('wikidatasparql: missing query parameter
in: ' + opt.url);
+ }
+ urlParts.query = {format: 'json', query: urlParts.query.query};
+ urlParts.pathname = '/bigdata/namespace/wdq/sparql';
+ break;
+
+ case 'geoshape':
+ // geoshape:///?ids=Q16,Q30
+ // Get geo shapes data from OSM database by supplying Wikidata
IDs
+ // https://maps.wikimedia.org/shape?ids=Q16,Q30
+ this._validateExternalService(urlParts, sanitizedHost,
opt.url);
+ if (!urlParts.query || (!urlParts.query.ids &&
!urlParts.query.query)) {
+ throw new Error('geoshape: missing ids or query parameter
in: ' + opt.url);
+ }
+ // the query object is not modified
+ urlParts.pathname = '/shape';
+ break;
+
+ default:
+ throw new Error('Unknown protocol ' + opt.url);
+ }
}
+
return this.formatUrl(urlParts, opt);
};
diff --git a/lib/vega2/vega.js b/lib/vega2/vega.js
index a8a4040..1863166 100644
--- a/lib/vega2/vega.js
+++ b/lib/vega2/vega.js
@@ -15306,7 +15306,7 @@
fn.eventGroup = 'event.vg.getGroup';
fn.eventX = 'event.vg.getX';
fn.eventY = 'event.vg.getY';
-// fn.open = 'window.open';
+ fn.open = openGen(codegen);
fn.scale = scaleGen(codegen, false);
fn.iscale = scaleGen(codegen, true);
fn.inrange = 'this.defs.inrange';
@@ -15323,10 +15323,37 @@
'indata': indata,
'format': numberFormat,
'timeFormat': timeFormat,
- 'utcFormat': utcFormat
+ 'utcFormat': utcFormat,
+ 'open': windowOpen
};
}
});
+
+function openGen(codegen) {
+ return function (args) {
+ args = args.map(codegen);
+ var n = args.length;
+ if (n < 1 || n > 2) {
+ throw Error("open takes exactly 1 or 2 arguments.");
+ }
+ return 'this.defs.open(this.model, ' +
+ args[0] + (n > 1 ? ',' + args[1] : '') + ')';
+ };
+}
+
+function windowOpen(model, url, name) {
+ if (window && window.open) {
+ var opt = dl.extend({type: 'open', url: url, name: name},
model.config().load),
+ uri = dl.load.sanitizeUrl(opt);
+ if (uri) {
+ window.open(uri, name);
+ } else {
+ throw Error('Invalid URL: ' + opt.url);
+ }
+ } else {
+ throw Error("open can only be invoked in a browser.")
+ }
+}
function scaleGen(codegen, invert) {
return function(args) {
@@ -15377,7 +15404,7 @@
}
args = args.map(codegen);
- return 'this.defs.indata(this.model,' +
+ return 'this.defs.indata(this.model,' +
args[0] + ',' + args[1] + ',' + args[2] + ')';
};
}
--
To view, visit https://gerrit.wikimedia.org/r/297343
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: Ia1facb619eb91e4af1d1829c104849f87c596153
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/Graph
Gerrit-Branch: master
Gerrit-Owner: Yurik <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits