diff --git a/ursh/MANIFEST.in b/ursh/MANIFEST.in
new file mode 100644
index 0000000..db61780
--- /dev/null
+++ b/ursh/MANIFEST.in
@@ -0,0 +1,4 @@
+graft indico_ursh/static
+graft indico_ursh/translations
+
+global-exclude *.pyc __pycache__ .keep
diff --git a/ursh/indico_ursh/__init__.py b/ursh/indico_ursh/__init__.py
new file mode 100644
index 0000000..7e6aeec
--- /dev/null
+++ b/ursh/indico_ursh/__init__.py
@@ -0,0 +1,22 @@
+# This file is part of Indico.
+# Copyright (C) 2002 - 2018 European Organization for Nuclear Research (CERN).
+#
+# Indico 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 3 of the
+# License, or (at your option) any later version.
+#
+# Indico is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Indico; if not, see .
+
+from __future__ import unicode_literals
+
+from indico.util.i18n import make_bound_gettext
+
+
+_ = make_bound_gettext('ursh')
diff --git a/ursh/indico_ursh/blueprint.py b/ursh/indico_ursh/blueprint.py
new file mode 100644
index 0000000..67d40ae
--- /dev/null
+++ b/ursh/indico_ursh/blueprint.py
@@ -0,0 +1,25 @@
+# This file is part of Indico.
+# Copyright (C) 2002 - 2018 European Organization for Nuclear Research (CERN).
+#
+# Indico 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 3 of the
+# License, or (at your option) any later version.
+#
+# Indico is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Indico; if not, see .
+
+from __future__ import unicode_literals
+
+from indico.core.plugins import IndicoPluginBlueprint
+
+from indico_ursh.controllers import RHShortenURL
+
+
+blueprint = IndicoPluginBlueprint('ursh', 'indico_ursh')
+blueprint.add_url_rule('/ursh', 'get_short_url', RHShortenURL, methods=('POST',))
diff --git a/ursh/indico_ursh/client/index.js b/ursh/indico_ursh/client/index.js
new file mode 100644
index 0000000..9771580
--- /dev/null
+++ b/ursh/indico_ursh/client/index.js
@@ -0,0 +1,60 @@
+/* This file is part of Indico.
+ * Copyright (C) 2002 - 2018 European Organization for Nuclear Research (CERN).
+ *
+ * Indico 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 3 of the
+ * License, or (at your option) any later version.
+ *
+ * Indico is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Indico; if not, see .
+ */
+
+import {handleAxiosError, indicoAxios} from 'indico/utils/axios';
+
+
+async function _makeUrshRequest(originalURL, triggerElement) {
+ const urshEndpoint = '/ursh';
+
+ let response;
+ try {
+ response = await indicoAxios.post(urshEndpoint, {
+ original_url: originalURL,
+ });
+ } catch (error) {
+ handleAxiosError(error);
+ return;
+ }
+
+ const data = response.data;
+ if (data.success) {
+ $(triggerElement).copyURLTooltip(data.msg).show();
+ } else {
+ $(triggerElement).qtip({
+ content: {
+ text: $T.gettext(`URL shortening service is unavailable: ${data.msg}`),
+ },
+ hide: {
+ event: 'mouseleave',
+ fixed: true,
+ delay: 700,
+ },
+ show: {
+ event: false,
+ ready: true,
+ }
+ }).show();
+ }
+}
+
+
+$(document).on('click', '.ursh-get', (event) => {
+ event.preventDefault();
+ const originalURL = $(event.target).attr('data-original-url');
+ _makeUrshRequest(originalURL, event.target);
+});
diff --git a/ursh/indico_ursh/controllers.py b/ursh/indico_ursh/controllers.py
new file mode 100644
index 0000000..3998784
--- /dev/null
+++ b/ursh/indico_ursh/controllers.py
@@ -0,0 +1,51 @@
+# This file is part of Indico.
+# Copyright (C) 2002 - 2018 European Organization for Nuclear Research (CERN).
+#
+# Indico 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 3 of the
+# License, or (at your option) any later version.
+#
+# Indico is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Indico; if not, see .
+
+from __future__ import unicode_literals
+
+import posixpath
+
+from flask import jsonify, request
+from werkzeug.exceptions import BadRequest
+from werkzeug.urls import url_parse
+
+from indico.core.config import config
+from indico.web.rh import RH
+
+from indico_ursh.util import request_short_url
+
+
+class RHShortenURL(RH):
+ """Make a request to the URL shortening service"""
+
+ @staticmethod
+ def _resolve_full_url(original_url):
+ if url_parse(original_url).host is not None:
+ return original_url
+ original_url = original_url.lstrip('/')
+ return posixpath.join(config.BASE_URL, original_url)
+
+ @staticmethod
+ def _check_host(full_url):
+ if url_parse(full_url).host != url_parse(config.BASE_URL).host:
+ raise BadRequest('Invalid host for URL shortening service')
+
+ def _process(self):
+ original_url = request.json.get('original_url')
+ full_url = self._resolve_full_url(original_url)
+ self._check_host(full_url)
+ short_url = request_short_url(full_url)
+ return jsonify(success=True, msg=short_url)
diff --git a/ursh/indico_ursh/plugin.py b/ursh/indico_ursh/plugin.py
new file mode 100644
index 0000000..f445921
--- /dev/null
+++ b/ursh/indico_ursh/plugin.py
@@ -0,0 +1,63 @@
+# This file is part of Indico.
+# Copyright (C) 2002 - 2018 European Organization for Nuclear Research (CERN).
+#
+# Indico 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 3 of the
+# License, or (at your option) any later version.
+#
+# Indico is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Indico; if not, see .
+
+from __future__ import unicode_literals
+
+from flask_pluginengine import render_plugin_template
+
+from indico.core.plugins import IndicoPlugin
+from indico.web.forms.base import IndicoForm
+from indico.web.views import WPBase
+
+from indico_ursh import _
+from indico_ursh.blueprint import blueprint
+from wtforms.fields.core import StringField
+from wtforms.fields.html5 import URLField
+from wtforms.validators import DataRequired
+
+
+class SettingsForm(IndicoForm):
+ api_key = StringField(_('API key'), [DataRequired()],
+ description=_('The API key to access the ursh service'))
+ api_host = URLField(_('API host'), [DataRequired()],
+ description=_('The ursh API host, providing the interface to generate short URLs'))
+
+
+class UrshPlugin(IndicoPlugin):
+ """URL Shortener
+
+ Provides a URL shortening service for indico assets, events, etc.
+ """
+
+ configurable = True
+ settings_form = SettingsForm
+ default_settings = {
+ 'api_key': '',
+ 'api_host': '',
+ }
+
+ def init(self):
+ super(UrshPlugin, self).init()
+ self.template_hook('url-shortener-link', self._inject_ursh_button)
+ self.inject_bundle('main.js', WPBase)
+
+ def get_blueprints(self):
+ return blueprint
+
+ def _inject_ursh_button(self, target, element_type='a', element_class='', text='(short-url)', **kwargs):
+ if self.settings.get('api_key') and self.settings.get('api_host'):
+ return render_plugin_template('ursh_button.html', target=target,
+ element_type=element_type, element_class=element_class, text=text)
diff --git a/ursh/indico_ursh/templates/ursh_button.html b/ursh/indico_ursh/templates/ursh_button.html
new file mode 100644
index 0000000..f08c6c5
--- /dev/null
+++ b/ursh/indico_ursh/templates/ursh_button.html
@@ -0,0 +1,5 @@
+<{{ element_type }} class="ursh-get {{ element_class }}"
+ title="{% trans %}Obtain short URL{% endtrans %}"
+ data-original-url="{{ target }}">
+ {{- text -}}
+{{ element_type }}>
diff --git a/ursh/indico_ursh/util.py b/ursh/indico_ursh/util.py
new file mode 100644
index 0000000..b9e3a10
--- /dev/null
+++ b/ursh/indico_ursh/util.py
@@ -0,0 +1,36 @@
+# This file is part of Indico.
+# Copyright (C) 2002 - 2018 European Organization for Nuclear Research (CERN).
+#
+# Indico 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 3 of the
+# License, or (at your option) any later version.
+#
+# Indico is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Indico; if not, see .
+
+from __future__ import unicode_literals
+
+import requests
+from werkzeug.exceptions import ServiceUnavailable
+
+
+def request_short_url(original_url):
+ from indico_ursh.plugin import UrshPlugin
+ api_key = UrshPlugin.settings.get('api_key')
+ api_host = UrshPlugin.settings.get('api_host')
+
+ if not api_key or not api_host:
+ raise ServiceUnavailable('Not configured')
+
+ headers = {'Authorization': 'Bearer {api_key}'.format(api_key=api_key)}
+ response = requests.post(api_host, data=dict(url=original_url, allow_reuse=True), headers=headers)
+ response.raise_for_status()
+
+ data = response.json()
+ return data['short_url']
diff --git a/ursh/setup.py b/ursh/setup.py
new file mode 100644
index 0000000..7cde8f5
--- /dev/null
+++ b/ursh/setup.py
@@ -0,0 +1,43 @@
+# This file is part of Indico.
+# Copyright (C) 2002 - 2018 European Organization for Nuclear Research (CERN).
+#
+# Indico 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 3 of the
+# License, or (at your option) any later version.
+#
+# Indico is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Indico; if not, see .
+
+from __future__ import unicode_literals
+
+from setuptools import find_packages, setup
+
+
+setup(
+ name='indico-plugin-ursh',
+ version='3.0-dev',
+ description='URL shortening service for Indico',
+ url='https://github.com/indico/indico-plugins',
+ license='https://www.gnu.org/licenses/gpl-3.0.txt',
+ author='Indico Team',
+ author_email='indico-team@cern.ch',
+ packages=find_packages(),
+ zip_safe=False,
+ include_package_data=True,
+ install_requires=[
+ 'indico>=2.2.dev0',
+ ],
+ classifiers=[
+ 'Environment :: Plugins',
+ 'Environment :: Web Environment',
+ 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)',
+ 'Programming Language :: Python :: 2.7'
+ ],
+ entry_points={'indico.plugins': {'ursh = indico_ursh.plugin:UrshPlugin'}}
+)
diff --git a/ursh/webpack-bundles.json b/ursh/webpack-bundles.json
new file mode 100644
index 0000000..5f28835
--- /dev/null
+++ b/ursh/webpack-bundles.json
@@ -0,0 +1,5 @@
+{
+ "entry": {
+ "main": "./index.js"
+ }
+}