From 1f6f7f13093e0c3c51fe43fa9c4eb1330256ad93 Mon Sep 17 00:00:00 2001 From: Byron Jones Date: Mon, 1 Sep 2014 14:04:02 +0800 Subject: Bug 1056162: add bit.ly support to bmo --- extensions/Bitly/Config.pm | 27 +++++ extensions/Bitly/Extension.pm | 31 +++++ extensions/Bitly/lib/WebService.pm | 133 +++++++++++++++++++++ .../params/editparams-current_panel.html.tmpl | 13 ++ .../en/default/hook/global/header-start.html.tmpl | 13 ++ .../hook/global/user-error-errors.html.tmpl | 17 +++ .../en/default/hook/list/list-links.html.tmpl | 24 ++++ extensions/Bitly/web/js/bitly.js | 100 ++++++++++++++++ extensions/Bitly/web/styles/bitly.css | 23 ++++ 9 files changed, 381 insertions(+) create mode 100644 extensions/Bitly/Config.pm create mode 100644 extensions/Bitly/Extension.pm create mode 100644 extensions/Bitly/lib/WebService.pm create mode 100644 extensions/Bitly/template/en/default/hook/admin/params/editparams-current_panel.html.tmpl create mode 100644 extensions/Bitly/template/en/default/hook/global/header-start.html.tmpl create mode 100644 extensions/Bitly/template/en/default/hook/global/user-error-errors.html.tmpl create mode 100644 extensions/Bitly/template/en/default/hook/list/list-links.html.tmpl create mode 100644 extensions/Bitly/web/js/bitly.js create mode 100644 extensions/Bitly/web/styles/bitly.css (limited to 'extensions/Bitly') diff --git a/extensions/Bitly/Config.pm b/extensions/Bitly/Config.pm new file mode 100644 index 000000000..0c834eed2 --- /dev/null +++ b/extensions/Bitly/Config.pm @@ -0,0 +1,27 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +package Bugzilla::Extension::Bitly; +use strict; + +use constant NAME => 'Bitly'; +use constant REQUIRED_MODULES => [ + { + package => 'LWP-Protocol-https', + module => 'LWP::Protocol::https', + version => 0 + }, +]; +use constant OPTIONAL_MODULES => [ + { + package => 'Mozilla-CA', + module => 'Mozilla::CA', + version => 0 + }, +]; + +__PACKAGE__->NAME; diff --git a/extensions/Bitly/Extension.pm b/extensions/Bitly/Extension.pm new file mode 100644 index 000000000..a368b20fe --- /dev/null +++ b/extensions/Bitly/Extension.pm @@ -0,0 +1,31 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +package Bugzilla::Extension::Bitly; +use strict; +use warnings; + +use base qw(Bugzilla::Extension); +our $VERSION = '1'; + +use Bugzilla; + +sub webservice { + my ($self, $args) = @_; + $args->{dispatch}->{Bitly} = "Bugzilla::Extension::Bitly::WebService"; +} + +sub config_modify_panels { + my ($self, $args) = @_; + push @{ $args->{panels}->{advanced}->{params} }, { + name => 'bitly_token', + type => 't', + default => '', + }; +} + +__PACKAGE__->NAME; diff --git a/extensions/Bitly/lib/WebService.pm b/extensions/Bitly/lib/WebService.pm new file mode 100644 index 000000000..ce235c913 --- /dev/null +++ b/extensions/Bitly/lib/WebService.pm @@ -0,0 +1,133 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +package Bugzilla::Extension::Bitly::WebService; + +use strict; +use warnings; + +use base qw(Bugzilla::WebService); + +use Bugzilla::CGI; +use Bugzilla::Constants; +use Bugzilla::Error; +use Bugzilla::Search; +use Bugzilla::Search::Quicksearch; +use Bugzilla::Util 'correct_urlbase'; +use Bugzilla::WebService::Util 'validate'; +use JSON; +use LWP::UserAgent; +use URI; +use URI::Escape; +use URI::QueryParam; + +sub _validate_uri { + my ($self, $params) = @_; + + # extract url from params + if (!defined $params->{url}) { + ThrowCodeError( + 'param_required', + { function => 'Bitly.shorten', param => 'url' } + ); + } + my $url = ref($params->{url}) ? $params->{url}->[0] : $params->{url}; + + # only allow buglist queries for this bugzilla install + my $uri = URI->new($url); + $uri->query(undef); + $uri->fragment(undef); + if ($uri->as_string ne correct_urlbase() . 'buglist.cgi') { + ThrowUserError('bitly_unsupported'); + } + + return URI->new($url); +} + +sub shorten { + my ($self) = shift; + my $uri = $self->_validate_uri(@_); + + # the list_id is user-specific, remove it + $uri->query_param_delete('list_id'); + + return $self->_bitly($uri); +} + +sub list { + my ($self) = shift; + my $uri = $self->_validate_uri(@_); + + # map params to cgi vars, converting quicksearch if required + my $params = $uri->query_param('quicksearch') + ? Bugzilla::CGI->new(quicksearch($uri->query_param('quicksearch')))->Vars + : Bugzilla::CGI->new($uri->query)->Vars; + + # execute the search + my $search = Bugzilla::Search->new( + params => $params, + fields => ['bug_id'], + limit => Bugzilla->params->{max_search_results}, + ); + my $data = $search->data; + + # form a bug_id only url, sanity check the length + $uri = URI->new(correct_urlbase() . 'buglist.cgi?bug_id=' . join(',', map { $_->[0] } @$data)); + if (length($uri->as_string) > CGI_URI_LIMIT) { + ThrowUserError('bitly_failure', { message => "Too many bugs returned by search" }); + } + + # shorten + return $self->_bitly($uri); +} + +sub _bitly { + my ($self, $uri) = @_; + + # form request url + # http://dev.bitly.com/links.html#v3_shorten + my $bitly_url = sprintf( + 'https://api-ssl.bitly.com/v3/shorten?access_token=%s&longUrl=%s', + Bugzilla->params->{bitly_token}, + uri_escape($uri->as_string) + ); + + # is Mozilla::CA isn't installed, skip certificate verification + eval { require Mozilla::CA }; + $ENV{PERL_LWP_SSL_VERIFY_HOSTNAME} = $@ ? 0 : 1; + + # request + my $ua = LWP::UserAgent->new(agent => 'Bugzilla'); + my $response = $ua->get($bitly_url); + if ($response->is_error) { + ThrowUserError('bitly_failure', { message => $response->message }); + } + my $result = decode_json($response->decoded_content); + if ($result->{status_code} != 200) { + ThrowUserError('bitly_failure', { message => $result->{status_txt} }); + } + + # return just the short url + return { url => $result->{data}->{url} }; +} + +sub rest_resources { + return [ + qr{^/bitly/shorten$}, { + GET => { + method => 'shorten', + }, + }, + qr{^/bitly/list$}, { + GET => { + method => 'list', + }, + }, + ] +} + +1; diff --git a/extensions/Bitly/template/en/default/hook/admin/params/editparams-current_panel.html.tmpl b/extensions/Bitly/template/en/default/hook/admin/params/editparams-current_panel.html.tmpl new file mode 100644 index 000000000..2e0f58bc4 --- /dev/null +++ b/extensions/Bitly/template/en/default/hook/admin/params/editparams-current_panel.html.tmpl @@ -0,0 +1,13 @@ +[%# This Source Code Form is subject to the terms of the Mozilla Public + # License, v. 2.0. If a copy of the MPL was not distributed with this + # file, You can obtain one at http://mozilla.org/MPL/2.0/. + # + # This Source Code Form is "Incompatible With Secondary Licenses", as + # defined by the Mozilla Public License, v. 2.0. + #%] + +[% IF panel.name == "advanced" %] + [% panel.param_descs.bitly_token = + 'Bitly Generic Access Token' + %] +[% END -%] diff --git a/extensions/Bitly/template/en/default/hook/global/header-start.html.tmpl b/extensions/Bitly/template/en/default/hook/global/header-start.html.tmpl new file mode 100644 index 000000000..12ab7b20f --- /dev/null +++ b/extensions/Bitly/template/en/default/hook/global/header-start.html.tmpl @@ -0,0 +1,13 @@ +[%# This Source Code Form is subject to the terms of the Mozilla Public + # License, v. 2.0. If a copy of the MPL was not distributed with this + # file, You can obtain one at http://mozilla.org/MPL/2.0/. + # + # This Source Code Form is "Incompatible With Secondary Licenses", as + # defined by the Mozilla Public License, v. 2.0. + #%] + +[% RETURN UNLESS template.name == 'list/list.html.tmpl' %] + +[% style_urls.push('extensions/Bitly/web/styles/bitly.css') %] +[% javascript_urls.push('extensions/Bitly/web/js/bitly.js') %] +[% yui.push('container') %] diff --git a/extensions/Bitly/template/en/default/hook/global/user-error-errors.html.tmpl b/extensions/Bitly/template/en/default/hook/global/user-error-errors.html.tmpl new file mode 100644 index 000000000..edf0b0724 --- /dev/null +++ b/extensions/Bitly/template/en/default/hook/global/user-error-errors.html.tmpl @@ -0,0 +1,17 @@ +[%# This Source Code Form is subject to the terms of the Mozilla Public + # License, v. 2.0. If a copy of the MPL was not distributed with this + # file, You can obtain one at http://mozilla.org/MPL/2.0/. + # + # This Source Code Form is "Incompatible With Secondary Licenses", as + # defined by the Mozilla Public License, v. 2.0. + #%] + +[% IF error == "bitly_failure" %] + [% title = "Failed to generate short URL" %] + [% message FILTER html %] + +[% ELSIF error == "bitly_unsupported" %] + [% title = "Unsupported URL" %] + The requested URL is not supported. + +[% END %] diff --git a/extensions/Bitly/template/en/default/hook/list/list-links.html.tmpl b/extensions/Bitly/template/en/default/hook/list/list-links.html.tmpl new file mode 100644 index 000000000..836c017ed --- /dev/null +++ b/extensions/Bitly/template/en/default/hook/list/list-links.html.tmpl @@ -0,0 +1,24 @@ +[%# This Source Code Form is subject to the terms of the Mozilla Public + # License, v. 2.0. If a copy of the MPL was not distributed with this + # file, You can obtain one at http://mozilla.org/MPL/2.0/. + # + # This Source Code Form is "Incompatible With Secondary Licenses", as + # defined by the Mozilla Public License, v. 2.0. + #%] + +[% RETURN UNLESS user.id && Bugzilla.params.bitly_token %] + +
+
+ + +
+
+ +
+
+Short URL +|  [%# using nbsp because tt always trims trailing whitespace from templates %] diff --git a/extensions/Bitly/web/js/bitly.js b/extensions/Bitly/web/js/bitly.js new file mode 100644 index 000000000..62c49b650 --- /dev/null +++ b/extensions/Bitly/web/js/bitly.js @@ -0,0 +1,100 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This Source Code Form is "Incompatible With Secondary Licenses", as + * defined by the Mozilla Public License, v. 2.0. */ + +(function() { + 'use strict'; + var Dom = YAHOO.util.Dom; + YAHOO.namespace('bitly'); + var bitly = YAHOO.bitly; + + bitly.dialog = false; + bitly.url = { shorten: '', list: '' }; + + bitly.shorten = function() { + if (this.dialog) { + this.dialog.show(); + var el = Dom.get('bitly_url'); + el.select(); + el.focus(); + return; + } + this.dialog = new YAHOO.widget.Overlay('bitly_overlay', { + visible: true, + close: false, + underlay: 'shadow', + width: '400px', + context: [ 'bitly_shorten', 'bl', 'tl', ['windowResize'], [0, -10] ] + }); + this.dialog.render(document.body); + + YAHOO.util.Event.addListener('bitly_close', 'click', function() { + YAHOO.bitly.dialog.hide(); + }); + YAHOO.util.Event.addListener('bitly_url', 'keypress', function(o) { + if (o.keyCode == 27 || o.keyCode == 13) + YAHOO.bitly.dialog.hide(); + }); + this.execute(); + Dom.get('bitly_url').focus(); + }; + + bitly.execute = function() { + Dom.get('bitly_url').value = ''; + + var type = Dom.get('bitly_type').value; + if (this.url[type]) { + this.set(this.url[type]); + return; + } + + var url = 'rest/bitly/' + type + '?url=' + encodeURIComponent(document.location); + YAHOO.util.Connect.initHeader("Accept", "application/json"); + YAHOO.util.Connect.asyncRequest('GET', url, { + success: function(o) { + var response = YAHOO.lang.JSON.parse(o.responseText); + if (response.error) { + bitly.set(response.message); + } + else { + bitly.url[type] = response.url; + bitly.set(response.url); + } + }, + failure: function(o) { + try { + var response = YAHOO.lang.JSON.parse(o.responseText); + if (response.error) { + bitly.set(response.message); + } + else { + bitly.set(o.statusText); + } + } catch (ex) { + bitly.set(o.statusText); + } + } + }); + }; + + bitly.set = function(value) { + var el = Dom.get('bitly_url'); + el.value = value; + el.select(); + el.focus(); + }; + + bitly.toggle = function() { + if (this.dialog + && YAHOO.util.Dom.get('bitly_overlay').style.visibility == 'visible') + { + this.dialog.hide(); + } + else { + this.shorten(); + } + }; +})(); diff --git a/extensions/Bitly/web/styles/bitly.css b/extensions/Bitly/web/styles/bitly.css new file mode 100644 index 000000000..110a6bef4 --- /dev/null +++ b/extensions/Bitly/web/styles/bitly.css @@ -0,0 +1,23 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This Source Code Form is "Incompatible With Secondary Licenses", as + * defined by the Mozilla Public License, v. 2.0. */ + +#bitly_overlay { + position: absolute; + background: #eee; + border: 1px solid black; + padding: 5px; + margin: 10px; + visibility: collapse; + box-shadow: 3px 3px 6px #888; + -moz-box-shadow: 3px 3px 6px #888; +} + +#bitly_url { + margin: 2px 0; + display: block; + width: 100%; +} -- cgit v1.2.3-24-g4f1b