diff --git a/search_invenio/.gitignore b/search_invenio/.gitignore
new file mode 100644
index 0000000..3b7caa1
--- /dev/null
+++ b/search_invenio/.gitignore
@@ -0,0 +1,7 @@
+.idea/
+.*.swp
+*.pyc
+*.egg-info
+.webassets-cache/
+*.min.css
+*.min.js
diff --git a/search_invenio/MANIFEST.in b/search_invenio/MANIFEST.in
new file mode 100644
index 0000000..764608e
--- /dev/null
+++ b/search_invenio/MANIFEST.in
@@ -0,0 +1,3 @@
+graft indico_search_invenio/static
+graft indico_search_invenio/templates
+include indico_search_invenio/marc2short.xsl
diff --git a/search_invenio/indico_search_invenio/__init__.py b/search_invenio/indico_search_invenio/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/search_invenio/indico_search_invenio/engine.py b/search_invenio/indico_search_invenio/engine.py
new file mode 100644
index 0000000..031215f
--- /dev/null
+++ b/search_invenio/indico_search_invenio/engine.py
@@ -0,0 +1,287 @@
+# This file is part of Indico.
+# Copyright (C) 2002 - 2014 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 os
+import re
+from datetime import timedelta, datetime
+
+import requests
+from flask import redirect, request, jsonify
+from flask_pluginengine import current_plugin
+from lxml import etree
+from werkzeug.urls import url_encode
+from xml.dom import minidom
+
+from indico.core.plugins import get_plugin_template_module
+from indico.util.string import to_unicode
+from indico_search import SearchEngine
+from MaKaC.conference import Category, Conference
+
+from indico_search_invenio.entries import Author, SubContributionEntry, ContributionEntry, EventEntry
+
+
+class InvenioRemoteSearch(object):
+ # In general it would be nice if some of the functionality here was provided by a base class, but
+ # since most likely not all search backends distinguish between event/contribution collections,
+ # the base class would have to be very generic.
+ # But if someone ever integrates another search engine and uses this as a base, he is very welcome
+ # to extract parts from here and move them into the indico_search plugins instead of copying it to
+ # his own plugin!
+
+ def __init__(self, engine):
+ self.engine = engine
+
+ @property
+ def results_per_page(self):
+ return current_plugin.settings.get('results_per_page')
+
+ def process(self):
+ if request.is_xhr:
+ # AJAX request loading additional results
+ return self.process_xhr()
+
+ collections = set()
+ if self.engine.values['collection'] in {'', 'events'}:
+ collections.add('events')
+ if self.engine.values['collection'] in {'', 'contributions'}:
+ collections.add('contributions')
+
+ result_data = {'query': {k: v for k, v in request.form.iterlists() if k != 'search-collection'},
+ 'events': None,
+ 'contributions': None}
+ for collection in collections:
+ result_data[collection] = self._query_results(collection, 1)
+
+ if request.is_xhr:
+ return jsonify(events=self._make_json_response(result_data['events'], True),
+ contributions=self._make_json_response(result_data['contributions']))
+
+ return result_data
+
+ def process_xhr(self):
+ offset = int(request.form['offset'])
+ collection = self.engine.values['collection']
+ assert collection in {'events', 'contributions'}
+ return jsonify(self._make_json_response(self._query_results(collection, offset), collection == 'events'))
+
+ def _make_json_response(self, data, is_event):
+ mod = get_plugin_template_module('_results.html')
+ return {'html': '\n'.join(mod.render_result(r, is_event) for r in data['results']),
+ 'has_more': data['has_more'],
+ 'offset': data['offset']}
+
+ def _query_results(self, collection, offset):
+ num = self.results_per_page
+ if self.engine.user:
+ # Querying private data so we might have to skip some results - fetching more may avoid an extra query
+ num *= 2
+ params = self.engine.make_query(collection)
+ results = []
+ has_more = True
+ while len(results) < self.results_per_page:
+ data = self._process_data(self._fetch_data(of='xm', rg=num, jrec=offset, **params))
+ if not data:
+ # No results returned => stop searching, and we know for sure increasing the offset won't return more
+ has_more = False
+ break
+ offset += len(data)
+ for r in data:
+ if not r.is_visible(self.engine.user):
+ continue
+ results.append(r)
+ # We might have more data than we want to show - remove those and also make sure they are found again in
+ # the next batch of items
+ extra = len(results) - self.results_per_page
+ if extra > 0:
+ del results[-extra:]
+ offset -= extra
+ break
+ if len(data) < num:
+ has_more = False
+ break
+ if not results:
+ return None
+ return {'has_more': has_more, 'offset': offset, 'results': results}
+
+ def _fetch_data(self, **params):
+ return requests.get(current_plugin.settings.get('search_url'), params=params).content
+
+ def _process_data(self, data):
+ xsl = etree.XSLT(etree.parse(os.path.join(current_plugin.root_path, 'marc2short.xsl')))
+ xml = unicode(xsl(etree.fromstring(data)))
+ doc = minidom.parseString(xml)
+ return [self._process_record(e) for e in doc.getElementsByTagName('record')
+ if e.getElementsByTagName('identifier')]
+
+ def _process_record(self, elem):
+ authors = []
+ materials = []
+ element_id = elem.getElementsByTagName('identifier')[0].firstChild.data
+
+ try:
+ title = elem.getElementsByTagName('title')[0].firstChild.data
+ except (IndexError, AttributeError):
+ title = None
+
+ try:
+ location = elem.getElementsByTagName('location')[0].firstChild.data
+ except (IndexError, AttributeError):
+ location = None
+
+ try:
+ start_date = elem.getElementsByTagName('start_date')[0].firstChild.data
+ start_date = datetime.strptime(start_date, '%Y-%m-%dT%H:%M')
+ except (IndexError, AttributeError):
+ start_date = None
+
+ try:
+ description = elem.getElementsByTagName('description')[0].firstChild.data
+ except (IndexError, AttributeError):
+ description = None
+
+ for author in elem.getElementsByTagName('author'):
+ name = author.getElementsByTagName('name')[0].firstChild.data
+ try:
+ role = author.getElementsByTagName('role')[0].firstChild.data
+ except (IndexError, AttributeError):
+ role = None
+
+ try:
+ affiliation = author.getElementsByTagName('affiliation')[0].firstChild.data
+ except (IndexError, AttributeError):
+ affiliation = None
+ authors.append(Author(name, role, affiliation))
+
+ for material in elem.getElementsByTagName('material'):
+ try:
+ material_url = material.getElementsByTagName('url')[0].firstChild.data
+ except (IndexError, AttributeError):
+ material_url = None
+
+ try:
+ material_description = material.getElementsByTagName('description')[0].firstChild.data
+ except (IndexError, AttributeError):
+ material_description = None
+ materials.append((material_url, material_description))
+
+ return self._make_result(element_id, title, location, start_date, materials, authors, description)
+
+ def _make_result(self, entry_id, title, location, start_date, materials, authors, description):
+ match = re.match(r'^INDICO\.(\w+)(\.(\w+)(\.(\w)+)?)?$', entry_id)
+ if not match:
+ raise Exception('unrecognized entry id: {}'.format(entry_id))
+
+ if match.group(4):
+ return SubContributionEntry(match.group(5), title, location, start_date, materials, authors, description,
+ match.group(3), match.group(1))
+ elif match.group(2):
+ return ContributionEntry(match.group(3), title, location, start_date, materials, authors, description,
+ match.group(1))
+ else:
+ return EventEntry(match.group(1), title, location, start_date, materials, authors, description)
+
+
+class InvenioSearchEngine(SearchEngine):
+ @property
+ def use_redirect(self):
+ return current_plugin.settings.get('display_mode') == 'redirect'
+
+ @property
+ def only_public(self):
+ return current_plugin.settings.get('display_mode') != 'api_private'
+
+ def process(self):
+ query = self.make_query()
+ if not any(query.viewvalues()):
+ return None
+ elif self.use_redirect:
+ return redirect(self._build_url(**query))
+ else:
+ rs = InvenioRemoteSearch(self)
+ return rs.process()
+
+ def _build_url(self, **query_params):
+ return '{}?{}'.format(current_plugin.settings.get('search_url'), url_encode(query_params))
+
+ def make_query(self, collection=None):
+ query = {}
+ search_items = []
+ # Main search term
+ field = self.values['field']
+ phrase = self.values['phrase']
+ if phrase:
+ if field == 'author':
+ query['f'] = ''
+ search_items += self._make_field_query(phrase, field)
+ else:
+ query['f'] = field
+ search_items.append(phrase)
+ # Collection
+ if self.only_public:
+ search_items.append(self._make_public_collection_query(collection))
+ else:
+ query['c'] = self._make_private_collection_query(collection)
+ # Category/Event
+ search_items.append(self._make_obj_query())
+ # Date
+ search_items.append(self._make_date_query())
+ # Build query
+ query['p'] = ' '.join(filter(None, search_items))
+ # Sorting
+ query['sf'] = 'date'
+ query['so'] = self.values['sort_order']
+ return query
+
+ def _make_field_query(self, phrase, field):
+ if phrase[0] == '"' and phrase[-1] == '"':
+ return ['{}:"{}"'.format(field, phrase.replace('"', ''))]
+ return ['{}:{}'.format(field, word) for word in phrase.split()]
+
+ def _make_date_query(self):
+ start_date = self.values['start_date']
+ end_date = self.values['end_date']
+ if not start_date and not end_date:
+ return None
+ start_date = start_date.strftime('%Y-%m-%d') if start_date else '1950'
+ end_date = (end_date + timedelta(days=1)).strftime('%Y-%m-%d') if end_date else '2100'
+ return '518__d:"{}"->"{}"'.format(start_date, end_date)
+
+ def _make_public_collection_query(self, collection=None):
+ if collection is None:
+ collection = self.values['collection']
+ if collection == 'events':
+ return """970__a:"INDICO.*" -970__a:'.*.'"""
+ elif collection == 'contributions':
+ return """970__a:"INDICO.*" 970__a:'.*.'"""
+ else:
+ # XXX: This was missing in the old plugin, but I think it's necessary to get only Indico results
+ return '970__a:"INDICO.*"'
+
+ def _make_private_collection_query(self, collection=None):
+ if collection is None:
+ collection = self.values['collection']
+ suffix = 'CONTRIBS' if collection == 'contributions' else 'EVENTS'
+ prefix = 'INDICOSEARCH' if self.user else 'INDICOSEARCH.PUBLIC'
+ return '{}.{}'.format(prefix, suffix)
+
+ def _make_obj_query(self):
+ if isinstance(self.obj, Category) and not self.obj.isRoot():
+ return '650_7:"{}*"'.format(':'.join(map(to_unicode, self.obj.getCategoryPath())))
+ elif isinstance(self.obj, Conference):
+ # XXX: The old plugin prefixed this with 'AND ' but according to the invenio docs, AND is implied
+ return '970__a:"INDICO.{}.*"'.format(self.obj.id)
diff --git a/search_invenio/indico_search_invenio/entries.py b/search_invenio/indico_search_invenio/entries.py
new file mode 100644
index 0000000..f10cfad
--- /dev/null
+++ b/search_invenio/indico_search_invenio/entries.py
@@ -0,0 +1,151 @@
+# This file is part of Indico.
+# Copyright (C) 2002 - 2014 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 current_plugin
+from pytz import timezone
+
+from indico.util.date_time import as_utc
+from indico.web.flask.util import url_for
+from MaKaC.accessControl import AccessWrapper
+from MaKaC.common.timezoneUtils import DisplayTZ
+from MaKaC.conference import ConferenceHolder
+
+
+class Author(object):
+ def __init__(self, name, role, affiliation):
+ self.name = name
+ self.role = role
+ self.affiliation = affiliation
+
+
+class SearchResult(object):
+ def __init__(self, result_id, title, location, start_date, materials, authors, description):
+ self.id = result_id
+ self.title = title
+ self.location = location
+ self._start_date = as_utc(start_date) if start_date else None
+ self.materials = materials
+ self.authors = authors
+ self.description = description
+
+ @property
+ def event(self):
+ return ConferenceHolder().getById(self.event_id, quiet=True)
+
+ @property
+ def start_date(self):
+ if not self._start_date:
+ return None
+ tz = DisplayTZ(conf=self.event).getDisplayTZ()
+ return self._start_date.astimezone(timezone(tz))
+
+ @property
+ def object(self):
+ raise NotImplementedError
+
+ @property
+ def event_id(self):
+ raise NotImplementedError
+
+ @property
+ def compound_id(self):
+ raise NotImplementedError
+
+ def is_visible(self, user):
+ obj = self.object
+ if not obj:
+ current_plugin.logger.warning('referenced element {} does not exist'.format(self.compound_id))
+ return False
+ return obj.canView(AccessWrapper(user))
+
+ def __repr__(self):
+ return '<{}({})>'.format(type(self).__name__, self.compound_id)
+
+
+class ContributionEntry(SearchResult):
+ def __init__(self, entry_id, title, location, start_date, materials, authors, description, parent):
+ super(ContributionEntry, self).__init__(entry_id, title, location, start_date, materials, authors, description)
+ self.parent = parent
+
+ @property
+ def event_id(self):
+ return self.parent
+
+ @property
+ def url(self):
+ return url_for('event.contributionDisplay', confId=self.event_id, contribId=self.id)
+
+ @property
+ def compound_id(self):
+ return '{}:{}'.format(self.event_id, self.id)
+
+ @property
+ def object(self):
+ event = self.event
+ if not event:
+ return None
+ return event.getContributionById(self.id)
+
+
+class SubContributionEntry(SearchResult):
+ def __init__(self, entry_id, title, location, start_date, materials, authors, description, parent, event):
+ super(SubContributionEntry, self).__init__(entry_id, title, location, start_date, materials, authors,
+ description)
+ self.parent = parent
+ self._event_id = event
+ materials.append((url_for('event.conferenceDisplay', confId=event), 'Event details'))
+
+ @property
+ def event_id(self):
+ return self._event_id
+
+ @property
+ def url(self):
+ return url_for('event.subContributionDisplay', confId=self.event_id, contribId=self.parent, subContId=self.id)
+
+ @property
+ def compound_id(self):
+ return '{}:{}:{}'.format(self.event_id, self.parent, self.id)
+
+ @property
+ def object(self):
+ event = self.event
+ if not event:
+ return None
+ contribution = event.getContributionById(self.parent)
+ if not contribution:
+ return None
+ return contribution.getSubContributionById(self.id)
+
+
+class EventEntry(SearchResult):
+ @property
+ def url(self):
+ return url_for('event.conferenceDisplay', confId=self.id)
+
+ @property
+ def compound_id(self):
+ return self.id
+
+ @property
+ def event_id(self):
+ return self.id
+
+ @property
+ def object(self):
+ return self.event
diff --git a/search_invenio/indico_search_invenio/forms.py b/search_invenio/indico_search_invenio/forms.py
new file mode 100644
index 0000000..244717c
--- /dev/null
+++ b/search_invenio/indico_search_invenio/forms.py
@@ -0,0 +1,34 @@
+# This file is part of Indico.
+# Copyright (C) 2002 - 2014 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 wtforms.fields.core import SelectField
+
+from indico.util.i18n import _
+from indico_search import SearchForm
+
+
+COLLECTION_CHOICES = [('', _('Both (Events + Contributions)')),
+ ('events', _('Events')),
+ ('contributions', _('Contributions'))]
+SORT_ORDER_CHOICES = [('a', _('Oldest first')),
+ ('d', _('Newest first'))]
+
+
+class InvenioSearchForm(SearchForm):
+ collection = SelectField(_('Search for'), choices=COLLECTION_CHOICES, default='')
+ sort_order = SelectField(_('Sort order'), choices=SORT_ORDER_CHOICES, default='d')
diff --git a/search_invenio/indico_search_invenio/marc2short.xsl b/search_invenio/indico_search_invenio/marc2short.xsl
new file mode 100644
index 0000000..a819b32
--- /dev/null
+++ b/search_invenio/indico_search_invenio/marc2short.xsl
@@ -0,0 +1,145 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/search_invenio/indico_search_invenio/plugin.py b/search_invenio/indico_search_invenio/plugin.py
new file mode 100644
index 0000000..d691fec
--- /dev/null
+++ b/search_invenio/indico_search_invenio/plugin.py
@@ -0,0 +1,68 @@
+# This file is part of Indico.
+# Copyright (C) 2002 - 2014 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 wtforms.fields.core import SelectField
+from wtforms.fields.html5 import URLField, IntegerField
+from wtforms.validators import URL, NumberRange
+
+from indico.core.plugins import IndicoPluginBlueprint
+from indico.util.i18n import _
+from indico.web.forms.base import IndicoForm
+from indico_search import SearchPluginBase
+from indico_search.views import WPSearchCategory, WPSearchConference
+
+from indico_search_invenio.engine import InvenioSearchEngine
+from indico_search_invenio.forms import InvenioSearchForm
+
+
+class SettingsForm(IndicoForm):
+ search_url = URLField(_('Invenio URL'), [URL()])
+ display_mode = SelectField(_('Display mode'), choices=[('api_public', _('Embedded (public data)')),
+ ('api_private', _('Embedded (private data)')),
+ ('redirect', _('External (Redirect)'))])
+ results_per_page = IntegerField(_('Results per page'), [NumberRange(min=5)],
+ description=_("Number of results to show per page (only in embedded mode)"))
+
+
+class InvenioSearchPlugin(SearchPluginBase):
+ """Invenio Search
+
+ Uses Invenio as Indico's search engine
+ """
+
+ settings_form = SettingsForm
+ default_settings = {
+ 'search_url': None,
+ 'display_mode': 'redirect',
+ 'results_per_page': 20
+ }
+ engine_class = InvenioSearchEngine
+ search_form = InvenioSearchForm
+
+ def init(self):
+ super(InvenioSearchPlugin, self).init()
+ for wp in (WPSearchCategory, WPSearchConference):
+ self.inject_css('search_invenio_css', wp)
+ self.inject_js('search_invenio_js', wp)
+
+ def register_assets(self):
+ self.register_css_bundle('search_invenio_css', 'css/search_invenio.scss')
+ self.register_js_bundle('search_invenio_js', 'js/search_invenio.js')
+
+ def get_blueprints(self):
+ return IndicoPluginBlueprint('search_invenio', 'indico_search_invenio')
diff --git a/search_invenio/indico_search_invenio/static/css/search_invenio.scss b/search_invenio/indico_search_invenio/static/css/search_invenio.scss
new file mode 100644
index 0000000..022a45b
--- /dev/null
+++ b/search_invenio/indico_search_invenio/static/css/search_invenio.scss
@@ -0,0 +1,67 @@
+#result-tabs {
+ clear: both;
+
+ .no-results {
+ margin-top: 20px;
+ color: #f00;
+ }
+}
+
+.results-container {
+ .load-more-container {
+ overflow: auto;
+ text-align: center;
+
+ a {
+ font-weight: bold;
+ }
+ }
+
+ ul.result-list {
+ padding: 0;
+ list-style-type: none;
+
+ & > li {
+ border-bottom: 1px solid #ddd;
+ padding: 5px;
+
+ .title {
+ font-size: 14px;
+ }
+
+ .date {
+ display: block;
+ }
+
+ .parent-event {
+ display: block;
+ font-style: italic;
+ }
+
+ .link {
+ color: #76A2C4;
+ }
+
+ ul.authors {
+ margin-bottom: 5px;
+ margin-top: 5px;
+ }
+
+ ul.material {
+ margin-top: 5px;
+ margin-left: 20px;
+ }
+
+ .description {
+ margin: 5px 5px 5px 10px;
+ padding: 5px;
+ font-size: 13px;
+ color: #666;
+
+ a.show-full-desc {
+ margin-bottom: 10px;
+ }
+ }
+ }
+ }
+}
diff --git a/search_invenio/indico_search_invenio/static/images/logo.png b/search_invenio/indico_search_invenio/static/images/logo.png
new file mode 100644
index 0000000..683871f
Binary files /dev/null and b/search_invenio/indico_search_invenio/static/images/logo.png differ
diff --git a/search_invenio/indico_search_invenio/static/js/search_invenio.js b/search_invenio/indico_search_invenio/static/js/search_invenio.js
new file mode 100644
index 0000000..4f37922
--- /dev/null
+++ b/search_invenio/indico_search_invenio/static/js/search_invenio.js
@@ -0,0 +1,75 @@
+/*
+ * This file is part of Indico.
+ * Copyright (C) 2002 - 2014 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 .
+ */
+
+(function(global) {
+ 'use strict';
+
+ global.invenioSearchResults = function invenioSearchResults(options) {
+ // expected options: queryData, url, hasEvents, hasContribs
+
+ var tabList = [];
+ if (options.hasEvents) {
+ tabList.push([$T('Events'), $E('results-events')]);
+ }
+ if (options.hasContribs) {
+ tabList.push([$T('Contributions'), $E('results-contribs')]);
+ }
+ var tabCtrl = new JTabWidget(tabList);
+ $E('result-tabs').set(tabCtrl.draw());
+ $('#result-tabs > div').css({display: 'table', width: '100%'});
+
+ $('#result-tabs').on('click', '.js-show-full-desc', function(e) {
+ e.preventDefault();
+ var desc = $(this).next().remove().text();
+ $(this).parent().html(desc);
+ });
+
+ $('.js-load-more').on('click', function(e) {
+ e.preventDefault();
+ var $this = $(this);
+ var collection = $this.data('collection');
+ $.ajax({
+ url: options.url,
+ type: 'POST',
+ traditional: true, // we don't want foo[] for list data!
+ data: $.extend({}, options.queryData, {
+ 'offset': $this.data('offset'),
+ 'search-collection': collection
+ }),
+ dataType: 'json',
+ complete: IndicoUI.Dialogs.Util.progress(),
+ success: function(data) {
+ if (handleAjaxError(data)) {
+ return;
+ }
+ var resultList = $this.closest('.js-results-container').find('.result-list');
+ var lastOld = resultList.children().last();
+ resultList.append(data.html);
+ $('html, body').animate({
+ scrollTop: lastOld.next().offset().top
+ }, 250);
+ lastOld.nextAll().effect('highlight', 1000);
+ $this.data('offset', data.offset);
+ if (!data.has_more) {
+ $this.parent().hide();
+ }
+ }
+ });
+ });
+ };
+})(window);
diff --git a/search_invenio/indico_search_invenio/templates/_results.html b/search_invenio/indico_search_invenio/templates/_results.html
new file mode 100644
index 0000000..db58c3f
--- /dev/null
+++ b/search_invenio/indico_search_invenio/templates/_results.html
@@ -0,0 +1,39 @@
+{# This macro is used by results.html and also in the InvenioRemoteSearch class! #}
+{% macro render_result(result, is_event) %}
+
+ {% if result.title %}
+ {{ result.title }}
+ {% endif %}
+ {% if result.start_date %}
+ {{ result.start_date.strftime('%Y-%m-%d %H:%M:%S (%Z)') }}
+ {% endif %}
+ {% if not is_event %}
+ {{ result.event.title }}
+ {% endif %}
+ {% if result.authors %}
+
+ {% for author in result.authors %}
+ -
+ {{ author.name }}
+ ({{ author.role }}{% if author.affiliation %}, {{ author.affiliation }}{% endif %})
+
+ {% endfor %}
+
+ {% endif %}
+ {% if result.description %}
+ {% set truncated = result.description | truncate(100) %}
+
+ {{ truncated }}
+ {% if truncated != result.description %}
+
more
+
{{ result.description }}
+ {% endif %}
+
+ {% endif %}
+
+ {% for link, title in result.materials %}
+ - {{ title }}
+ {% endfor %}
+
+
+{% endmacro %}
diff --git a/search_invenio/indico_search_invenio/templates/results.html b/search_invenio/indico_search_invenio/templates/results.html
new file mode 100644
index 0000000..d68043b
--- /dev/null
+++ b/search_invenio/indico_search_invenio/templates/results.html
@@ -0,0 +1,76 @@
+{% extends 'search:results.html' %}
+{% from 'search_invenio:_results.html' import render_result %}
+
+{% block banner %}
+
+{% endblock %}
+
+{% block criteria_fields %}
+
+ | {{ form.collection.label() }} |
+ {{ form.collection() }} |
+
+{% endblock %}
+
+{% block sort_fields %}
+
+ | {{ form.sort_order.label() }} |
+ {{ form.sort_order() }} |
+
+{% endblock %}
+
+{% block results %}
+
+ {% if not result.events and not result.contributions %}
+
{% trans %}No results found{% endtrans %}
+ {% else %}
+ {% if result.events %}
+
+
+ {% for event in result.events.results %}
+ {{ render_result(event, is_event=true) }}
+ {% endfor %}
+
+
+ {% if result.events.has_more %}
+
+ {% endif %}
+
+ {% endif %}
+
+ {% if result.contributions %}
+
+
+ {% for contrib in result.contributions.results %}
+ {{ render_result(contrib, is_event=false) }}
+ {% endfor %}
+
+
+ {% if result.contributions.has_more %}
+
+ {% endif %}
+
+ {% endif %}
+ {% endif %}
+
+{% endblock %}
+
+{% block scripts %}
+ {% if result.events or result.contributions %}
+
+ {% endif %}
+{% endblock %}
diff --git a/search_invenio/indico_search_invenio/templates/searchbox_category.html b/search_invenio/indico_search_invenio/templates/searchbox_category.html
new file mode 100644
index 0000000..97c5d1a
--- /dev/null
+++ b/search_invenio/indico_search_invenio/templates/searchbox_category.html
@@ -0,0 +1,8 @@
+{% extends 'search:searchbox_category.html' %}
+
+{% block extra_fields %}
+
+ | {{ form.collection.label() }} |
+ {{ form.collection() }} |
+
+{% endblock %}
diff --git a/search_invenio/indico_search_invenio/zodbimport.py b/search_invenio/indico_search_invenio/zodbimport.py
new file mode 100644
index 0000000..96b99e7
--- /dev/null
+++ b/search_invenio/indico_search_invenio/zodbimport.py
@@ -0,0 +1,42 @@
+# This file is part of Indico.
+# Copyright (C) 2002 - 2014 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.db import db
+from indico.util.console import cformat
+from indico_zodbimport import Importer, convert_to_unicode
+
+from indico_search_invenio.plugin import InvenioSearchPlugin
+
+
+class InvenioSearchImporter(Importer):
+ plugins = {'search', 'search_invenio'}
+
+ def migrate(self):
+ self.migrate_settings()
+
+ def migrate_settings(self):
+ print cformat('%{white!}migrating settings')
+ InvenioSearchPlugin.settings.delete_all()
+ opts = self.zodb_root['plugins']['search']._PluginType__plugins['invenio']._PluginBase__options
+ InvenioSearchPlugin.settings.set('search_url', convert_to_unicode(opts['serverUrl'].getValue()).strip())
+ type_map = {'public': 'api_public',
+ 'private': 'api_private',
+ 'redirect': 'redirect'}
+ display_mode = type_map[opts['type'].getValue()]
+ InvenioSearchPlugin.settings.set('display_mode', display_mode)
+ db.session.commit()
diff --git a/search_invenio/setup.py b/search_invenio/setup.py
new file mode 100644
index 0000000..613d8bb
--- /dev/null
+++ b/search_invenio/setup.py
@@ -0,0 +1,45 @@
+# This file is part of Indico.
+# Copyright (C) 2002 - 2014 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 setup, find_packages
+
+
+setup(
+ name='indico_search_invenio',
+ version='0.1',
+ url='https://github.com/indico/indico-plugin-search-invenio',
+ 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,
+ platforms='any',
+ install_requires=[
+ 'indico>=1.9.1',
+ 'indico_search'
+ ],
+ 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': {'search_invenio = indico_search_invenio.plugin:InvenioSearchPlugin'},
+ 'indico.zodb_importers': {'search_invenio = indico_search_invenio.zodbimport:InvenioSearchImporter'}}
+)