diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 708b82a189..a99ff2d0a0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -61,6 +61,9 @@ jobs: - name: Run checks run: python manage.py check working-directory: ./src + - name: Install JS dependencies + working-directory: ./src + run: make npminstall - name: Compile working-directory: ./src run: make all compress diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7c7b9554c7..310eb168c4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -24,6 +24,7 @@ pypi: - XDG_CACHE_HOME=/cache pip3 install -Ur src/requirements.txt -r src/requirements/dev.txt - cd src - python setup.py sdist + - make npminstall - pip install dist/pretix-*.tar.gz - python -m pretix migrate - python -m pretix check diff --git a/Dockerfile b/Dockerfile index a18c54e428..0cf8a2260c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -67,6 +67,7 @@ RUN chmod +x /usr/local/bin/pretix && \ rm -f pretix.cfg && \ mkdir -p data && \ chown -R pretixuser:pretixuser /pretix /data data && \ + sudo -u pretixuser make npminstall && \ sudo -u pretixuser make production USER pretixuser diff --git a/doc/api/resources/checkinlists.rst b/doc/api/resources/checkinlists.rst index edb564d98e..2a08ee50ad 100644 --- a/doc/api/resources/checkinlists.rst +++ b/doc/api/resources/checkinlists.rst @@ -49,6 +49,10 @@ exit_all_at datetime Automatically c The ``exit_all_at`` attribute has been added. +.. versionchanged:: 3.17 + + The ``ends_after`` and ``expand`` query parameters have been added. + Endpoints --------- @@ -100,6 +104,8 @@ Endpoints :query integer page: The page number in case of a multi-page result set, default is 1 :query integer subevent: Only return check-in lists of the sub-event with the given ID :query integer subevent_match: Only return check-in lists that are valid for the sub-event with the given ID (i.e. also lists valid for all subevents) + :query string ends_after: Exclude all check-in lists attached to a sub-event that is already in the past at the given time. + :query string expand: Expand a field into a full object. Currently only ``subevent`` is supported. Can be passed multiple times. :query string exclude: Exclude a field from the output, e.g. ``checkin_count``. Can be used as a performance optimization. Can be passed multiple times. :param organizer: The ``slug`` field of the organizer to fetch :param event: The ``slug`` field of the event to fetch @@ -447,6 +453,7 @@ Order position endpoints ``attendee_name,positionid`` :query string order: Only return positions of the order with the given order code :query string search: Fuzzy search matching the attendee name, order code, invoice address name as well as to the beginning of the secret. + :query string expand: Expand a field into a full object. Currently only ``subevent``, ``item``, and ``variation`` are supported. Can be passed multiple times. :query integer item: Only return positions with the purchased item matching the given ID. :query integer item__in: Only return positions with the purchased item matching one of the given comma-separated IDs. :query integer variation: Only return positions with the purchased item variation matching the given ID. diff --git a/doc/api/resources/teams.rst b/doc/api/resources/teams.rst index 7eda8fd921..d94cd3b391 100644 --- a/doc/api/resources/teams.rst +++ b/doc/api/resources/teams.rst @@ -1,4 +1,4 @@ -.. spelling:: fullname +.. spelling:: fullname checkin .. _`rest-teams`: @@ -32,6 +32,7 @@ can_view_orders boolean can_change_orders boolean can_view_vouchers boolean can_change_vouchers boolean +can_checkin_orders boolean ===================================== ========================== ======================================================= Team member resource diff --git a/doc/development/setup.rst b/doc/development/setup.rst index 73d98dbdb7..d019846ff7 100644 --- a/doc/development/setup.rst +++ b/doc/development/setup.rst @@ -67,6 +67,10 @@ Then, create the local database:: A first user with username ``admin@localhost`` and password ``admin`` will be automatically created. +You will also need to install a few JavaScript dependencies:: + + make npminstall + If you want to see pretix in a different language than English, you have to compile our language files:: diff --git a/doc/user/organizers/teams.rst b/doc/user/organizers/teams.rst index 0cc2526027..09b9855cb5 100644 --- a/doc/user/organizers/teams.rst +++ b/doc/user/organizers/teams.rst @@ -59,6 +59,8 @@ Permissions separate into two areas: * Can view vouchers – This permission allows viewing the list of vouchers including the voucher codes themselves and their redemption status. + * Can perform check-ins – This permission allows using web-based features to perform ticket search and check-in. + * Can change vouchers – This permission allows to create and modify vouchers in all their details. It only works properly if the same users also have the "Can view vouchers" permission. diff --git a/src/.gitignore b/src/.gitignore index 57e167277b..0cbb6390b9 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -8,3 +8,5 @@ dist/ *.egg-info/ *.bak pretix/static/jsi18n/ +node_modules/ + diff --git a/src/Makefile b/src/Makefile index 1815e22eb1..6a1aa19350 100644 --- a/src/Makefile +++ b/src/Makefile @@ -12,7 +12,7 @@ localegen: staticfiles: jsi18n ./manage.py collectstatic --noinput -compress: +compress: npminstall ./manage.py compress jsi18n: localecompile @@ -25,6 +25,7 @@ coverage: coverage run -m py.test npminstall: - mkdir -p pretix/static.dist/node_prefix - npm install --prefix=pretix/static.dist/node_prefix pretix/static/npm_dir/ + mkdir -p pretix/static.dist/node_prefix/ + cp -r pretix/static/npm_dir/* pretix/static.dist/node_prefix/ + npm install --prefix=pretix/static.dist/node_prefix diff --git a/src/pretix/api/auth/permission.py b/src/pretix/api/auth/permission.py index 05db800c86..4875b30b75 100644 --- a/src/pretix/api/auth/permission.py +++ b/src/pretix/api/auth/permission.py @@ -46,8 +46,12 @@ class EventPermission(BasePermission): else: request.eventpermset = perm_holder.get_event_permission_set(request.organizer, request.event) - if required_permission and required_permission not in request.eventpermset: - return False + if isinstance(required_permission, (list, tuple)): + if not any(p in request.eventpermset for p in required_permission): + return False + else: + if required_permission and required_permission not in request.eventpermset: + return False elif 'organizer' in request.resolver_match.kwargs: if not request.organizer or not perm_holder.has_organizer_permission(request.organizer, request=request): @@ -57,8 +61,12 @@ class EventPermission(BasePermission): else: request.orgapermset = perm_holder.get_organizer_permission_set(request.organizer) - if required_permission and required_permission not in request.orgapermset: - return False + if isinstance(required_permission, (list, tuple)): + if not any(p in request.eventpermset for p in required_permission): + return False + else: + if required_permission and required_permission not in request.orgapermset: + return False if isinstance(request.auth, OAuthAccessToken): if not request.auth.allow_scopes(['write']) and request.method not in SAFE_METHODS: diff --git a/src/pretix/api/serializers/checkin.py b/src/pretix/api/serializers/checkin.py index 4f7b4aff91..daaff47979 100644 --- a/src/pretix/api/serializers/checkin.py +++ b/src/pretix/api/serializers/checkin.py @@ -2,6 +2,7 @@ from django.utils.translation import gettext as _ from rest_framework import serializers from rest_framework.exceptions import ValidationError +from pretix.api.serializers.event import SubEventSerializer from pretix.api.serializers.i18n import I18nAwareModelSerializer from pretix.base.channels import get_all_sales_channels from pretix.base.models import CheckinList @@ -20,6 +21,9 @@ class CheckinListSerializer(I18nAwareModelSerializer): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + if 'subevent' in self.context['request'].query_params.getlist('expand'): + self.fields['subevent'] = SubEventSerializer(read_only=True) + for exclude_field in self.context['request'].query_params.getlist('exclude'): p = exclude_field.split('.') if p[0] in self.fields: @@ -50,4 +54,6 @@ class CheckinListSerializer(I18nAwareModelSerializer): if channel not in get_all_sales_channels(): raise ValidationError(_('Unknown sales channel.')) + CheckinList.validate_rules(data.get('rules')) + return data diff --git a/src/pretix/api/serializers/order.py b/src/pretix/api/serializers/order.py index 7ba2a607f8..438872cda8 100644 --- a/src/pretix/api/serializers/order.py +++ b/src/pretix/api/serializers/order.py @@ -13,7 +13,11 @@ from rest_framework.exceptions import ValidationError from rest_framework.relations import SlugRelatedField from rest_framework.reverse import reverse +from pretix.api.serializers.event import SubEventSerializer from pretix.api.serializers.i18n import I18nAwareModelSerializer +from pretix.api.serializers.item import ( + InlineItemVariationSerializer, ItemSerializer, +) from pretix.base.channels import get_all_sales_channels from pretix.base.decimal import round_decimal from pretix.base.i18n import language @@ -349,8 +353,9 @@ class OrderPositionSerializer(I18nAwareModelSerializer): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - if 'request' in self.context and not self.context['request'].query_params.get('pdf_data', 'false') == 'true': - self.fields.pop('pdf_data') + request = self.context.get('request') + if not request or not self.context['request'].query_params.get('pdf_data', 'false') == 'true' or 'can_view_orders' not in request.eventpermset: + self.fields.pop('pdf_data', None) def validate(self, data): if data.get('attendee_name') and data.get('attendee_name_parts'): @@ -484,6 +489,18 @@ class CheckinListOrderPositionSerializer(OrderPositionSerializer): 'downloads', 'answers', 'tax_rule', 'pseudonymization_id', 'pdf_data', 'seat', 'require_attention', 'order__status') + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + if 'subevent' in self.context['request'].query_params.getlist('expand'): + self.fields['subevent'] = SubEventSerializer(read_only=True) + + if 'item' in self.context['request'].query_params.getlist('expand'): + self.fields['item'] = ItemSerializer(read_only=True) + + if 'variation' in self.context['request'].query_params.getlist('expand'): + self.fields['variation'] = InlineItemVariationSerializer(read_only=True) + class OrderPaymentTypeField(serializers.Field): # TODO: Remove after pretix 2.2 @@ -584,7 +601,7 @@ class OrderSerializer(I18nAwareModelSerializer): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) if not self.context['request'].query_params.get('pdf_data', 'false') == 'true': - self.fields['positions'].child.fields.pop('pdf_data') + self.fields['positions'].child.fields.pop('pdf_data', None) for exclude_field in self.context['request'].query_params.getlist('exclude'): p = exclude_field.split('.') diff --git a/src/pretix/api/serializers/organizer.py b/src/pretix/api/serializers/organizer.py index 6e2c67a21c..fb64587b3e 100644 --- a/src/pretix/api/serializers/organizer.py +++ b/src/pretix/api/serializers/organizer.py @@ -95,7 +95,7 @@ class TeamSerializer(serializers.ModelSerializer): 'id', 'name', 'all_events', 'limit_events', 'can_create_events', 'can_change_teams', 'can_change_organizer_settings', 'can_manage_gift_cards', 'can_change_event_settings', 'can_change_items', 'can_view_orders', 'can_change_orders', 'can_view_vouchers', - 'can_change_vouchers' + 'can_change_vouchers', 'can_checkin_orders' ) def validate(self, data): diff --git a/src/pretix/api/views/checkin.py b/src/pretix/api/views/checkin.py index 75919b1cb3..78494d5506 100644 --- a/src/pretix/api/views/checkin.py +++ b/src/pretix/api/views/checkin.py @@ -25,13 +25,14 @@ from pretix.base.models import ( CachedFile, Checkin, CheckinList, Event, Order, OrderPosition, Question, ) from pretix.base.services.checkin import ( - CheckInError, RequiredQuestionsError, perform_checkin, + CheckInError, RequiredQuestionsError, SQLLogic, perform_checkin, ) from pretix.helpers.database import FixedOrderBy with scopes_disabled(): class CheckinListFilter(FilterSet): subevent_match = django_filters.NumberFilter(method='subevent_match_qs') + ends_after = django_filters.rest_framework.IsoDateTimeFilter(method='ends_after_qs') class Meta: model = CheckinList @@ -42,19 +43,35 @@ with scopes_disabled(): Q(subevent_id=value) | Q(subevent_id__isnull=True) ) + def ends_after_qs(self, queryset, name, value): + expr = ( + Q(subevent__isnull=True) | + Q( + Q(Q(subevent__date_to__isnull=True) & Q(subevent__date_from__gte=value)) + | Q(Q(subevent__date_to__isnull=False) & Q(subevent__date_to__gte=value)) + ) + ) + return queryset.filter(expr) + class CheckinListViewSet(viewsets.ModelViewSet): serializer_class = CheckinListSerializer queryset = CheckinList.objects.none() filter_backends = (DjangoFilterBackend,) filterset_class = CheckinListFilter - permission = 'can_view_orders' + permission = ('can_view_orders', 'can_checkin_orders',) write_permission = 'can_change_event_settings' def get_queryset(self): qs = self.request.event.checkin_lists.prefetch_related( 'limit_products', ) + + if 'subevent' in self.request.query_params.getlist('expand'): + qs = qs.prefetch_related( + 'subevent', 'subevent__event', 'subevent__subeventitem_set', 'subevent__subeventitemvariation_set', + 'subevent__seat_category_mappings', 'subevent__meta_values' + ) return qs def perform_create(self, serializer): @@ -155,15 +172,37 @@ class CheckinListViewSet(viewsets.ModelViewSet): with scopes_disabled(): class CheckinOrderPositionFilter(OrderPositionFilter): + check_rules = django_filters.rest_framework.BooleanFilter(method='check_rules_qs') + # check_rules is currently undocumented on purpose, let's get a feel for the performance impact first + + def __init__(self, *args, **kwargs): + self.checkinlist = kwargs.pop('checkinlist') + super().__init__(*args, **kwargs) def has_checkin_qs(self, queryset, name, value): return queryset.filter(last_checked_in__isnull=not value) + def check_rules_qs(self, queryset, name, value): + if not self.checkinlist.rules: + return queryset + return queryset.filter(SQLLogic(self.checkinlist).apply(self.checkinlist.rules)) + + +class ExtendedBackend(DjangoFilterBackend): + def get_filterset_kwargs(self, request, queryset, view): + kwargs = super().get_filterset_kwargs(request, queryset, view) + + # merge filterset kwargs provided by view class + if hasattr(view, 'get_filterset_kwargs'): + kwargs.update(view.get_filterset_kwargs()) + + return kwargs + class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet): serializer_class = CheckinListOrderPositionSerializer queryset = OrderPosition.all.none() - filter_backends = (DjangoFilterBackend, RichOrderingFilter) + filter_backends = (ExtendedBackend, RichOrderingFilter) ordering = ('attendee_name_cached', 'positionid') ordering_fields = ( 'order__code', 'order__datetime', 'positionid', 'attendee_name', @@ -187,8 +226,13 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet): } filterset_class = CheckinOrderPositionFilter - permission = 'can_view_orders' - write_permission = 'can_change_orders' + permission = ('can_view_orders', 'can_checkin_orders') + write_permission = ('can_change_orders', 'can_checkin_orders') + + def get_filterset_kwargs(self): + return { + 'checkinlist': self.checkinlist, + } @cached_property def checkinlist(self): @@ -209,7 +253,7 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet): order__event=self.request.event, ).annotate( last_checked_in=Subquery(cqs) - ) + ).prefetch_related('order__event', 'order__event__organizer') if self.checkinlist.subevent: qs = qs.filter( subevent=self.checkinlist.subevent @@ -255,6 +299,22 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet): if not self.checkinlist.all_products and not ignore_products: qs = qs.filter(item__in=self.checkinlist.limit_products.values_list('id', flat=True)) + if 'subevent' in self.request.query_params.getlist('expand'): + qs = qs.prefetch_related( + 'subevent', 'subevent__event', 'subevent__subeventitem_set', 'subevent__subeventitemvariation_set', + 'subevent__seat_category_mappings', 'subevent__meta_values' + ) + + if 'item' in self.request.query_params.getlist('expand'): + qs = qs.prefetch_related('item', 'item__addons', 'item__bundles', 'item__meta_values', 'item__variations').select_related('item__tax_rule') + + if 'variation' in self.request.query_params.getlist('expand'): + qs = qs.prefetch_related('variation') + + if 'pk' not in self.request.resolver_match.kwargs and 'can_view_orders' not in self.request.eventpermset \ + and len(self.request.query_params.get('search', '')) < 3: + qs = qs.none() + return qs @action(detail=False, methods=['POST'], url_name='redeem', url_path='(?P.*)/redeem') diff --git a/src/pretix/api/views/event.py b/src/pretix/api/views/event.py index 8c36577c5a..13515bfeab 100644 --- a/src/pretix/api/views/event.py +++ b/src/pretix/api/views/event.py @@ -259,7 +259,7 @@ class SubEventViewSet(ConditionalListView, viewsets.ModelViewSet): qs = filter_qs_by_attr(qs, self.request) return qs.prefetch_related( - 'subeventitem_set', 'subeventitemvariation_set', 'seat_category_mappings' + 'subeventitem_set', 'subeventitemvariation_set', 'seat_category_mappings', 'meta_values' ) def list(self, request, **kwargs): diff --git a/src/pretix/api/views/item.py b/src/pretix/api/views/item.py index ff90522885..c231d3fb9a 100644 --- a/src/pretix/api/views/item.py +++ b/src/pretix/api/views/item.py @@ -49,7 +49,9 @@ class ItemViewSet(ConditionalListView, viewsets.ModelViewSet): write_permission = 'can_change_items' def get_queryset(self): - return self.request.event.items.select_related('tax_rule').prefetch_related('variations', 'addons', 'bundles').all() + return self.request.event.items.select_related('tax_rule').prefetch_related( + 'variations', 'addons', 'bundles', 'meta_values' + ).all() def perform_create(self, serializer): serializer.save(event=self.request.event) diff --git a/src/pretix/base/migrations/0181_team_can_checkin_orders.py b/src/pretix/base/migrations/0181_team_can_checkin_orders.py new file mode 100644 index 0000000000..0819a36fe3 --- /dev/null +++ b/src/pretix/base/migrations/0181_team_can_checkin_orders.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.12 on 2021-03-29 08:12 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pretixbase', '0180_auto_20210324_1309'), + ] + + operations = [ + migrations.AddField( + model_name='team', + name='can_checkin_orders', + field=models.BooleanField(default=False), + ), + ] diff --git a/src/pretix/base/models/checkin.py b/src/pretix/base/models/checkin.py index c4210f09a9..46502a1dfb 100644 --- a/src/pretix/base/models/checkin.py +++ b/src/pretix/base/models/checkin.py @@ -1,4 +1,5 @@ from django.conf import settings +from django.core.exceptions import ValidationError from django.db import models from django.db.models import Exists, F, Max, OuterRef, Q, Subquery from django.utils.timezone import now @@ -142,6 +143,54 @@ class CheckinList(LoggedModel): def __str__(self): return self.name + @classmethod + def validate_rules(cls, rules, seen_nonbool=False, depth=0): + # While we implement a full jsonlogic machine on Python-level, we also use the logic rules to generate + # SQL queries, which is not a full implementation of JSON logic right now, but makes some assumptions, + # e.g. it does not support something like (a AND b) == (c OR D) + # Every change to our supported JSON logic must be done + # * in pretix.base.services.checkin + # * in pretix.base.models.checkin + # * in checkinrules.js + # * in libpretixsync + top_level_operators = { + '<', '<=', '>', '>=', '==', '!=', 'inList', 'isBefore', 'isAfter', 'or', 'and' + } + allowed_operators = top_level_operators | { + 'buildTime', 'objectList', 'lookup', 'var', + } + allowed_vars = { + 'product', 'variation', 'now', 'entries_number', 'entries_today', 'entries_days' + } + if not rules or not isinstance(rules, dict): + return + + if len(rules) > 1: + raise ValidationError(f'Rules should not include dictionaries with more than one key, found: "{rules}".') + + operator = list(rules.keys())[0] + + if operator not in allowed_operators: + raise ValidationError(f'Logic operator "{operator}" is currently not allowed.') + + if depth == 0 and operator not in top_level_operators: + raise ValidationError(f'Logic operator "{operator}" is currently not allowed on the first level.') + + values = rules[operator] + if not isinstance(values, list) and not isinstance(values, tuple): + values = [values] + + if operator == 'var': + if values[0] not in allowed_vars: + raise ValidationError(f'Logic variable "{values[0]}" is currently not allowed.') + return + + if operator in ('or', 'and') and seen_nonbool: + raise ValidationError(f'You cannot use OR/AND logic on a level below a comparison operator.') + + for v in values: + cls.validate_rules(v, seen_nonbool=seen_nonbool or operator not in ('or', 'and'), depth=depth + 1) + class Checkin(models.Model): """ diff --git a/src/pretix/base/models/items.py b/src/pretix/base/models/items.py index 83e81b7892..f3a1721b81 100644 --- a/src/pretix/base/models/items.py +++ b/src/pretix/base/models/items.py @@ -1088,17 +1088,23 @@ class Question(LoggedModel): ) dependency_values = MultiStringField(default=[]) valid_number_min = models.DecimalField(decimal_places=6, max_digits=16, null=True, blank=True, - verbose_name=_('Minimum value'), help_text=_('Currently not supported in our apps')) + verbose_name=_('Minimum value'), + help_text=_('Currently not supported in our apps and during check-in')) valid_number_max = models.DecimalField(decimal_places=6, max_digits=16, null=True, blank=True, - verbose_name=_('Maximum value'), help_text=_('Currently not supported in our apps')) + verbose_name=_('Maximum value'), + help_text=_('Currently not supported in our apps and during check-in')) valid_date_min = models.DateField(null=True, blank=True, - verbose_name=_('Minimum value'), help_text=_('Currently not supported in our apps')) + verbose_name=_('Minimum value'), + help_text=_('Currently not supported in our apps and during check-in')) valid_date_max = models.DateField(null=True, blank=True, - verbose_name=_('Maximum value'), help_text=_('Currently not supported in our apps')) + verbose_name=_('Maximum value'), + help_text=_('Currently not supported in our apps and during check-in')) valid_datetime_min = models.DateTimeField(null=True, blank=True, - verbose_name=_('Minimum value'), help_text=_('Currently not supported in our apps')) + verbose_name=_('Minimum value'), + help_text=_('Currently not supported in our apps and during check-in')) valid_datetime_max = models.DateTimeField(null=True, blank=True, - verbose_name=_('Maximum value'), help_text=_('Currently not supported in our apps')) + verbose_name=_('Maximum value'), + help_text=_('Currently not supported in our apps and during check-in')) objects = ScopedManager(organizer='event__organizer') diff --git a/src/pretix/base/models/organizer.py b/src/pretix/base/models/organizer.py index eb0b09d88a..cb3e09b817 100644 --- a/src/pretix/base/models/organizer.py +++ b/src/pretix/base/models/organizer.py @@ -174,6 +174,8 @@ class Team(LoggedModel): :type can_view_orders: bool :param can_change_orders: If ``True``, the members can change details of orders of the associated events. :type can_change_orders: bool + :param can_checkin_orders: If ``True``, the members can perform check-in related actions. + :type can_checkin_orders: bool :param can_view_vouchers: If ``True``, the members can inspect details of all vouchers of the associated events. :type can_view_vouchers: bool :param can_change_vouchers: If ``True``, the members can change and create vouchers for the associated events. @@ -220,6 +222,12 @@ class Team(LoggedModel): default=False, verbose_name=_("Can change orders") ) + can_checkin_orders = models.BooleanField( + default=False, + verbose_name=_("Can perform check-ins"), + help_text=_('This includes searching for attendees, which can be used to obtain personal information about ' + 'attendees. Users with "can change orders" can also perform check-ins.') + ) can_view_vouchers = models.BooleanField( default=False, verbose_name=_("Can view vouchers") diff --git a/src/pretix/base/notifications.py b/src/pretix/base/notifications.py index 7041a5b282..1440198867 100644 --- a/src/pretix/base/notifications.py +++ b/src/pretix/base/notifications.py @@ -191,7 +191,7 @@ class ParametrizedOrderNotificationType(NotificationType): n.add_attribute(_('Net total'), money_filter(sum([p.net_price for p in positions] + [f.net_value for f in fees]), logentry.event.currency)) n.add_attribute(_('Order total'), money_filter(order.total, logentry.event.currency)) n.add_attribute(_('Pending amount'), money_filter(order.pending_sum, logentry.event.currency)) - n.add_attribute(_('Order date'), date_format(order.datetime, 'SHORT_DATETIME_FORMAT')) + n.add_attribute(_('Order date'), date_format(order.datetime.astimezone(logentry.event.timezone), 'SHORT_DATETIME_FORMAT')) n.add_attribute(_('Order status'), order.get_status_display()) n.add_attribute(_('Order positions'), str(order.positions.count())) diff --git a/src/pretix/base/services/checkin.py b/src/pretix/base/services/checkin.py index 2b1541bab8..a50fd4bcd0 100644 --- a/src/pretix/base/services/checkin.py +++ b/src/pretix/base/services/checkin.py @@ -1,9 +1,15 @@ from datetime import timedelta +from functools import partial, reduce import dateutil +import dateutil.parser from django.core.files import File from django.db import transaction -from django.db.models.functions import TruncDate +from django.db.models import ( + BooleanField, Count, ExpressionWrapper, F, IntegerField, OuterRef, Q, + Subquery, Value, +) +from django.db.models.functions import Coalesce, TruncDate from django.dispatch import receiver from django.utils.functional import cached_property from django.utils.timezone import now, override @@ -15,9 +21,18 @@ from pretix.base.models import ( ) from pretix.base.signals import checkin_created, order_placed, periodic_task from pretix.helpers.jsonlogic import Logic +from pretix.helpers.jsonlogic_query import ( + Equal, GreaterEqualThan, GreaterThan, InList, LowerEqualThan, LowerThan, + tolerance, +) def get_logic_environment(ev): + # Every change to our supported JSON logic must be done + # * in pretix.base.services.checkin + # * in pretix.base.models.checkin + # * in checkinrules.js + # * in libpretixsync def build_time(t=None, value=None): if t == "custom": return dateutil.parser.parse(value) @@ -82,10 +97,181 @@ class LazyRuleVars: tz = self._clist.event.timezone with override(tz): return self._position.checkins.filter(list=self._clist, type=Checkin.TYPE_ENTRY).annotate( - day=TruncDate('datetime') + day=TruncDate('datetime', tzinfo=tz) ).values('day').distinct().count() +class SQLLogic: + """ + This is a simplified implementation of JSON logic that creates a Q-object to be used in a QuerySet. + It does not implement all operations supported by JSON logic and makes a few simplifying assumptions, + but all that can be created through our graphical editor. There's also CheckinList.validate_rules() + which tries to validate the same preconditions for rules set throught he API (probably not perfect). + + Assumptions: + + * Only a limited set of operators is used + * The top level operator is always a boolean operation (and, or) or a comparison operation (==, !=, …) + * Expression operators (var, lookup, buildTime) do not require further recursion + * Comparison operators (==, !=, …) never contain boolean operators (and, or) further down in the stack + """ + + def __init__(self, list): + self.list = list + self.bool_ops = { + "and": lambda *args: reduce(lambda total, arg: total & arg, args), + "or": lambda *args: reduce(lambda total, arg: total | arg, args), + } + self.comparison_ops = { + "==": partial(self.comparison_to_q, operator=Equal), + "!=": partial(self.comparison_to_q, operator=Equal, negate=True), + ">": partial(self.comparison_to_q, operator=GreaterThan), + ">=": partial(self.comparison_to_q, operator=GreaterEqualThan), + "<": partial(self.comparison_to_q, operator=LowerThan), + "<=": partial(self.comparison_to_q, operator=LowerEqualThan), + "inList": partial(self.comparison_to_q, operator=InList), + "isBefore": partial(self.comparison_to_q, operator=LowerThan, modifier=partial(tolerance, sign=1)), + "isAfter": partial(self.comparison_to_q, operator=GreaterThan, modifier=partial(tolerance, sign=-1)), + } + self.expression_ops = {'buildTime', 'objectList', 'lookup', 'var'} + + def operation_to_expression(self, rule): + if not isinstance(rule, dict): + return rule + + operator = list(rule.keys())[0] + values = rule[operator] + + if not isinstance(values, list) and not isinstance(values, tuple): + values = [values] + + if operator == 'buildTime': + if values[0] == "custom": + return Value(dateutil.parser.parse(values[1])) + elif values[0] == 'date_from': + return Coalesce( + F(f'subevent__date_from'), + F(f'order__event__date_from'), + ) + elif values[0] == 'date_to': + return Coalesce( + F(f'subevent__date_to'), + F(f'subevent__date_from'), + F(f'order__event__date_to'), + F(f'order__event__date_from'), + ) + elif values[0] == 'date_admission': + return Coalesce( + F(f'subevent__date_admission'), + F(f'subevent__date_from'), + F(f'order__event__date_admission'), + F(f'order__event__date_from'), + ) + else: + raise ValueError(f'Unknown time type {values[0]}') + elif operator == 'objectList': + return [self.operation_to_expression(v) for v in values] + elif operator == 'lookup': + return int(values[1]) + elif operator == 'var': + if values[0] == 'now': + return Value(now()) + elif values[0] == 'product': + return F('item_id') + elif values[0] == 'variation': + return F('variation_id') + elif values[0] == 'entries_number': + return Coalesce( + Subquery( + Checkin.objects.filter( + position_id=OuterRef('pk'), + type=Checkin.TYPE_ENTRY, + list_id=self.list.pk + ).values('position_id').order_by().annotate( + c=Count('*') + ).values('c') + ), + Value(0), + output_field=IntegerField() + ) + elif values[0] == 'entries_today': + midnight = now().astimezone(self.list.event.timezone).replace(hour=0, minute=0, second=0, microsecond=0) + return Coalesce( + Subquery( + Checkin.objects.filter( + position_id=OuterRef('pk'), + type=Checkin.TYPE_ENTRY, + list_id=self.list.pk, + datetime__gte=midnight, + ).values('position_id').order_by().annotate( + c=Count('*') + ).values('c') + ), + Value(0), + output_field=IntegerField() + ) + elif values[0] == 'entries_days': + tz = self.list.event.timezone + return Coalesce( + Subquery( + Checkin.objects.filter( + position_id=OuterRef('pk'), + type=Checkin.TYPE_ENTRY, + list_id=self.list.pk, + ).annotate( + day=TruncDate('datetime', tzinfo=tz) + ).values('position_id').order_by().annotate( + c=Count('day', distinct=True) + ).values('c') + ), + Value(0), + output_field=IntegerField() + ) + else: + raise ValueError(f'Unknown operator {operator}') + + def comparison_to_q(self, a, b, *args, operator, negate=False, modifier=None): + a = self.operation_to_expression(a) + b = self.operation_to_expression(b) + if modifier: + b = modifier(b, *args) + q = Q( + ExpressionWrapper( + operator( + a, + b, + ), + output_field=BooleanField() + ) + ) + return ~q if negate else q + + def apply(self, tests): + """ + Convert JSON logic to queryset info, returns an Q object and fills self.annotations + """ + if not tests: + return Q() + if isinstance(tests, bool): + # not really a legal configuration but used in the test suite + return Value(tests, output_field=BooleanField()) + + operator = list(tests.keys())[0] + values = tests[operator] + + # Easy syntax for unary operators, like {"var": "x"} instead of strict + # {"var": ["x"]} + if not isinstance(values, list) and not isinstance(values, tuple): + values = [values] + + if operator in self.bool_ops: + return self.bool_ops[operator](*[self.apply(v) for v in values]) + elif operator in self.comparison_ops: + return self.comparison_ops[operator](*values) + else: + raise ValueError(f'Invalid operator {operator} on first level') + + class CheckInError(Exception): def __init__(self, msg, code): self.msg = msg @@ -207,7 +393,7 @@ def perform_checkin(op: OrderPosition, clist: CheckinList, given_answers: dict, 'product' ) elif op.order.status != Order.STATUS_PAID and not force and not ( - ignore_unpaid and clist.include_pending and op.order.status == Order.STATUS_PENDING + ignore_unpaid and clist.include_pending and op.order.status == Order.STATUS_PENDING ): raise CheckInError( _('This order is not marked as paid.'), diff --git a/src/pretix/control/context.py b/src/pretix/control/context.py index 1fc689e2e7..ee06a97dcd 100644 --- a/src/pretix/control/context.py +++ b/src/pretix/control/context.py @@ -66,7 +66,9 @@ def _default_context(request): if complain_testmode_orders is None: complain_testmode_orders = request.event.orders.filter(testmode=True).exists() request.event.cache.set('complain_testmode_orders', complain_testmode_orders, 30) - ctx['complain_testmode_orders'] = complain_testmode_orders + ctx['complain_testmode_orders'] = complain_testmode_orders and request.user.has_event_permission( + request.organizer, request.event, 'can_view_orders', request=request + ) else: ctx['complain_testmode_orders'] = False diff --git a/src/pretix/control/forms/checkin.py b/src/pretix/control/forms/checkin.py index 5e8bd71cca..04afa04421 100644 --- a/src/pretix/control/forms/checkin.py +++ b/src/pretix/control/forms/checkin.py @@ -105,6 +105,11 @@ class CheckinListForm(forms.ModelForm): 'exit_all_at': NextTimeField, } + def clean(self): + d = super().clean() + CheckinList.validate_rules(d.get('rules')) + return d + class SimpleCheckinListForm(forms.ModelForm): def __init__(self, **kwargs): diff --git a/src/pretix/control/forms/event.py b/src/pretix/control/forms/event.py index 4fc1a2a17e..cf6eb21dc4 100644 --- a/src/pretix/control/forms/event.py +++ b/src/pretix/control/forms/event.py @@ -1134,6 +1134,13 @@ class TicketSettingsForm(SettingsForm): class CommentForm(I18nModelForm): + + def __init__(self, *args, **kwargs): + self.readonly = kwargs.pop('readonly') + super().__init__(*args, **kwargs) + if self.readonly: + self.fields['comment'].widget.attrs['readonly'] = 'readonly' + class Meta: model = Event fields = ['comment'] diff --git a/src/pretix/control/forms/organizer.py b/src/pretix/control/forms/organizer.py index e006cdd021..a69a34f6fb 100644 --- a/src/pretix/control/forms/organizer.py +++ b/src/pretix/control/forms/organizer.py @@ -149,7 +149,7 @@ class TeamForm(forms.ModelForm): 'can_change_teams', 'can_change_organizer_settings', 'can_manage_gift_cards', 'can_change_event_settings', 'can_change_items', - 'can_view_orders', 'can_change_orders', + 'can_view_orders', 'can_change_orders', 'can_checkin_orders', 'can_view_vouchers', 'can_change_vouchers'] widgets = { 'limit_events': forms.CheckboxSelectMultiple(attrs={ diff --git a/src/pretix/control/navigation.py b/src/pretix/control/navigation.py index d5ed5cd1c5..4c7090b968 100644 --- a/src/pretix/control/navigation.py +++ b/src/pretix/control/navigation.py @@ -514,5 +514,5 @@ def merge_in(nav, newnav): if 'children' not in parents[0]: parents[0]['children'] = [] parents[0]['children'].append(item) - else: - nav.append(item) + continue + nav.append(item) diff --git a/src/pretix/control/templates/pretixcontrol/base.html b/src/pretix/control/templates/pretixcontrol/base.html index 6ad510dee7..dc6c2d81ab 100644 --- a/src/pretix/control/templates/pretixcontrol/base.html +++ b/src/pretix/control/templates/pretixcontrol/base.html @@ -283,6 +283,7 @@ {% for nav in nav_items %}
  • {% if nav.icon %} {% if " {{ item.label }} diff --git a/src/pretix/control/templates/pretixcontrol/event/index.html b/src/pretix/control/templates/pretixcontrol/event/index.html index 45ef51318b..a44de4bd38 100644 --- a/src/pretix/control/templates/pretixcontrol/event/index.html +++ b/src/pretix/control/templates/pretixcontrol/event/index.html @@ -150,12 +150,14 @@
    {% bootstrap_field comment_form.comment layout="horizontal" show_help=True show_label=False horizontal_field_class="col-md-12" %}
    -

    -
    - -

    + {% if not comment_form.readonly %} +

    +
    + +

    + {% endif %} diff --git a/src/pretix/control/templates/pretixcontrol/organizers/team_edit.html b/src/pretix/control/templates/pretixcontrol/organizers/team_edit.html index 519e58ec4b..bd6d25c4b5 100644 --- a/src/pretix/control/templates/pretixcontrol/organizers/team_edit.html +++ b/src/pretix/control/templates/pretixcontrol/organizers/team_edit.html @@ -35,6 +35,7 @@ {% bootstrap_field form.can_change_items layout="control" %} {% bootstrap_field form.can_view_orders layout="control" %} {% bootstrap_field form.can_change_orders layout="control" %} + {% bootstrap_field form.can_checkin_orders layout="control" %} {% bootstrap_field form.can_view_vouchers layout="control" %} {% bootstrap_field form.can_change_vouchers layout="control" %} diff --git a/src/pretix/control/views/dashboards.py b/src/pretix/control/views/dashboards.py index 34f7a39427..9d7c3523f5 100644 --- a/src/pretix/control/views/dashboards.py +++ b/src/pretix/control/views/dashboards.py @@ -22,8 +22,8 @@ from django.utils.translation import gettext_lazy as _, pgettext, ungettext from pretix.base.decimal import round_decimal from pretix.base.models import ( - Item, ItemVariation, Order, OrderPosition, OrderRefund, RequiredAction, - SubEvent, Voucher, WaitingListEntry, + Item, ItemCategory, ItemVariation, Order, OrderPosition, OrderRefund, + Question, Quota, RequiredAction, SubEvent, Voucher, WaitingListEntry, ) from pretix.base.services.quotas import QuotaAvailability from pretix.base.timeline import timeline_for_event @@ -313,19 +313,40 @@ def event_index(request, organizer, event): except SubEvent.DoesNotExist: pass - widgets = [] - for r, result in event_dashboard_widgets.send(sender=request.event, subevent=subevent, lazy=True): - widgets.extend(result) - + can_view_orders = request.user.has_event_permission(request.organizer, request.event, 'can_view_orders', + request=request) can_change_orders = request.user.has_event_permission(request.organizer, request.event, 'can_change_orders', request=request) + can_change_event_settings = request.user.has_event_permission(request.organizer, request.event, + 'can_change_event_settings', request=request) + can_view_vouchers = request.user.has_event_permission(request.organizer, request.event, 'can_view_vouchers', + request=request) + + widgets = [] + if can_view_orders: + for r, result in event_dashboard_widgets.send(sender=request.event, subevent=subevent, lazy=True): + widgets.extend(result) + qs = request.event.logentry_set.all().select_related('user', 'content_type', 'api_token', 'oauth_application', 'device').order_by('-datetime') qs = qs.exclude(action_type__in=OVERVIEW_BANLIST) - if not request.user.has_event_permission(request.organizer, request.event, 'can_view_orders', request=request): + if not can_view_orders: qs = qs.exclude(content_type=ContentType.objects.get_for_model(Order)) - if not request.user.has_event_permission(request.organizer, request.event, 'can_view_vouchers', request=request): + if not can_view_vouchers: qs = qs.exclude(content_type=ContentType.objects.get_for_model(Voucher)) + if not can_change_event_settings: + allowed_types = [ + ContentType.objects.get_for_model(Voucher), + ContentType.objects.get_for_model(Order) + ] + if request.user.has_event_permission(request.organizer, request.event, 'can_change_items', request=request): + allowed_types += [ + ContentType.objects.get_for_model(Item), + ContentType.objects.get_for_model(ItemCategory), + ContentType.objects.get_for_model(Quota), + ContentType.objects.get_for_model(Question), + ] + qs = qs.filter(content_type__in=allowed_types) a_qs = request.event.requiredaction_set.filter(done=False) @@ -334,25 +355,25 @@ def event_index(request, organizer, event): 'logs': qs[:5], 'subevent': subevent, 'actions': a_qs[:5] if can_change_orders else [], - 'comment_form': CommentForm(initial={'comment': request.event.comment}) + 'comment_form': CommentForm(initial={'comment': request.event.comment}, readonly=not can_change_event_settings), } - ctx['has_overpaid_orders'] = Order.annotate_overpayments(request.event.orders).filter( + ctx['has_overpaid_orders'] = can_view_orders and Order.annotate_overpayments(request.event.orders).filter( Q(~Q(status=Order.STATUS_CANCELED) & Q(pending_sum_t__lt=0)) | Q(Q(status=Order.STATUS_CANCELED) & Q(pending_sum_rc__lt=0)) ).exists() - ctx['has_pending_orders_with_full_payment'] = Order.annotate_overpayments(request.event.orders).filter( + ctx['has_pending_orders_with_full_payment'] = can_view_orders and Order.annotate_overpayments(request.event.orders).filter( Q(status__in=(Order.STATUS_EXPIRED, Order.STATUS_PENDING)) & Q(pending_sum_t__lte=0) & Q(require_approval=False) ).exists() - ctx['has_pending_refunds'] = OrderRefund.objects.filter( + ctx['has_pending_refunds'] = can_view_orders and OrderRefund.objects.filter( order__event=request.event, state__in=(OrderRefund.REFUND_STATE_CREATED, OrderRefund.REFUND_STATE_EXTERNAL) ).exists() - ctx['has_pending_approvals'] = request.event.orders.filter( + ctx['has_pending_approvals'] = can_view_orders and request.event.orders.filter( status=Order.STATUS_PENDING, require_approval=True ).exists() - ctx['has_cancellation_requests'] = CancellationRequest.objects.filter( + ctx['has_cancellation_requests'] = can_view_orders and CancellationRequest.objects.filter( order__event=request.event ).exists() diff --git a/src/pretix/control/views/event.py b/src/pretix/control/views/event.py index 5ad08ac5e9..a4eba10f27 100644 --- a/src/pretix/control/views/event.py +++ b/src/pretix/control/views/event.py @@ -55,7 +55,9 @@ from pretix.plugins.stripe.payment import StripeSettingsHolder from pretix.presale.style import regenerate_css from ...base.i18n import language -from ...base.models.items import ItemMetaProperty +from ...base.models.items import ( + Item, ItemCategory, ItemMetaProperty, Question, Quota, +) from ...base.settings import SETTINGS_AFFECTING_CSS, LazyI18nStringList from ..logdisplay import OVERVIEW_BANLIST from . import CreateView, PaginationMixin, UpdateView @@ -971,6 +973,21 @@ class EventLog(EventPermissionRequiredMixin, PaginationMixin, ListView): if not self.request.user.has_event_permission(self.request.organizer, self.request.event, 'can_view_vouchers', request=self.request): qs = qs.exclude(content_type=ContentType.objects.get_for_model(Voucher)) + if not self.request.user.has_event_permission(self.request.organizer, self.request.event, + 'can_change_event_settings', request=self.request): + allowed_types = [ + ContentType.objects.get_for_model(Voucher), + ContentType.objects.get_for_model(Order) + ] + if self.request.user.has_event_permission(self.request.organizer, self.request.event, + 'can_change_items', request=self.request): + allowed_types += [ + ContentType.objects.get_for_model(Item), + ContentType.objects.get_for_model(ItemCategory), + ContentType.objects.get_for_model(Quota), + ContentType.objects.get_for_model(Question), + ] + qs = qs.filter(content_type__in=allowed_types) if self.request.GET.get('user') == 'yes': qs = qs.filter(user__isnull=False) diff --git a/src/pretix/helpers/compressor.py b/src/pretix/helpers/compressor.py new file mode 100644 index 0000000000..0f715202b3 --- /dev/null +++ b/src/pretix/helpers/compressor.py @@ -0,0 +1,42 @@ +import os +import re +import shlex + +from compressor.exceptions import FilterError +from compressor.filters import CompilerFilter +from django.conf import settings + + +class VueCompiler(CompilerFilter): + # Based on work (c) Laura Klünder in https://github.com/codingcatgirl/django-vue-rollup + # Released under Apache License 2.0 + + def __init__(self, content, attrs, **kwargs): + config_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'static', 'npm_dir') + node_path = os.path.join(settings.STATIC_ROOT, 'node_prefix', 'node_modules') + self.rollup_bin = os.path.join(node_path, 'rollup', 'dist', 'bin', 'rollup') + rollup_config = os.path.join(config_dir, 'rollup.config.js') + if not os.path.exists(self.rollup_bin) and not settings.DEBUG: + raise FilterError("Rollup not installed or pretix not built properly, please run 'make npminstall' in source root.") + command = ( + ' '.join(( + 'NODE_PATH=' + shlex.quote(node_path), + shlex.quote(self.rollup_bin), + '-c', + shlex.quote(rollup_config)) + ) + + ' --input {infile} -n {export_name} --file {outfile}' + ) + super().__init__(content, command=command, **kwargs) + + def input(self, **kwargs): + if self.filename is None: + raise FilterError('VueCompiler can only compile files, not inline code.') + if not os.path.exists(self.rollup_bin): + raise FilterError("Rollup not installed, please run 'make npminstall' in source root.") + self.options += (('export_name', re.sub( + r'^([a-z])|[^a-z0-9A-Z]+([a-zA-Z0-9])?', + lambda s: s.group(0)[-1].upper(), + os.path.basename(self.filename).split('.')[0] + )),) + return super().input(**kwargs) diff --git a/src/pretix/helpers/jsonlogic_query.py b/src/pretix/helpers/jsonlogic_query.py new file mode 100644 index 0000000000..7008e5c026 --- /dev/null +++ b/src/pretix/helpers/jsonlogic_query.py @@ -0,0 +1,59 @@ +import logging +from datetime import timedelta + +from django.db.models import Func, Value + +logger = logging.getLogger(__name__) + + +class Equal(Func): + arg_joiner = ' = ' + arity = 2 + function = '' + + +class GreaterThan(Func): + arg_joiner = ' > ' + arity = 2 + function = '' + + +class GreaterEqualThan(Func): + arg_joiner = ' >= ' + arity = 2 + function = '' + + +class LowerEqualThan(Func): + arg_joiner = ' < ' + arity = 2 + function = '' + + +class LowerThan(Func): + arg_joiner = ' < ' + arity = 2 + function = '' + + +class InList(Func): + arity = 2 + + def as_sql(self, compiler, connection, function=None, template=None, arg_joiner=None, **extra_context): + connection.ops.check_expression_support(self) + + # This ignores the special case for databases which limit the number of + # elements which can appear in an 'IN' clause, which hopefully is only Oracle. + lhs, lhs_params = compiler.compile(self.source_expressions[0]) + + if not isinstance(self.source_expressions[1], Value) and not isinstance(self.source_expressions[1].value, (list, tuple)): + raise TypeError(f'Dynamic right-hand-site currently not implemented, found {type(self.source_expressions[1])}') + rhs, rhs_params = ['%s' for _ in self.source_expressions[1].value], [d for d in self.source_expressions[1].value] + + return '%s IN (%s)' % (lhs, ', '.join(rhs)), lhs_params + rhs_params + + +def tolerance(b, tol=None, sign=1): + if tol: + return b + timedelta(minutes=sign * float(tol)) + return b diff --git a/src/pretix/plugins/webcheckin/__init__.py b/src/pretix/plugins/webcheckin/__init__.py new file mode 100644 index 0000000000..34667d517b --- /dev/null +++ b/src/pretix/plugins/webcheckin/__init__.py @@ -0,0 +1,22 @@ +from django.apps import AppConfig +from django.utils.translation import gettext_lazy as _ + +from pretix import __version__ as version + + +class WebCheckinApp(AppConfig): + name = 'pretix.plugins.webcheckin' + verbose_name = _("Web-based check-in") + + class PretixPluginMeta: + name = _("Web-based check-in") + author = _("the pretix team") + version = version + category = "FEATURE" + description = _("This plugin allows you to perform check-in actions in your browser.") + + def ready(self): + from . import signals # NOQA + + +default_app_config = 'pretix.plugins.webcheckin.WebCheckinApp' diff --git a/src/pretix/plugins/webcheckin/signals.py b/src/pretix/plugins/webcheckin/signals.py new file mode 100644 index 0000000000..35bc2bb3d3 --- /dev/null +++ b/src/pretix/plugins/webcheckin/signals.py @@ -0,0 +1,27 @@ +from django.dispatch import receiver +from django.urls import reverse +from django.utils.safestring import mark_safe +from django.utils.translation import gettext_lazy as _ + +from pretix.control.signals import nav_event + + +@receiver(nav_event, dispatch_uid='webcheckin_nav_event') +def navbar_entry(sender, request, **kwargs): + url = request.resolver_match + if not request.user.has_event_permission(request.organizer, request.event, ('can_change_orders', 'can_checkin_orders'), request=request): + return [] + return [{ + 'label': mark_safe(_('Web Check-in') + ' beta'), + 'url': reverse('plugins:webcheckin:index', kwargs={ + 'event': request.event.slug, + 'organizer': request.organizer.slug, + }), + 'parent': reverse('control:event.orders.checkinlists', kwargs={ + 'event': request.event.slug, + 'organizer': request.event.organizer.slug, + }), + 'external': True, + 'icon': 'check-square-o', + 'active': url.namespace == 'plugins:webcheckin' and url.url_name.startswith('index'), + }] diff --git a/src/pretix/plugins/webcheckin/static/pretixplugins/webcheckin/components/app.vue b/src/pretix/plugins/webcheckin/static/pretixplugins/webcheckin/components/app.vue new file mode 100644 index 0000000000..93a1c17d99 --- /dev/null +++ b/src/pretix/plugins/webcheckin/static/pretixplugins/webcheckin/components/app.vue @@ -0,0 +1,517 @@ + + diff --git a/src/pretix/plugins/webcheckin/static/pretixplugins/webcheckin/components/checkinlist-item.vue b/src/pretix/plugins/webcheckin/static/pretixplugins/webcheckin/components/checkinlist-item.vue new file mode 100644 index 0000000000..ac408d3ad1 --- /dev/null +++ b/src/pretix/plugins/webcheckin/static/pretixplugins/webcheckin/components/checkinlist-item.vue @@ -0,0 +1,28 @@ + + diff --git a/src/pretix/plugins/webcheckin/static/pretixplugins/webcheckin/components/checkinlist-select.vue b/src/pretix/plugins/webcheckin/static/pretixplugins/webcheckin/components/checkinlist-select.vue new file mode 100644 index 0000000000..7f7315a356 --- /dev/null +++ b/src/pretix/plugins/webcheckin/static/pretixplugins/webcheckin/components/checkinlist-select.vue @@ -0,0 +1,101 @@ + + diff --git a/src/pretix/plugins/webcheckin/static/pretixplugins/webcheckin/components/datefield.vue b/src/pretix/plugins/webcheckin/static/pretixplugins/webcheckin/components/datefield.vue new file mode 100644 index 0000000000..260e906793 --- /dev/null +++ b/src/pretix/plugins/webcheckin/static/pretixplugins/webcheckin/components/datefield.vue @@ -0,0 +1,54 @@ + + \ No newline at end of file diff --git a/src/pretix/plugins/webcheckin/static/pretixplugins/webcheckin/components/datetimefield.vue b/src/pretix/plugins/webcheckin/static/pretixplugins/webcheckin/components/datetimefield.vue new file mode 100644 index 0000000000..5db993a86c --- /dev/null +++ b/src/pretix/plugins/webcheckin/static/pretixplugins/webcheckin/components/datetimefield.vue @@ -0,0 +1,55 @@ + + \ No newline at end of file diff --git a/src/pretix/plugins/webcheckin/static/pretixplugins/webcheckin/components/searchresult-item.vue b/src/pretix/plugins/webcheckin/static/pretixplugins/webcheckin/components/searchresult-item.vue new file mode 100644 index 0000000000..d05ad6f55c --- /dev/null +++ b/src/pretix/plugins/webcheckin/static/pretixplugins/webcheckin/components/searchresult-item.vue @@ -0,0 +1,46 @@ + + diff --git a/src/pretix/plugins/webcheckin/static/pretixplugins/webcheckin/components/timefield.vue b/src/pretix/plugins/webcheckin/static/pretixplugins/webcheckin/components/timefield.vue new file mode 100644 index 0000000000..f6846ace5e --- /dev/null +++ b/src/pretix/plugins/webcheckin/static/pretixplugins/webcheckin/components/timefield.vue @@ -0,0 +1,54 @@ + + \ No newline at end of file diff --git a/src/pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js b/src/pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js new file mode 100644 index 0000000000..9bf805b7d1 --- /dev/null +++ b/src/pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js @@ -0,0 +1,71 @@ +/*global gettext, Vue, App*/ +function gettext(msgid) { + if (typeof django !== 'undefined' && typeof django.gettext !== 'undefined') { + return django.gettext(msgid); + } + return msgid; +} + +function ngettext(singular, plural, count) { + if (typeof django !== 'undefined' && typeof django.ngettext !== 'undefined') { + return django.ngettext(singular, plural, count); + } + return plural; +} + + +moment.locale(document.body.attributes['data-datetimelocale'].value) +window.vapp = new Vue({ + components: { + App: App.default + }, + render: function (h) { + return h('App') + }, + data: { + api: { + lists: document.querySelector('#app').attributes['data-api-lists'].value, + }, + strings: { + 'checkinlist.select': gettext('Select a check-in list'), + 'checkinlist.none': gettext('No active check-in lists found.'), + 'checkinlist.switch': gettext('Switch check-in list'), + 'results.headline': gettext('Search results'), + 'results.none': gettext('No tickets found'), + 'check.headline': gettext('Check-in result'), + 'check.attention': gettext('This ticket requires special attention'), + 'scantype.switch': gettext('Switch direction'), + 'scantype.entry': gettext('Entry'), + 'scantype.exit': gettext('Exit'), + 'input.placeholder': gettext('Scan a ticket or search and press return…'), + 'pagination.next': gettext('Load more'), + 'status.p': gettext('Valid'), + 'status.n': gettext('Unpaid'), + 'status.c': gettext('Canceled'), + 'status.e': gettext('Canceled'), + 'status.redeemed': gettext('Redeemed'), + 'modal.cancel': gettext('Cancel'), + 'modal.continue': gettext('Continue'), + 'modal.unpaid.head': gettext('Ticket not paid'), + 'modal.unpaid.text': gettext('This ticket is not yet paid. Do you want to continue anyways?'), + 'modal.questions': gettext('Additional information required'), + 'result.ok': gettext('Valid ticket'), + 'result.exit': gettext('Exit recorded'), + 'result.already_redeemed': gettext('Ticket already used'), + 'result.questions': gettext('Information required'), + 'result.invalid': gettext('Invalid ticket'), + 'result.product': gettext('Invalid product'), + 'result.unpaid': gettext('Ticket not paid'), + 'result.rules': gettext('Entry not allowed'), + 'result.revoked': gettext('Ticket code revoked/changed'), + 'result.canceled': gettext('Order canceled'), + 'status.checkin': gettext('Checked-in Tickets'), + 'status.position': gettext('Valid Tickets'), + 'status.inside': gettext('Currently inside'), + }, + event_name: document.querySelector('#app').attributes['data-event-name'].value, + timezone: document.body.attributes['data-timezone'].value, + datetime_format: document.body.attributes['data-datetimeformat'].value, + }, + el: '#app' +}) diff --git a/src/pretix/plugins/webcheckin/static/pretixplugins/webcheckin/scss/main.scss b/src/pretix/plugins/webcheckin/static/pretixplugins/webcheckin/scss/main.scss new file mode 100644 index 0000000000..2c5c1bb1eb --- /dev/null +++ b/src/pretix/plugins/webcheckin/static/pretixplugins/webcheckin/scss/main.scss @@ -0,0 +1,153 @@ +@import "pretixbase/scss/_variables.scss"; +@import "bootstrap/scss/_bootstrap.scss"; +@import "pretixbase/scss/_theme.scss"; +@import "fontawesome/scss/font-awesome.scss"; +@import "datetimepicker/_bootstrap-datetimepicker.scss"; + +body { + background: #FBF7FC; +} + +.container { + padding-top: 20px; + padding-bottom: 20px; +} + +.loading-icon { + color: $brand-primary; + -webkit-animation: fa-spin 8s infinite linear; + animation: fa-spin 8s infinite linear; +} + +.meta { + padding: 20px; + text-align: center; + .settings { + font-size: 32px; + } + color: $text-muted; + .fa-sign-out { + color: $brand-warning; + } + + .status { + padding-top: 20px; + color: $text-muted; + .statistic { + display: block; + font-size: 30px; + } + } +} + +.scan-input { + margin-bottom: 20px; +} + +a.searchresult { + display: flex; + flex-direction: row; + min-height: 96px; + padding: 0; + color: $text-color; + + &:focus { + background: $gray-lightest; + } + + h4 { + margin: 0 0 5px; + } + + .details { + flex: auto 1 1; + padding: 10px; + } + + .status { + flex: 128px 0 0; + font-weight: bold; + color: white; + text-align: center; + display: flex; + justify-content: center; + align-items: center; + font-size: 140%; + + &.status-p { + background: $brand-success; + } + &.status-c, &.status-e, &.status-n { + background: $brand-danger; + } + &.status-redeemed { + background: $brand-warning; + } + } + + .secret { + word-break: break-word; + color: $text-muted; + } +} + +.check-result-status { + height: 30vh; + max-height: 200px; + font-size: 35px; + text-transform: uppercase; + font-weight: bold; + text-align: center; + display: flex; + align-items: center; + justify-content: center; + color: white; + + &.check-result-red { + background: $brand-danger; + } + &.check-result-green { + background: $brand-success; + } + &.check-result-orange { + background: $brand-warning; + } + &.check-result-purple { + background: $brand-primary; + } +} + +.attention { + padding: 10px; + text-align: center; + animation: blinking 1s infinite; + font-weight: bold; + font-size: 16px; +} + +.modal { + background: rgba(0, 0, 0, 0.7); +} +.modal.fade.in { + display: block; + overflow: auto; +} + +@-webkit-keyframes blinking { + 0%, 49% { + background-color: $brand-primary; + color: white; + } + 50%, 100% { + background-color: $brand-warning; + color: $brand-primary; + } +} + +.panel-primary .panel-heading a { + color: white; +} + +.modal-footer .btn-primary.pull-right { + margin-left: 10px; +} \ No newline at end of file diff --git a/src/pretix/plugins/webcheckin/templates/pretixplugins/webcheckin/index.html b/src/pretix/plugins/webcheckin/templates/pretixplugins/webcheckin/index.html new file mode 100644 index 0000000000..3d23eb4d80 --- /dev/null +++ b/src/pretix/plugins/webcheckin/templates/pretixplugins/webcheckin/index.html @@ -0,0 +1,51 @@ +{% load compress %} +{% load static %} +{% load i18n %} +{% load hijack_tags %} +{% load statici18n %} +{% load eventurl %} +{% load escapejson %} + + + + {{ request.event.name }} :: {% trans "Check-in" %} :: {{ settings.PRETIX_INSTANCE_NAME }} + {% compress css %} + + {% endcompress %} + {% if DEBUG %} + + {% else %} + + {% endif %} + {{ html_head|safe }} + + + + +
    +{# TODO: use vue.min.js #} +{% compress js %} + + + + + + + + + + + + + + +{% endcompress %} + +{% csrf_token %} + + diff --git a/src/pretix/plugins/webcheckin/urls.py b/src/pretix/plugins/webcheckin/urls.py new file mode 100644 index 0000000000..3d2d712005 --- /dev/null +++ b/src/pretix/plugins/webcheckin/urls.py @@ -0,0 +1,8 @@ +from django.conf.urls import url + +from .views import IndexView + +urlpatterns = [ + url(r'^control/event/(?P[^/]+)/(?P[^/]+)/webcheckin/$', + IndexView.as_view(), name='index'), +] diff --git a/src/pretix/plugins/webcheckin/views.py b/src/pretix/plugins/webcheckin/views.py new file mode 100644 index 0000000000..7c10dbfeaf --- /dev/null +++ b/src/pretix/plugins/webcheckin/views.py @@ -0,0 +1,20 @@ +from django.views.generic import TemplateView + +from pretix.control.permissions import EventPermissionRequiredMixin +from pretix.helpers.countries import CachedCountries + + +class IndexView(EventPermissionRequiredMixin, TemplateView): + permission = ('can_change_orders', 'can_checkin_orders') + template_name = 'pretixplugins/webcheckin/index.html' + + def get_context_data(self, **kwargs): + ctx = super().get_context_data(**kwargs) + ctx['countries'] = [ + { + 'key': key, + 'value': name + } + for key, name in CachedCountries() + ] + return ctx diff --git a/src/pretix/settings.py b/src/pretix/settings.py index d0e0123141..3213182bc0 100644 --- a/src/pretix/settings.py +++ b/src/pretix/settings.py @@ -297,6 +297,7 @@ INSTALLED_APPS = [ 'pretix.plugins.badges', 'pretix.plugins.manualpayment', 'pretix.plugins.returnurl', + 'pretix.plugins.webcheckin', 'django_markup', 'django_otp', 'django_otp.plugins.otp_totp', @@ -563,6 +564,7 @@ STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesSto COMPRESS_PRECOMPILERS = ( ('text/x-scss', 'django_libsass.SassCompiler'), + ('text/vue', 'pretix.helpers.compressor.VueCompiler'), ) COMPRESS_ENABLED = COMPRESS_OFFLINE = not debug_fallback diff --git a/src/pretix/static/npm_dir/package-lock.json b/src/pretix/static/npm_dir/package-lock.json new file mode 100644 index 0000000000..45e80ec59d --- /dev/null +++ b/src/pretix/static/npm_dir/package-lock.json @@ -0,0 +1,1673 @@ +{ + "name": "pretix", + "version": "0.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/helper-module-imports": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.12.13.tgz", + "integrity": "sha512-NGmfvRp9Rqxy0uHSSVP+SRIW1q31a7Ji10cLBcqSDUngGentY4FRiHOFZFE1CLU5eiL0oE8reH7Tg1y99TDM/g==", + "requires": { + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==" + }, + "@babel/types": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.13.tgz", + "integrity": "sha512-oKrdZTld2im1z8bDwTOQvUbxKwE+854zc16qWZQlcTqMN00pWxHQ4ZeOq0yDMnisOpRykH2/5Qqcrk/OlbAjiQ==", + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + }, + "dependencies": { + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" + } + } + }, + "@rollup/plugin-babel": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.0.tgz", + "integrity": "sha512-9uIC8HZOnVLrLHxayq/PTzw+uS25E14KPUBh5ktF+18Mjo5yK0ToMMx6epY0uEgkjwJw0aBW4x2horYXh8juWw==", + "requires": { + "@babel/helper-module-imports": "^7.10.4", + "@rollup/pluginutils": "^3.1.0" + } + }, + "@rollup/plugin-node-resolve": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.2.0.tgz", + "integrity": "sha512-qHjNIKYt5pCcn+5RUBQxK8krhRvf1HnyVgUCcFFcweDS7fhkOLZeYh0mhHK6Ery8/bb9tvN/ubPzmfF0qjDCTA==", + "requires": { + "@rollup/pluginutils": "^3.1.0", + "@types/resolve": "1.17.1", + "builtin-modules": "^3.1.0", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.19.0" + } + }, + "@rollup/pluginutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", + "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", + "requires": { + "@types/estree": "0.0.39", + "estree-walker": "^1.0.1", + "picomatch": "^2.2.2" + }, + "dependencies": { + "@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==" + }, + "estree-walker": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", + "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==" + } + } + }, + "@types/babel-types": { + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/@types/babel-types/-/babel-types-7.0.9.tgz", + "integrity": "sha512-qZLoYeXSTgQuK1h7QQS16hqLGdmqtRmN8w/rl3Au/l5x/zkHx+a4VHrHyBsi1I1vtK2oBHxSzKIu0R5p6spdOA==", + "optional": true + }, + "@types/babylon": { + "version": "6.16.5", + "resolved": "https://registry.npmjs.org/@types/babylon/-/babylon-6.16.5.tgz", + "integrity": "sha512-xH2e58elpj1X4ynnKp9qSnWlsRTIs6n3tgLGNfwAGHwePw0mulHQllV34n0T25uYSu1k0hRKkWXF890B1yS47w==", + "optional": true, + "requires": { + "@types/babel-types": "*" + } + }, + "@types/estree": { + "version": "0.0.46", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.46.tgz", + "integrity": "sha512-laIjwTQaD+5DukBZaygQ79K1Z0jb1bPEMRrkXSLjtCcZm+abyp5YbrqpSLzD42FwWW6gK/aS4NYpJ804nG2brg==" + }, + "@types/node": { + "version": "14.14.28", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.28.tgz", + "integrity": "sha512-lg55ArB+ZiHHbBBttLpzD07akz0QPrZgUODNakeC09i62dnrywr9mFErHuaPlB6I7z+sEbK+IYmplahvplCj2g==" + }, + "@types/resolve": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", + "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==", + "requires": { + "@types/node": "*" + } + }, + "@vue/component-compiler": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@vue/component-compiler/-/component-compiler-4.2.3.tgz", + "integrity": "sha512-B221AV3T/6PF37WnkoqUKIxBeHXmGuZsi/8pby89MAVSj9zmDdLCEZ7LDT8+DJWbElFrPELgnSvEadXxDRcrJQ==", + "requires": { + "@vue/component-compiler-utils": "^3.0.0", + "clean-css": "^4.1.11", + "hash-sum": "^1.0.2", + "less": "^3.9.0", + "postcss-modules-sync": "^1.0.0", + "pug": "^2.0.3", + "sass": "^1.18.0", + "source-map": "0.6.*", + "stylus": "^0.54.5" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "@vue/component-compiler-utils": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@vue/component-compiler-utils/-/component-compiler-utils-3.2.0.tgz", + "integrity": "sha512-lejBLa7xAMsfiZfNp7Kv51zOzifnb29FwdnMLa96z26kXErPFioSf9BMcePVIQ6/Gc6/mC0UrPpxAWIHyae0vw==", + "requires": { + "consolidate": "^0.15.1", + "hash-sum": "^1.0.2", + "lru-cache": "^4.1.2", + "merge-source-map": "^1.1.0", + "postcss": "^7.0.14", + "postcss-selector-parser": "^6.0.2", + "prettier": "^1.18.2", + "source-map": "~0.6.1", + "vue-template-es2015-compiler": "^1.9.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==" + }, + "acorn-globals": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-3.1.0.tgz", + "integrity": "sha1-/YJw9x+7SZawBPqIDuXUZXOnMb8=", + "optional": true, + "requires": { + "acorn": "^4.0.4" + }, + "dependencies": { + "acorn": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", + "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=", + "optional": true + } + } + }, + "align-text": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "optional": true, + "requires": { + "kind-of": "^3.0.2", + "longest": "^1.0.1", + "repeat-string": "^1.5.2" + } + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "optional": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", + "optional": true + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "optional": true + }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "optional": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "optional": true, + "requires": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + } + }, + "babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "optional": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "optional": true + }, + "big.js": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", + "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==" + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "optional": true + }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "optional": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "optional": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "builtin-modules": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz", + "integrity": "sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==" + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "optional": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "optional": true + }, + "center-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "optional": true, + "requires": { + "align-text": "^0.1.3", + "lazy-cache": "^1.0.3" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "character-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz", + "integrity": "sha1-x84o821LzZdE5f/CxfzeHHMmH8A=", + "optional": true, + "requires": { + "is-regex": "^1.0.3" + } + }, + "chokidar": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", + "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", + "optional": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.3.1", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.5.0" + } + }, + "clean-css": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz", + "integrity": "sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA==", + "requires": { + "source-map": "~0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "cliui": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "optional": true, + "requires": { + "center-align": "^0.1.1", + "right-align": "^0.1.1", + "wordwrap": "0.0.2" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "optional": true + }, + "consolidate": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/consolidate/-/consolidate-0.15.1.tgz", + "integrity": "sha512-DW46nrsMJgy9kqAbPt5rKaCr7uFtpo4mSUvLHIUbJEjm0vo+aY5QLwBUq3FK4tRnJr/X0Psc0C4jf/h+HtXSMw==", + "requires": { + "bluebird": "^3.1.1" + } + }, + "constantinople": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-3.1.2.tgz", + "integrity": "sha512-yePcBqEFhLOqSBtwYOGGS1exHo/s1xjekXiinh4itpNQGCu4KA1euPh1fg07N2wMITZXQkBz75Ntdt1ctGZouw==", + "optional": true, + "requires": { + "@types/babel-types": "^7.0.0", + "@types/babylon": "^6.16.2", + "babel-types": "^6.26.0", + "babylon": "^6.18.0" + } + }, + "copy-anything": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.3.tgz", + "integrity": "sha512-GK6QUtisv4fNS+XcI7shX0Gx9ORg7QqIznyfho79JTnX1XhLiyZHfftvGiziqzRiEi/Bjhgpi+D2o7HxJFPnDQ==", + "optional": true, + "requires": { + "is-what": "^3.12.0" + } + }, + "core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "optional": true + }, + "css": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz", + "integrity": "sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==", + "optional": true, + "requires": { + "inherits": "^2.0.3", + "source-map": "^0.6.1", + "source-map-resolve": "^0.5.2", + "urix": "^0.1.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true + } + } + }, + "css-parse": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/css-parse/-/css-parse-2.0.0.tgz", + "integrity": "sha1-pGjuZnwW2BzPBcWMONKpfHgNv9Q=", + "optional": true, + "requires": { + "css": "^2.0.0" + } + }, + "css-selector-tokenizer": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.3.tgz", + "integrity": "sha512-jWQv3oCEL5kMErj4wRnK/OPoBi0D+P1FR2cDCKYPaMeD2eW3/mttav8HT4hT1CKopiJI/psEULjkClhvJo4Lvg==", + "requires": { + "cssesc": "^3.0.0", + "fastparse": "^1.1.2" + } + }, + "cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==" + }, + "de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=" + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "requires": { + "ms": "2.1.2" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "optional": true + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "optional": true + }, + "deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==" + }, + "doctypes": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", + "integrity": "sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk=", + "optional": true + }, + "emojis-list": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", + "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=" + }, + "errno": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "optional": true, + "requires": { + "prr": "~1.0.1" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "estree-walker": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", + "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==" + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "optional": true + }, + "fastparse": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", + "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==" + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "optional": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "optional": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "generic-names": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/generic-names/-/generic-names-1.0.3.tgz", + "integrity": "sha1-LXhqEhruUIh2eWk56OO/+DbCCRc=", + "requires": { + "loader-utils": "^0.2.16" + } + }, + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "optional": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "optional": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "graceful-fs": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", + "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", + "optional": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "optional": true + }, + "hash-sum": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-1.0.2.tgz", + "integrity": "sha1-M7QHd3VMZDJXPBIMw4CLvRDUfwQ=" + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" + }, + "icss-replace-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz", + "integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=" + }, + "image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=", + "optional": true + }, + "indexes-of": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", + "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "optional": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "optional": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "optional": true + }, + "is-core-module": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", + "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", + "requires": { + "has": "^1.0.3" + } + }, + "is-expression": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-3.0.0.tgz", + "integrity": "sha1-Oayqa+f9HzRx3ELHQW5hwkMXrJ8=", + "optional": true, + "requires": { + "acorn": "~4.0.2", + "object-assign": "^4.0.1" + }, + "dependencies": { + "acorn": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", + "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=", + "optional": true + } + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "optional": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "optional": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=" + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "optional": true + }, + "is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", + "optional": true + }, + "is-regex": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz", + "integrity": "sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==", + "optional": true, + "requires": { + "call-bind": "^1.0.2", + "has-symbols": "^1.0.1" + } + }, + "is-what": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.12.0.tgz", + "integrity": "sha512-2ilQz5/f/o9V7WRWJQmpFYNmQFZ9iM+OXRonZKcYgTkCzjb949Vi4h282PD1UfmgHk666rcWonbRJ++KI41VGw==", + "optional": true + }, + "js-base64": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz", + "integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==" + }, + "js-stringify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", + "integrity": "sha1-Fzb939lyTyijaCrcYjCufk6Weds=", + "optional": true + }, + "json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=" + }, + "jstransformer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz", + "integrity": "sha1-7Yvwkh4vPx7U1cGkT2hwntJHIsM=", + "optional": true, + "requires": { + "is-promise": "^2.0.0", + "promise": "^7.0.1" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "optional": true, + "requires": { + "is-buffer": "^1.1.5" + } + }, + "lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", + "optional": true + }, + "less": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/less/-/less-3.13.1.tgz", + "integrity": "sha512-SwA1aQXGUvp+P5XdZslUOhhLnClSLIjWvJhmd+Vgib5BFIr9lMNlQwmwUNOjXThF/A0x+MCYYPeWEfeWiLRnTw==", + "optional": true, + "requires": { + "copy-anything": "^2.0.1", + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "make-dir": "^2.1.0", + "mime": "^1.4.1", + "native-request": "^1.0.5", + "source-map": "~0.6.0", + "tslib": "^1.10.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true + } + } + }, + "loader-utils": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", + "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", + "requires": { + "big.js": "^3.1.3", + "emojis-list": "^2.0.0", + "json5": "^0.5.0", + "object-assign": "^4.0.1" + } + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" + }, + "longest": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", + "optional": true + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "magic-string": { + "version": "0.25.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", + "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", + "requires": { + "sourcemap-codec": "^1.4.4" + } + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "optional": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "merge-source-map": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", + "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", + "requires": { + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "optional": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "optional": true + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "optional": true + }, + "native-request": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/native-request/-/native-request-1.0.8.tgz", + "integrity": "sha512-vU2JojJVelUGp6jRcLwToPoWGxSx23z/0iX+I77J3Ht17rf2INGjrhOoQnjVo60nQd8wVsgzKkPfRXBiVdD2ag==", + "optional": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "optional": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "optional": true, + "requires": { + "wrappy": "1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "optional": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" + }, + "picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==" + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "optional": true + }, + "postcss": { + "version": "7.0.35", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz", + "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "postcss-modules-local-by-default": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz", + "integrity": "sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk=", + "requires": { + "css-selector-tokenizer": "^0.7.0", + "postcss": "^6.0.1" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-modules-scope": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz", + "integrity": "sha1-1upkmUx5+XtipytCb75gVqGUu5A=", + "requires": { + "css-selector-tokenizer": "^0.7.0", + "postcss": "^6.0.1" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-modules-sync": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-sync/-/postcss-modules-sync-1.0.0.tgz", + "integrity": "sha1-YZpxnPeN0WpINBNRQLMkz3czS+E=", + "requires": { + "generic-names": "^1.0.2", + "icss-replace-symbols": "^1.0.2", + "postcss": "^5.2.5", + "postcss-modules-local-by-default": "^1.1.1", + "postcss-modules-scope": "^1.0.2", + "string-hash": "^1.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-selector-parser": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz", + "integrity": "sha512-gjMeXBempyInaBqpp8gODmwZ52WaYsVOsfr4L4lDQ7n3ncD6mEyySiDtgzCT+NYC0mmeOLvtsF8iaEf0YT6dBw==", + "requires": { + "cssesc": "^3.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1", + "util-deprecate": "^1.0.2" + } + }, + "prettier": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", + "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", + "optional": true + }, + "promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "optional": true, + "requires": { + "asap": "~2.0.3" + } + }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", + "optional": true + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" + }, + "pug": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pug/-/pug-2.0.4.tgz", + "integrity": "sha512-XhoaDlvi6NIzL49nu094R2NA6P37ijtgMDuWE+ofekDChvfKnzFal60bhSdiy8y2PBO6fmz3oMEIcfpBVRUdvw==", + "optional": true, + "requires": { + "pug-code-gen": "^2.0.2", + "pug-filters": "^3.1.1", + "pug-lexer": "^4.1.0", + "pug-linker": "^3.0.6", + "pug-load": "^2.0.12", + "pug-parser": "^5.0.1", + "pug-runtime": "^2.0.5", + "pug-strip-comments": "^1.0.4" + } + }, + "pug-attrs": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-2.0.4.tgz", + "integrity": "sha512-TaZ4Z2TWUPDJcV3wjU3RtUXMrd3kM4Wzjbe3EWnSsZPsJ3LDI0F3yCnf2/W7PPFF+edUFQ0HgDL1IoxSz5K8EQ==", + "optional": true, + "requires": { + "constantinople": "^3.0.1", + "js-stringify": "^1.0.1", + "pug-runtime": "^2.0.5" + } + }, + "pug-code-gen": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-2.0.2.tgz", + "integrity": "sha512-kROFWv/AHx/9CRgoGJeRSm+4mLWchbgpRzTEn8XCiwwOy6Vh0gAClS8Vh5TEJ9DBjaP8wCjS3J6HKsEsYdvaCw==", + "optional": true, + "requires": { + "constantinople": "^3.1.2", + "doctypes": "^1.1.0", + "js-stringify": "^1.0.1", + "pug-attrs": "^2.0.4", + "pug-error": "^1.3.3", + "pug-runtime": "^2.0.5", + "void-elements": "^2.0.1", + "with": "^5.0.0" + } + }, + "pug-error": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-1.3.3.tgz", + "integrity": "sha512-qE3YhESP2mRAWMFJgKdtT5D7ckThRScXRwkfo+Erqga7dyJdY3ZquspprMCj/9sJ2ijm5hXFWQE/A3l4poMWiQ==", + "optional": true + }, + "pug-filters": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-3.1.1.tgz", + "integrity": "sha512-lFfjNyGEyVWC4BwX0WyvkoWLapI5xHSM3xZJFUhx4JM4XyyRdO8Aucc6pCygnqV2uSgJFaJWW3Ft1wCWSoQkQg==", + "optional": true, + "requires": { + "clean-css": "^4.1.11", + "constantinople": "^3.0.1", + "jstransformer": "1.0.0", + "pug-error": "^1.3.3", + "pug-walk": "^1.1.8", + "resolve": "^1.1.6", + "uglify-js": "^2.6.1" + } + }, + "pug-lexer": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-4.1.0.tgz", + "integrity": "sha512-i55yzEBtjm0mlplW4LoANq7k3S8gDdfC6+LThGEvsK4FuobcKfDAwt6V4jKPH9RtiE3a2Akfg5UpafZ1OksaPA==", + "optional": true, + "requires": { + "character-parser": "^2.1.1", + "is-expression": "^3.0.0", + "pug-error": "^1.3.3" + } + }, + "pug-linker": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-3.0.6.tgz", + "integrity": "sha512-bagfuHttfQOpANGy1Y6NJ+0mNb7dD2MswFG2ZKj22s8g0wVsojpRlqveEQHmgXXcfROB2RT6oqbPYr9EN2ZWzg==", + "optional": true, + "requires": { + "pug-error": "^1.3.3", + "pug-walk": "^1.1.8" + } + }, + "pug-load": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/pug-load/-/pug-load-2.0.12.tgz", + "integrity": "sha512-UqpgGpyyXRYgJs/X60sE6SIf8UBsmcHYKNaOccyVLEuT6OPBIMo6xMPhoJnqtB3Q3BbO4Z3Bjz5qDsUWh4rXsg==", + "optional": true, + "requires": { + "object-assign": "^4.1.0", + "pug-walk": "^1.1.8" + } + }, + "pug-parser": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-5.0.1.tgz", + "integrity": "sha512-nGHqK+w07p5/PsPIyzkTQfzlYfuqoiGjaoqHv1LjOv2ZLXmGX1O+4Vcvps+P4LhxZ3drYSljjq4b+Naid126wA==", + "optional": true, + "requires": { + "pug-error": "^1.3.3", + "token-stream": "0.0.1" + } + }, + "pug-runtime": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-2.0.5.tgz", + "integrity": "sha512-P+rXKn9un4fQY77wtpcuFyvFaBww7/91f3jHa154qU26qFAnOe6SW1CbIDcxiG5lLK9HazYrMCCuDvNgDQNptw==", + "optional": true + }, + "pug-strip-comments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-1.0.4.tgz", + "integrity": "sha512-i5j/9CS4yFhSxHp5iKPHwigaig/VV9g+FgReLJWWHEHbvKsbqL0oP/K5ubuLco6Wu3Kan5p7u7qk8A4oLLh6vw==", + "optional": true, + "requires": { + "pug-error": "^1.3.3" + } + }, + "pug-walk": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-1.1.8.tgz", + "integrity": "sha512-GMu3M5nUL3fju4/egXwZO0XLi6fW/K3T3VTgFQ14GxNi8btlxgT5qZL//JwZFm/2Fa64J/PNS8AZeys3wiMkVA==", + "optional": true + }, + "querystring": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.1.tgz", + "integrity": "sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==" + }, + "readdirp": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "optional": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "optional": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "optional": true + }, + "resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "requires": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + } + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "optional": true + }, + "right-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", + "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", + "optional": true, + "requires": { + "align-text": "^0.1.1" + } + }, + "rollup": { + "version": "1.32.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.32.1.tgz", + "integrity": "sha512-/2HA0Ec70TvQnXdzynFffkjA6XN+1e2pEv/uKS5Ulca40g2L7KuOE3riasHoNVHOsFD5KKZgDsMk1CP3Tw9s+A==", + "requires": { + "@types/estree": "*", + "@types/node": "*", + "acorn": "^7.1.0" + } + }, + "rollup-plugin-vue": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/rollup-plugin-vue/-/rollup-plugin-vue-5.1.9.tgz", + "integrity": "sha512-DXzrBUD2j68Y6nls4MmuJsFL1SrQDpdgjxvhk/oy04LzJmXJoX1x31yLEBFkkmvpbon6Q885WJLvEMiMyT+3rA==", + "requires": { + "@vue/component-compiler": "^4.2.3", + "@vue/component-compiler-utils": "^3.1.2", + "debug": "^4.1.1", + "hash-sum": "^1.0.2", + "magic-string": "^0.25.7", + "querystring": "^0.2.0", + "rollup-pluginutils": "^2.8.2", + "source-map": "0.7.3", + "vue-runtime-helpers": "^1.1.2" + } + }, + "rollup-pluginutils": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", + "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", + "requires": { + "estree-walker": "^0.6.1" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "optional": true + }, + "sass": { + "version": "1.32.7", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.32.7.tgz", + "integrity": "sha512-C8Z4bjqGWnsYa11o8hpKAuoyFdRhrSHcYjCr+XAWVPSIQqC8mp2f5Dx4em0dKYehPzg5XSekmCjqJnEZbIls9A==", + "optional": true, + "requires": { + "chokidar": ">=2.0.0 <4.0.0" + } + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "optional": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "optional": true + }, + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==" + }, + "source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "optional": true, + "requires": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-url": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", + "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", + "optional": true + }, + "sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" + }, + "string-hash": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/string-hash/-/string-hash-1.1.3.tgz", + "integrity": "sha1-6Kr8CsGFW0Zmkp7X3RJ1311sgRs=" + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "stylus": { + "version": "0.54.8", + "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.54.8.tgz", + "integrity": "sha512-vr54Or4BZ7pJafo2mpf0ZcwA74rpuYCZbxrHBsH8kbcXOwSfvBFwsRfpGO5OD5fhG5HDCFW737PKaawI7OqEAg==", + "optional": true, + "requires": { + "css-parse": "~2.0.0", + "debug": "~3.1.0", + "glob": "^7.1.6", + "mkdirp": "~1.0.4", + "safer-buffer": "^2.1.2", + "sax": "~1.2.4", + "semver": "^6.3.0", + "source-map": "^0.7.3" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "optional": true + } + } + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "to-fast-properties": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", + "optional": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "optional": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "token-stream": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-0.0.1.tgz", + "integrity": "sha1-zu78cXp2xDFvEm0LnbqlXX598Bo=", + "optional": true + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "optional": true + }, + "uglify-js": { + "version": "2.8.29", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "optional": true, + "requires": { + "source-map": "~0.5.1", + "uglify-to-browserify": "~1.0.0", + "yargs": "~3.10.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "optional": true + } + } + }, + "uglify-to-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "optional": true + }, + "uniq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", + "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=" + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "optional": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", + "optional": true + }, + "vue": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.12.tgz", + "integrity": "sha512-uhmLFETqPPNyuLLbsKz6ioJ4q7AZHzD8ZVFNATNyICSZouqP2Sz0rotWQC8UNBF6VGSCs5abnKJoStA6JbCbfg==" + }, + "vue-runtime-helpers": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vue-runtime-helpers/-/vue-runtime-helpers-1.1.2.tgz", + "integrity": "sha512-pZfGp+PW/IXEOyETE09xQHR1CKkR9HfHZdnMD/FVLUNI+HxYTa82evx5WrF6Kz4s82qtqHvMZ8MZpbk2zT2E1Q==" + }, + "vue-template-compiler": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.12.tgz", + "integrity": "sha512-OzzZ52zS41YUbkCBfdXShQTe69j1gQDZ9HIX8miuC9C3rBCk9wIRjLiZZLrmX9V+Ftq/YEyv1JaVr5Y/hNtByg==", + "requires": { + "de-indent": "^1.0.2", + "he": "^1.1.0" + } + }, + "vue-template-es2015-compiler": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz", + "integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==" + }, + "window-size": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", + "optional": true + }, + "with": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/with/-/with-5.1.1.tgz", + "integrity": "sha1-+k2qktrzLE6pTtRTyB8EaGtXXf4=", + "optional": true, + "requires": { + "acorn": "^3.1.0", + "acorn-globals": "^3.0.0" + }, + "dependencies": { + "acorn": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", + "optional": true + } + } + }, + "wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "optional": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "optional": true + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + }, + "yargs": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "optional": true, + "requires": { + "camelcase": "^1.0.2", + "cliui": "^2.1.0", + "decamelize": "^1.0.0", + "window-size": "0.1.0" + } + } + } +} diff --git a/src/pretix/static/npm_dir/package.json b/src/pretix/static/npm_dir/package.json new file mode 100644 index 0000000000..74eeddfac9 --- /dev/null +++ b/src/pretix/static/npm_dir/package.json @@ -0,0 +1,16 @@ +{ + "name": "pretix", + "version": "0.0.0", + "private": true, + "scripts": {}, + "dependencies": { + "@babel/core": "^7.12.16", + "@babel/preset-env": "^7.12.16", + "@rollup/plugin-babel": "^5.3.0", + "@rollup/plugin-node-resolve": "^11.2.0", + "vue": "^2.6.10", + "rollup": "^1.17.0", + "rollup-plugin-vue": "^5.0.1", + "vue-template-compiler": "^2.6.10" + } +} diff --git a/src/pretix/static/npm_dir/rollup.config.js b/src/pretix/static/npm_dir/rollup.config.js new file mode 100644 index 0000000000..04c12918ea --- /dev/null +++ b/src/pretix/static/npm_dir/rollup.config.js @@ -0,0 +1,23 @@ +import vue from 'rollup-plugin-vue' +import { getBabelOutputPlugin } from '@rollup/plugin-babel' + +export default { + output: { + format: 'iife', + exports: 'named', + }, + plugins: [ + getBabelOutputPlugin({ + presets: ['@babel/preset-env'], + // Running babel on iife output is apparently discouraged since it can lead to global + // variable leaks. Since we didn't get it to work on inputs, let's take that risk. + // (In our tests, it did not leak anything.) + allowAllFormats: true + }), + vue({ + css: true, + compileTemplate: true, + needMap: false, + }), + ], +}; diff --git a/src/pretix/static/pretixbase/js/i18nstring.js b/src/pretix/static/pretixbase/js/i18nstring.js new file mode 100644 index 0000000000..1edcf8c410 --- /dev/null +++ b/src/pretix/static/pretixbase/js/i18nstring.js @@ -0,0 +1,25 @@ +function i18nstring_localize(o) { + var locale = document.body.attributes['data-pretixlocale'].value + var short_locale = locale.split('-')[0] + if (o[locale]) + return o[locale] + + if (o[short_locale]) + return o[short_locale] + + for (k of Object.keys(o)) { + if (k.split('-')[0] === short_locale && o[k]) { + return o[k] + } + } + + if (o['en']) + return o['en'] + + for (k of Object.keys(o)) { + if (o[k]) { + return o[k] + } + } +} + diff --git a/src/pretix/static/pretixcontrol/js/ui/checkinrules.js b/src/pretix/static/pretixcontrol/js/ui/checkinrules.js index aa95debd3b..f624f56f58 100644 --- a/src/pretix/static/pretixcontrol/js/ui/checkinrules.js +++ b/src/pretix/static/pretixcontrol/js/ui/checkinrules.js @@ -1,5 +1,10 @@ $(document).ready(function () { var TYPEOPS = { + // Every change to our supported JSON logic must be done + // * in pretix.base.services.checkin + // * in pretix.base.models.checkin + // * in checkinrules.js + // * in libpretixsync 'product': { 'inList': { 'label': gettext('is one of'), diff --git a/src/tests/api/test_teams.py b/src/tests/api/test_teams.py index 25d2f63a2d..28aee4553b 100644 --- a/src/tests/api/test_teams.py +++ b/src/tests/api/test_teams.py @@ -19,7 +19,7 @@ TEST_TEAM_RES = { 'id': 1, 'name': 'Test-Team', 'all_events': True, 'limit_events': [], 'can_create_events': True, 'can_change_teams': True, 'can_change_organizer_settings': True, 'can_manage_gift_cards': True, 'can_change_event_settings': True, 'can_change_items': True, 'can_view_orders': True, 'can_change_orders': True, - 'can_view_vouchers': True, 'can_change_vouchers': True + 'can_view_vouchers': True, 'can_change_vouchers': True, 'can_checkin_orders': False } SECOND_TEAM_RES = { @@ -27,7 +27,7 @@ SECOND_TEAM_RES = { 'can_create_events': False, 'can_change_teams': False, 'can_change_organizer_settings': False, 'can_manage_gift_cards': False, 'can_change_event_settings': False, 'can_change_items': False, 'can_view_orders': False, 'can_change_orders': False, - 'can_view_vouchers': False, 'can_change_vouchers': False + 'can_view_vouchers': False, 'can_change_vouchers': False, 'can_checkin_orders': False } diff --git a/src/tests/base/test_checkin.py b/src/tests/base/test_checkin.py index 89b565c08e..60b2898cdd 100644 --- a/src/tests/base/test_checkin.py +++ b/src/tests/base/test_checkin.py @@ -10,7 +10,8 @@ from freezegun import freeze_time from pretix.base.models import Checkin, Event, Order, OrderPosition, Organizer from pretix.base.services.checkin import ( - CheckInError, RequiredQuestionsError, perform_checkin, process_exit_all, + CheckInError, RequiredQuestionsError, SQLLogic, perform_checkin, + process_exit_all, ) @@ -369,6 +370,8 @@ def test_rules_simple(position, clist): clist.rules = {'and': [True, True]} clist.save() + + assert OrderPosition.objects.filter(SQLLogic(clist).apply(clist.rules), pk=position.pk).exists() perform_checkin(position, clist, {}) @@ -385,6 +388,7 @@ def test_rules_product(event, position, clist): ] } clist.save() + assert not OrderPosition.objects.filter(SQLLogic(clist).apply(clist.rules), pk=position.pk).exists() with pytest.raises(CheckInError) as excinfo: perform_checkin(position, clist, {}) assert excinfo.value.code == 'rules' @@ -400,6 +404,7 @@ def test_rules_product(event, position, clist): ] } clist.save() + assert OrderPosition.objects.filter(SQLLogic(clist).apply(clist.rules), pk=position.pk).exists() perform_checkin(position, clist, {}) @@ -421,6 +426,7 @@ def test_rules_variation(item, position, clist): clist.save() with pytest.raises(CheckInError) as excinfo: perform_checkin(position, clist, {}) + assert not OrderPosition.objects.filter(SQLLogic(clist).apply(clist.rules), pk=position.pk).exists() assert excinfo.value.code == 'rules' clist.rules = { @@ -434,6 +440,7 @@ def test_rules_variation(item, position, clist): ] } clist.save() + assert OrderPosition.objects.filter(SQLLogic(clist).apply(clist.rules), pk=position.pk).exists() perform_checkin(position, clist, {}) @@ -443,10 +450,13 @@ def test_rules_scan_number(position, clist): clist.allow_multiple_entries = True clist.rules = {"<": [{"var": "entries_number"}, 3]} clist.save() + assert OrderPosition.objects.filter(SQLLogic(clist).apply(clist.rules), pk=position.pk).exists() perform_checkin(position, clist, {}) perform_checkin(position, clist, {}) perform_checkin(position, clist, {}, type=Checkin.TYPE_EXIT) + assert OrderPosition.objects.filter(SQLLogic(clist).apply(clist.rules), pk=position.pk).exists() perform_checkin(position, clist, {}) + assert not OrderPosition.objects.filter(SQLLogic(clist).apply(clist.rules), pk=position.pk).exists() with pytest.raises(CheckInError) as excinfo: perform_checkin(position, clist, {}) assert excinfo.value.code == 'rules' @@ -463,20 +473,25 @@ def test_rules_scan_today(event, position, clist): perform_checkin(position, clist, {}) perform_checkin(position, clist, {}) perform_checkin(position, clist, {}, type=Checkin.TYPE_EXIT) + assert OrderPosition.objects.filter(SQLLogic(clist).apply(clist.rules), pk=position.pk).exists() perform_checkin(position, clist, {}) + assert not OrderPosition.objects.filter(SQLLogic(clist).apply(clist.rules), pk=position.pk).exists() with pytest.raises(CheckInError) as excinfo: perform_checkin(position, clist, {}) assert excinfo.value.code == 'rules' with freeze_time("2020-01-01 22:50:00"): + assert not OrderPosition.objects.filter(SQLLogic(clist).apply(clist.rules), pk=position.pk).exists() with pytest.raises(CheckInError) as excinfo: perform_checkin(position, clist, {}) assert excinfo.value.code == 'rules' with freeze_time("2020-01-01 23:10:00"): + assert OrderPosition.objects.filter(SQLLogic(clist).apply(clist.rules), pk=position.pk).exists() perform_checkin(position, clist, {}) perform_checkin(position, clist, {}) perform_checkin(position, clist, {}) + assert not OrderPosition.objects.filter(SQLLogic(clist).apply(clist.rules), pk=position.pk).exists() with pytest.raises(CheckInError) as excinfo: perform_checkin(position, clist, {}) assert excinfo.value.code == 'rules' @@ -492,18 +507,22 @@ def test_rules_scan_days(event, position, clist): with freeze_time("2020-01-01 10:00:00"): perform_checkin(position, clist, {}) perform_checkin(position, clist, {}) + assert OrderPosition.objects.filter(SQLLogic(clist).apply(clist.rules), pk=position.pk).exists() perform_checkin(position, clist, {}) with freeze_time("2020-01-03 10:00:00"): perform_checkin(position, clist, {}) perform_checkin(position, clist, {}) perform_checkin(position, clist, {}) + assert OrderPosition.objects.filter(SQLLogic(clist).apply(clist.rules), pk=position.pk).exists() perform_checkin(position, clist, {}) with freeze_time("2020-01-03 22:50:00"): + assert OrderPosition.objects.filter(SQLLogic(clist).apply(clist.rules), pk=position.pk).exists() perform_checkin(position, clist, {}) with freeze_time("2020-01-03 23:50:00"): + assert not OrderPosition.objects.filter(SQLLogic(clist).apply(clist.rules), pk=position.pk).exists() with pytest.raises(CheckInError) as excinfo: perform_checkin(position, clist, {}) assert excinfo.value.code == 'rules' @@ -518,11 +537,13 @@ def test_rules_time_isafter_tolerance(event, position, clist): clist.rules = {"isAfter": [{"var": "now"}, {"buildTime": ["date_admission"]}, 10]} clist.save() with freeze_time("2020-01-01 10:45:00"): + assert not OrderPosition.objects.filter(SQLLogic(clist).apply(clist.rules), pk=position.pk).exists() with pytest.raises(CheckInError) as excinfo: perform_checkin(position, clist, {}) assert excinfo.value.code == 'rules' with freeze_time("2020-01-01 10:51:00"): + assert OrderPosition.objects.filter(SQLLogic(clist).apply(clist.rules), pk=position.pk).exists() perform_checkin(position, clist, {}) @@ -536,11 +557,13 @@ def test_rules_time_isafter_no_tolerance(event, position, clist): clist.rules = {"isAfter": [{"var": "now"}, {"buildTime": ["date_admission"]}]} clist.save() with freeze_time("2020-01-01 10:51:00"): + assert not OrderPosition.objects.filter(SQLLogic(clist).apply(clist.rules), pk=position.pk).exists() with pytest.raises(CheckInError) as excinfo: perform_checkin(position, clist, {}) assert excinfo.value.code == 'rules' with freeze_time("2020-01-01 11:01:00"): + assert OrderPosition.objects.filter(SQLLogic(clist).apply(clist.rules), pk=position.pk).exists() perform_checkin(position, clist, {}) @@ -553,11 +576,13 @@ def test_rules_time_isbefore_with_tolerance(event, position, clist): clist.rules = {"isBefore": [{"var": "now"}, {"buildTime": ["date_to"]}, 10]} clist.save() with freeze_time("2020-01-01 11:11:00"): + assert not OrderPosition.objects.filter(SQLLogic(clist).apply(clist.rules), pk=position.pk).exists() with pytest.raises(CheckInError) as excinfo: perform_checkin(position, clist, {}) assert excinfo.value.code == 'rules' with freeze_time("2020-01-01 11:09:00"): + assert OrderPosition.objects.filter(SQLLogic(clist).apply(clist.rules), pk=position.pk).exists() perform_checkin(position, clist, {}) @@ -568,11 +593,13 @@ def test_rules_time_isafter_custom_time(event, position, clist): clist.rules = {"isAfter": [{"var": "now"}, {"buildTime": ["custom", "2020-01-01T22:00:00.000Z"]}, None]} clist.save() with freeze_time("2020-01-01 21:55:00"): + assert not OrderPosition.objects.filter(SQLLogic(clist).apply(clist.rules), pk=position.pk).exists() with pytest.raises(CheckInError) as excinfo: perform_checkin(position, clist, {}) assert excinfo.value.code == 'rules' with freeze_time("2020-01-01 22:05:00"): + assert OrderPosition.objects.filter(SQLLogic(clist).apply(clist.rules), pk=position.pk).exists() perform_checkin(position, clist, {}) @@ -587,11 +614,13 @@ def test_rules_isafter_subevent(position, clist, event): clist.rules = {"isAfter": [{"var": "now"}, {"buildTime": ["date_admission"]}]} clist.save() with freeze_time("2020-02-01 10:51:00"): + assert not OrderPosition.objects.filter(SQLLogic(clist).apply(clist.rules), pk=position.pk).exists() with pytest.raises(CheckInError) as excinfo: perform_checkin(position, clist, {}) assert excinfo.value.code == 'rules' with freeze_time("2020-02-01 11:01:00"): + assert OrderPosition.objects.filter(SQLLogic(clist).apply(clist.rules), pk=position.pk).exists() perform_checkin(position, clist, {})