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

Reply via email to