From da6a6739e56df3857dcb2d8dc5f12935fd0d3823 Mon Sep 17 00:00:00 2001 From: Ben Halpern Date: Wed, 15 Jul 2020 11:37:19 -0400 Subject: [PATCH] [deploy] Invite users to join and create password (#9294) * Initial invitation work * Add more invitation code * Add proper registration page * Fix up tests * Fix failings * Fix spec * Add self-serve auth config * Add registered condition * Change profile image call and registered_at test * Change comment * Fix copy test * Stub emojipedia * Linting * Add registered at to factory * Ensure registered for user tag * Update app/views/internal/invitations/index.html.erb Co-authored-by: Michael Kohl * Update app/controllers/internal/invitations_controller.rb Co-authored-by: Michael Kohl * Slight changes * Update recover password flow * Update app/views/internal/invitations/index.html.erb Co-authored-by: Michael Kohl * Update app/controllers/internal/invitations_controller.rb Co-authored-by: Michael Kohl * Update db/schema.rb Co-authored-by: Michael Kohl --- Gemfile | 1 + Gemfile.lock | 4 ++ .../article-form-needed-legacy.scss | 2 +- app/controllers/api/v0/users_controller.rb | 3 +- .../internal/feedback_messages_controller.rb | 2 +- .../internal/invitations_controller.rb | 23 ++++++++ app/controllers/invitations_controller.rb | 29 +++++++++++ app/controllers/stories_controller.rb | 1 + app/helpers/application_helper.rb | 4 ++ app/labor/badge_rewarder.rb | 4 +- app/labor/email_digest.rb | 2 +- app/liquid_tags/user_tag.rb | 2 +- app/models/invitation.rb | 7 +++ app/models/user.rb | 4 +- app/queries/internal/users_query.rb | 2 +- app/services/authentication/authenticator.rb | 2 + app/services/mentions/create_all.rb | 2 +- .../articles/_sidebar_additional.html.erb | 2 +- app/views/devise/invitations/edit.html.erb | 31 +++++++++++ .../mailer/invitation_instructions.html.erb | 11 ++++ .../mailer/invitation_instructions.text.erb | 11 ++++ app/views/devise/passwords/edit.html.erb | 42 ++++++++------- app/views/devise/passwords/new.html.erb | 26 +++++----- .../registrations/_registration_form.html.erb | 38 ++++++++++++-- app/views/internal/growth/_results.html.erb | 8 +-- app/views/internal/invitations/index.html.erb | 35 +++++++++++++ app/views/internal/invitations/new.html.erb | 13 +++++ app/views/internal/users/index.html.erb | 5 +- app/views/layouts/_nav_menu.html.erb | 15 +++++- app/views/layouts/_signup_modal.html.erb | 25 +++++---- .../_providers_registration_form.html.erb | 24 +++++---- .../stories/_sign_in_invitation.html.erb | 2 +- app/views/users/_account.html.erb | 2 + .../users/_account_set_password.html.erb | 15 ++++++ .../metrics/record_daily_usage_worker.rb | 6 +-- config/fastly/snippets/safe_params_list.vcl | 2 +- config/initializers/devise.rb | 49 ++++++++++++++++++ config/locales/devise_invitable.en.yml | 31 +++++++++++ config/routes.rb | 4 +- ...712150048_devise_invitable_add_to_users.rb | 32 ++++++++++++ db/schema.rb | 17 +++++- spec/factories/users.rb | 1 + spec/labor/email_digest_spec.rb | 16 ++++++ spec/models/user_spec.rb | 11 ++++ spec/rails_helper.rb | 2 + spec/requests/api/v0/users_spec.rb | 6 +++ spec/requests/internal/invitations_spec.rb | 33 ++++++++++++ spec/requests/registrations_spec.rb | 14 ++++- spec/requests/user/user_profile_spec.rb | 5 ++ spec/services/users/delete_spec.rb | 2 + vendor/cache/devise_invitable-2.0.2.gem | Bin 0 -> 46080 bytes 51 files changed, 547 insertions(+), 83 deletions(-) create mode 100644 app/controllers/internal/invitations_controller.rb create mode 100644 app/controllers/invitations_controller.rb create mode 100644 app/models/invitation.rb create mode 100644 app/views/devise/invitations/edit.html.erb create mode 100644 app/views/devise/mailer/invitation_instructions.html.erb create mode 100644 app/views/devise/mailer/invitation_instructions.text.erb create mode 100644 app/views/internal/invitations/index.html.erb create mode 100644 app/views/internal/invitations/new.html.erb create mode 100644 app/views/users/_account_set_password.html.erb create mode 100644 config/locales/devise_invitable.en.yml create mode 100644 db/migrate/20200712150048_devise_invitable_add_to_users.rb create mode 100644 spec/requests/internal/invitations_spec.rb create mode 100644 vendor/cache/devise_invitable-2.0.2.gem diff --git a/Gemfile b/Gemfile index 856d88d29..dfc766b16 100644 --- a/Gemfile +++ b/Gemfile @@ -30,6 +30,7 @@ gem "cloudinary", "~> 1.16" # Client library for easily using the Cloudinary ser gem "counter_culture", "~> 2.5" # counter_culture provides turbo-charged counter caches that are kept up-to-date gem "ddtrace", "~> 0.37.0" # ddtrace is Datadog’s tracing client for Ruby. gem "devise", "~> 4.7" # Flexible authentication solution for Rails +gem "devise_invitable", "~> 2.0.0" # Allows invitations to be sent for joining gem "dogstatsd-ruby", "~> 4.8" # A client for DogStatsD, an extension of the StatsD metric server for Datadog gem "doorkeeper", "~> 5.4" # Oauth 2 provider gem "dry-struct", "~> 1.2" # Typed structs and value objects diff --git a/Gemfile.lock b/Gemfile.lock index 4a1a27b41..e38b1f254 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -251,6 +251,9 @@ GEM railties (>= 4.1.0) responders warden (~> 1.2.3) + devise_invitable (2.0.2) + actionmailer (>= 5.0) + devise (>= 4.6) diff-lcs (1.3) distribution (0.7.3) docile (1.3.2) @@ -911,6 +914,7 @@ DEPENDENCIES ddtrace (~> 0.37.0) derailed_benchmarks (~> 1.7) devise (~> 4.7) + devise_invitable (~> 2.0.0) dogstatsd-ruby (~> 4.8) doorkeeper (~> 5.4) dry-struct (~> 1.2) diff --git a/app/assets/stylesheets/article-form-needed-legacy.scss b/app/assets/stylesheets/article-form-needed-legacy.scss index d6eaa2f45..7298318bf 100644 --- a/app/assets/stylesheets/article-form-needed-legacy.scss +++ b/app/assets/stylesheets/article-form-needed-legacy.scss @@ -476,7 +476,7 @@ } .new-article-pitch { - padding-top: calc(8% + 50px); + padding-top: calc(3% + 25px); width: 900px; max-width: 94%; margin: auto; diff --git a/app/controllers/api/v0/users_controller.rb b/app/controllers/api/v0/users_controller.rb index a49ad3c86..03d81f709 100644 --- a/app/controllers/api/v0/users_controller.rb +++ b/app/controllers/api/v0/users_controller.rb @@ -6,7 +6,7 @@ module Api SHOW_ATTRIBUTES_FOR_SERIALIZATION = %i[ id username name summary twitter_username github_username website_url - location created_at profile_image + location created_at profile_image registered ].freeze private_constant :SHOW_ATTRIBUTES_FOR_SERIALIZATION @@ -18,6 +18,7 @@ module Api else relation.find(params[:id]) end + not_found unless @user.registered end def me diff --git a/app/controllers/internal/feedback_messages_controller.rb b/app/controllers/internal/feedback_messages_controller.rb index 8802c7363..d57dc1b97 100644 --- a/app/controllers/internal/feedback_messages_controller.rb +++ b/app/controllers/internal/feedback_messages_controller.rb @@ -17,7 +17,7 @@ module Internal where("score > ? AND score < ?", -10, 8). limit(120) - @possible_spam_users = User.where( + @possible_spam_users = User.registered.where( "github_created_at > ? OR twitter_created_at > ? OR length(name) > ?", 50.hours.ago, 50.hours.ago, 30 ). diff --git a/app/controllers/internal/invitations_controller.rb b/app/controllers/internal/invitations_controller.rb new file mode 100644 index 000000000..5f88f1f84 --- /dev/null +++ b/app/controllers/internal/invitations_controller.rb @@ -0,0 +1,23 @@ +module Internal + class InvitationsController < Internal::ApplicationController + layout "internal" + + def index + @invitations = User.where(registered: false).page(params[:page]).per(50) + end + + def new; end + + def create + email = params.dig(:user, :email) + name = params.dig(:user, :name) + username = "#{name.downcase.tr(' ', '_').gsub(/[^0-9a-z ]/i, '')}_#{rand(1000)}" + User.invite!(email: email, + name: name, + username: username, + remote_profile_image_url: Users::ProfileImageGenerator.call, + registered: false) + redirect_to internal_invitations_path + end + end +end diff --git a/app/controllers/invitations_controller.rb b/app/controllers/invitations_controller.rb new file mode 100644 index 000000000..54d2a0547 --- /dev/null +++ b/app/controllers/invitations_controller.rb @@ -0,0 +1,29 @@ +class InvitationsController < Devise::InvitationsController + def update + # Copied from https://github.com/scambra/devise_invitable/blob/master/app/controllers/devise/invitations_controller.rb + # And edited. This is a common devise pattern, similar to OmniauthCallbacksController. + raw_invitation_token = update_resource_params[:invitation_token] + self.resource = accept_resource + invitation_accepted = resource.errors.empty? + + yield resource if block_given? + + if invitation_accepted + resource.registered = true + resource.registered_at = Time.current + if resource.class.allow_insecure_sign_in_after_accept + flash_message = resource.active_for_authentication? ? :updated : :updated_not_active + set_flash_message :notice, flash_message if is_flashing_format? + resource.after_database_authentication + sign_in(resource_name, resource) + redirect_to onboarding_path + else + set_flash_message :notice, :updated_not_active if is_flashing_format? + respond_with resource, location: new_session_path(resource_name) + end + else + resource.invitation_token = raw_invitation_token + respond_with_navigational(resource) { render :edit } + end + end +end diff --git a/app/controllers/stories_controller.rb b/app/controllers/stories_controller.rb index 0d02e5741..e96588f71 100644 --- a/app/controllers/stories_controller.rb +++ b/app/controllers/stories_controller.rb @@ -218,6 +218,7 @@ class StoriesController < ApplicationController return end not_found if @user.username.include?("spam_") && @user.decorate.fully_banished? + not_found unless @user.registered assign_user_comments assign_user_stories @list_of = "articles" diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 0b3274a49..d42421de2 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -101,6 +101,10 @@ module ApplicationHelper end end + def any_selfserve_auth? + authentication_enabled_providers.any? + end + def beautified_url(url) url.sub(/\A((https?|ftp):\/)?\//, "").sub(/\?.*/, "").chomp("/") rescue StandardError diff --git a/app/labor/badge_rewarder.rb b/app/labor/badge_rewarder.rb index 5a5ef36ee..c2b9fc9dc 100644 --- a/app/labor/badge_rewarder.rb +++ b/app/labor/badge_rewarder.rb @@ -14,7 +14,7 @@ module BadgeRewarder message = "Happy #{ApplicationConfig['COMMUNITY_NAME']} birthday! " \ "Can you believe it's been #{i} #{'year'.pluralize(i)} already?!" badge = Badge.find_by!(slug: "#{YEARS[i]}-year-club") - User.where("created_at < ? AND created_at > ?", i.year.ago, i.year.ago - 2.days).find_each do |user| + User.registered.where("created_at < ? AND created_at > ?", i.year.ago, i.year.ago - 2.days).find_each do |user| achievement = BadgeAchievement.create( user_id: user.id, badge_id: badge.id, @@ -114,7 +114,7 @@ module BadgeRewarder def self.award_badges(usernames, slug, message_markdown) badge_id = Badge.find_by!(slug: slug).id - User.where(username: usernames).find_each do |user| + User.registered.where(username: usernames).find_each do |user| BadgeAchievement.create( user_id: user.id, badge_id: badge_id, diff --git a/app/labor/email_digest.rb b/app/labor/email_digest.rb index 362e7cb4b..e821f9776 100644 --- a/app/labor/email_digest.rb +++ b/app/labor/email_digest.rb @@ -27,6 +27,6 @@ class EmailDigest private def get_users - User.where(email_digest_periodic: true).where.not(email: "") + User.registered.where(email_digest_periodic: true).where.not(email: "") end end diff --git a/app/liquid_tags/user_tag.rb b/app/liquid_tags/user_tag.rb index 0a1f801d4..0bc72b726 100644 --- a/app/liquid_tags/user_tag.rb +++ b/app/liquid_tags/user_tag.rb @@ -25,7 +25,7 @@ class UserTag < LiquidTagBase private def parse_username_to_user(user) - User.find_by(username: user) || DELETED_USER + User.find_by(username: user, registered: true) || DELETED_USER end def path_to_profile(user) diff --git a/app/models/invitation.rb b/app/models/invitation.rb new file mode 100644 index 000000000..13e915082 --- /dev/null +++ b/app/models/invitation.rb @@ -0,0 +1,7 @@ +class Invitation < ApplicationRecord + # This class exists to take advantage of Rolify for limiting authorization + # on internal reports. + # NOTE: It is not backed by a database table and should not be expected to + # function like a traditional Rails model + resourcify +end diff --git a/app/models/user.rb b/app/models/user.rb index 0dd42ccec..23ac206fd 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -112,7 +112,7 @@ class User < ApplicationRecord mount_uploader :profile_image, ProfileImageUploader - devise :omniauthable, :registerable, :database_authenticatable, :confirmable, :rememberable + devise :invitable, :omniauthable, :registerable, :database_authenticatable, :confirmable, :rememberable, :recoverable validates :behance_url, length: { maximum: 100 }, allow_blank: true, format: BEHANCE_URL_REGEXP validates :bg_color_hex, format: COLOR_HEX_REGEXP, allow_blank: true @@ -164,6 +164,7 @@ class User < ApplicationRecord alias_attribute :subscribed_to_welcome_notifications?, :welcome_notifications scope :eager_load_serialized_data, -> { includes(:roles) } + scope :registered, -> { where(registered: true) } after_save :bust_cache after_save :subscribe_to_mailchimp_newsletter @@ -385,6 +386,7 @@ class User < ApplicationRecord end def subscribe_to_mailchimp_newsletter + return unless registered return unless email.present? && email.include?("@") return if saved_changes["unconfirmed_email"] && saved_changes["confirmation_sent_at"] return unless saved_changes.key?(:email) || saved_changes.key?(:email_newsletter) diff --git a/app/queries/internal/users_query.rb b/app/queries/internal/users_query.rb index 7f08a1209..a35654816 100644 --- a/app/queries/internal/users_query.rb +++ b/app/queries/internal/users_query.rb @@ -1,6 +1,6 @@ module Internal class UsersQuery - def self.call(relation: User.all, options: {}) + def self.call(relation: User.registered, options: {}) role, search = options.values_at(:role, :search) relation = relation.with_role(role, :any) if role.presence diff --git a/app/services/authentication/authenticator.rb b/app/services/authentication/authenticator.rb index dcd47d7aa..c8e18cc98 100644 --- a/app/services/authentication/authenticator.rb +++ b/app/services/authentication/authenticator.rb @@ -116,6 +116,8 @@ module Authentication { password: Devise.friendly_token(20), signup_cta_variant: cta_variant, + registered: true, + registered_at: Time.current, saw_onboarding: false, editor_version: :v2 } diff --git a/app/services/mentions/create_all.rb b/app/services/mentions/create_all.rb index c184fefda..cfc1bc998 100644 --- a/app/services/mentions/create_all.rb +++ b/app/services/mentions/create_all.rb @@ -28,7 +28,7 @@ module Mentions end def collect_existing_users(usernames) - User.where(username: usernames) + User.registered.where(username: usernames) end def create_mentions_for(users) diff --git a/app/views/articles/_sidebar_additional.html.erb b/app/views/articles/_sidebar_additional.html.erb index 7b8a5da64..87775c4bd 100644 --- a/app/views/articles/_sidebar_additional.html.erb +++ b/app/views/articles/_sidebar_additional.html.erb @@ -1,7 +1,7 @@ -

Great to have you

+ <% if any_selfserve_auth? %> +

Great to have you

+ <% else %> +

Registration is by invitation only

+ <% end %> <% end %>

Open Source 😇

Free Forever ❤️

diff --git a/app/views/internal/growth/_results.html.erb b/app/views/internal/growth/_results.html.erb index 91a7679d1..5155c746f 100644 --- a/app/views/internal/growth/_results.html.erb +++ b/app/views/internal/growth/_results.html.erb @@ -1,4 +1,4 @@ -

Past <%= num_days %> day new users (<%= User.where("created_at > ?", num_days.day.ago).count %>)

+

Past <%= num_days %> day new users (<%= User.where("registered_at > ?", num_days.day.ago).count %>)

@@ -7,13 +7,13 @@
  • - Average comments: <%= User.where("created_at > ?", num_days.day.ago).average(:comments_count) %> + Average comments: <%= User.where("registered_at > ?", num_days.day.ago).average(:comments_count) %>
  • - Average articles: <%= User.where("created_at > ?", num_days.day.ago).average(:articles_count) %> + Average articles: <%= User.where("registered_at > ?", num_days.day.ago).average(:articles_count) %>
  • - Average reactions: <%= User.where("created_at > ?", num_days.day.ago).average(:reactions_count) %> + Average reactions: <%= User.where("registered_at > ?", num_days.day.ago).average(:reactions_count) %>
diff --git a/app/views/internal/invitations/index.html.erb b/app/views/internal/invitations/index.html.erb new file mode 100644 index 000000000..3da0966ec --- /dev/null +++ b/app/views/internal/invitations/index.html.erb @@ -0,0 +1,35 @@ +
+
+ +
+
+ + + + + + + + + + + + <% @invitations.each do |user| %> + + + + + + + <% end %> + +
UserIDNameEmail
<%= link_to user.username, internal_user_path(user) %><%= user.id %><%= user.name %><%= user.email %>
+ +<%= paginate @invitations, theme: "internal" %> diff --git a/app/views/internal/invitations/new.html.erb b/app/views/internal/invitations/new.html.erb new file mode 100644 index 000000000..d15b1559b --- /dev/null +++ b/app/views/internal/invitations/new.html.erb @@ -0,0 +1,13 @@ +
+
+ <%= form_for(User.new, url: internal_invitations_path) do |f| %> +
+ <%= f.label "Email:" %> + <%= f.text_field :email, required: true, class: "form-control", placeholder: "Email of invitee" %> + <%= f.label "Name:" %> + <%= f.text_field :name, required: true, class: "form-control", placeholder: "Name of invitee" %> +
+ <%= f.submit class: "btn btn-primary" %> + <% end %> +
+
diff --git a/app/views/internal/users/index.html.erb b/app/views/internal/users/index.html.erb index 904c268fa..7adcf8c1a 100644 --- a/app/views/internal/users/index.html.erb +++ b/app/views/internal/users/index.html.erb @@ -1,5 +1,5 @@
-
+
diff --git a/app/views/layouts/_nav_menu.html.erb b/app/views/layouts/_nav_menu.html.erb index 9f5814d3e..f74d836ae 100644 --- a/app/views/layouts/_nav_menu.html.erb +++ b/app/views/layouts/_nav_menu.html.erb @@ -21,7 +21,20 @@
<% else %>
- <%= render partial: "shared/authentication/providers_nav_menu" %> + <% if any_selfserve_auth? %> + <%= render partial: "shared/authentication/providers_nav_menu" %> + + More Sign In Options + + <% else %> + + Sign In + + <% end %>
All about <%= community_name %> diff --git a/app/views/layouts/_signup_modal.html.erb b/app/views/layouts/_signup_modal.html.erb index 61535da1e..28120c49b 100644 --- a/app/views/layouts/_signup_modal.html.erb +++ b/app/views/layouts/_signup_modal.html.erb @@ -9,19 +9,22 @@ class: "mascot-image", alt: SiteConfig.mascot_image_description, loading: "lazy") %> + <% if any_selfserve_auth? %> +

Join our <%= community_qualified_name %> :)

-

Join our <%= community_qualified_name %> :)

+ <% if SiteConfig.tagline.present? %> +

<%= SiteConfig.tagline %>

+ <% end %> - <% if SiteConfig.tagline.present? %> -

<%= SiteConfig.tagline %>

+
+ <%= render partial: "shared/authentication/providers_signup_modal" %> +
+ +

+ We strive for transparency and don't collect excess data. +

+ <% else %> +

Sign in to <%= community_qualified_name %> :)

<% end %> - -
- <%= render partial: "shared/authentication/providers_signup_modal" %> -
- -

- We strive for transparency and don't collect excess data. -

diff --git a/app/views/shared/authentication/_providers_registration_form.html.erb b/app/views/shared/authentication/_providers_registration_form.html.erb index f3b5423d6..7e2ad9e7b 100644 --- a/app/views/shared/authentication/_providers_registration_form.html.erb +++ b/app/views/shared/authentication/_providers_registration_form.html.erb @@ -1,9 +1,15 @@ -<% authentication_enabled_providers.each do |provider| %> - " - class="crayons-btn crayons-btn--xl crayons-btn--brand-<%= provider.provider_name %> crayons-btn--icon-left m-1 whitespace-nowrap" - data-no-instant> - <%= inline_svg_tag("#{provider.provider_name}.svg", aria: true, class: "crayons-icon", title: provider.provider_name) %> - Sign In with <%= provider.official_name %> - -<% end %> + + +<% if authentication_enabled_providers.any? %> +

We require social registration to prevent abuse.

+<% end %> \ No newline at end of file diff --git a/app/views/stories/_sign_in_invitation.html.erb b/app/views/stories/_sign_in_invitation.html.erb index 43062b109..a21671972 100644 --- a/app/views/stories/_sign_in_invitation.html.erb +++ b/app/views/stories/_sign_in_invitation.html.erb @@ -5,7 +5,7 @@

<%= community_name %> is a community of
- <%= number_with_delimiter User.estimated_count %> amazing <%= community_members_label %> + <%= number_with_delimiter User.registered.estimated_count %> amazing <%= community_members_label %>

diff --git a/app/views/users/_account.html.erb b/app/views/users/_account.html.erb index af486327c..d9c0225fb 100644 --- a/app/views/users/_account.html.erb +++ b/app/views/users/_account.html.erb @@ -1,5 +1,7 @@ <%= render "users/additional_authentication" %> +<%= render "users/account_set_password" %> + <%= render "users/account_providers_emails" %>
diff --git a/app/views/users/_account_set_password.html.erb b/app/views/users/_account_set_password.html.erb new file mode 100644 index 000000000..5581f5948 --- /dev/null +++ b/app/views/users/_account_set_password.html.erb @@ -0,0 +1,15 @@ +
+
+

Set New Password

+

As an alternative to signing in via linked social accounts, you may create a password.

+
+ <%= form_for @user do |f| %> +
+ <%= f.label :password, class: "crayons-field__label" %> + <%= f.password_field :password, class: "crayons-textfield", autocomplete: "off" %> + <%= f.label :password_confirmation, "Confirm new password", class: "crayons-field__label" %>
+ <%= f.password_field :password_confirmation, autocomplete: "off", class: "crayons-textfield" %> +
+ + <% end %> +
diff --git a/app/workers/metrics/record_daily_usage_worker.rb b/app/workers/metrics/record_daily_usage_worker.rb index 94f383173..5cd0a22ab 100644 --- a/app/workers/metrics/record_daily_usage_worker.rb +++ b/app/workers/metrics/record_daily_usage_worker.rb @@ -27,7 +27,7 @@ module Metrics DatadogStatsClient.count("articles.first_past_24h", first_articles_past_24h, tags: ["resource:articles"]) # Users who signed up in the past 24 hours who have made at least 1 comment so far - new_users_min_1_comment_past_24h = User.where("comments_count >= ? AND created_at > ?", 1, 24.hours.ago).size + new_users_min_1_comment_past_24h = User.where("comments_count >= ? AND registered_at > ?", 1, 24.hours.ago).size DatadogStatsClient.count( "users.new_min_1_comment_past_24h", new_users_min_1_comment_past_24h, @@ -64,8 +64,8 @@ module Metrics ids_by_day << id end flat_id_list = ids_by_day.flatten.uniq - non_new_user_ids = User.where("created_at < ?", 7.days.ago).where(id: flat_id_list).pluck(:id) - new_user_ids = User.where("created_at > ? AND created_at < ?", 8.days.ago, + non_new_user_ids = User.where("registered_at < ?", 7.days.ago).where(id: flat_id_list).pluck(:id) + new_user_ids = User.where("registered_at > ? AND registered_at < ?", 8.days.ago, 7.days.ago).where(id: flat_id_list).pluck(:id) record_active_days_of_group(ids_by_day, non_new_user_ids, "established") record_active_days_of_group(ids_by_day, new_user_ids, "new") diff --git a/config/fastly/snippets/safe_params_list.vcl b/config/fastly/snippets/safe_params_list.vcl index 65a9d9e82..30c3f0c65 100644 --- a/config/fastly/snippets/safe_params_list.vcl +++ b/config/fastly/snippets/safe_params_list.vcl @@ -2,6 +2,6 @@ import querystring; sub vcl_recv { # return this URL with only the parameters that match this regular expression if (req.url !~ "/internal/" && req.url !~ "/search/" && req.url !~ "/bulk_show") { - set req.url = querystring.regfilter_except(req.url, "^(a_id|args|article_id|article_ids|articles|asc|callback_url|category|chat_channel_id|client_id|code|collection_id|commentable_id|commentable_type|confirmation_token|created_at|end|filter|followable_id|followable_type|fork_id|i|key|message_offset|name|oauth_token|oauth_verifier|offset|org_id|organization_id|p|page|per_page|prefill|preview|purchaser|reactable_ids|redirect_uri|reported_url|reporter_username|response_type|scope|search|signature|sort|start|state|status|tag|tag_list|top|type_of|url|username|ut|verb)$"); + set req.url = querystring.regfilter_except(req.url, "^(a_id|args|article_id|article_ids|articles|asc|callback_url|category|chat_channel_id|client_id|code|collection_id|commentable_id|commentable_type|confirmation_token|created_at|end|filter|followable_id|followable_type|fork_id|i|key|message_offset|name|oauth_token|oauth_verifier|offset|org_id|organization_id|p|page|per_page|prefill|preview|purchaser|reactable_ids|redirect_uri|reported_url|reporter_username|response_type|scope|search|signature|sort|start|state|status|tag|tag_list|top|type_of|url|username|invitation_token|reset_password_token|ut|verb)$"); } } diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 1dc2a53eb..6ac411614 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -100,6 +100,55 @@ Devise.setup do |config| # Setup a pepper to generate the encrypted password. # config.pepper = '' + # ==> Configuration for :invitable + # The period the generated invitation token is valid. + # After this period, the invited resource won't be able to accept the invitation. + # When invite_for is 0 (the default), the invitation won't expire. + # config.invite_for = 2.weeks + + # Number of invitations users can send. + # - If invitation_limit is nil, there is no limit for invitations, users can + # send unlimited invitations, invitation_limit column is not used. + # - If invitation_limit is 0, users can't send invitations by default. + # - If invitation_limit n > 0, users can send n invitations. + # You can change invitation_limit column for some users so they can send more + # or less invitations, even with global invitation_limit = 0 + # Default: nil + # config.invitation_limit = 5 + + # The key to be used to check existing users when sending an invitation + # and the regexp used to test it when validate_on_invite is not set. + # config.invite_key = { email: /\A[^@]+@[^@]+\z/ } + # config.invite_key = { email: /\A[^@]+@[^@]+\z/, username: nil } + + # Ensure that invited record is valid. + # The invitation won't be sent if this check fails. + # Default: false + # config.validate_on_invite = true + + # Resend invitation if user with invited status is invited again + # Default: true + # config.resend_invitation = false + + # The class name of the inviting model. If this is nil, + # the #invited_by association is declared to be polymorphic. + # Default: nil + # config.invited_by_class_name = 'User' + + # The foreign key to the inviting model (if invited_by_class_name is set) + # Default: :invited_by_id + # config.invited_by_foreign_key = :invited_by_id + + # The column name used for counter_cache column. If this is nil, + # the #invited_by association is declared without counter_cache. + # Default: nil + # config.invited_by_counter_cache = :invitations_count + + # Auto-login after the user accepts the invite. If this is false, + # the user will need to manually log in after accepting the invite. + # Default: true + # config.allow_insecure_sign_in_after_accept = false + # ==> Configuration for :confirmable # A period that the user is allowed to access the website even without # confirming their account. For instance, if set to 2.days, the user will be diff --git a/config/locales/devise_invitable.en.yml b/config/locales/devise_invitable.en.yml new file mode 100644 index 000000000..63a0c5bd8 --- /dev/null +++ b/config/locales/devise_invitable.en.yml @@ -0,0 +1,31 @@ +en: + devise: + failure: + invited: "You have a pending invitation, accept it to finish creating your account." + invitations: + send_instructions: "An invitation email has been sent to %{email}." + invitation_token_invalid: "The invitation token provided is not valid!" + updated: "Your password was set successfully. You are now signed in." + updated_not_active: "Your password was set successfully." + no_invitations_remaining: "No invitations remaining" + invitation_removed: "Your invitation was removed." + new: + header: "Send invitation" + submit_button: "Send an invitation" + edit: + header: "Set your password" + submit_button: "Let's Get Started" + mailer: + invitation_instructions: + subject: "Invitation instructions" + hello: "Hello %{email}" + someone_invited_you: "Someone has invited you to %{url}, you can accept it through the link below." + accept: "Accept invitation" + accept_until: "This invitation will be due in %{due_date}." + ignore: "If you don't want to accept the invitation, please ignore this email. Your account won't be created until you access the link above and set your password." + time: + formats: + devise: + mailer: + invitation_instructions: + accept_until_format: "%B %d, %Y %I:%M %p" diff --git a/config/routes.rb b/config/routes.rb index 858ab79d4..02fa49fd1 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -7,7 +7,8 @@ Rails.application.routes.draw do devise_for :users, controllers: { omniauth_callbacks: "omniauth_callbacks", - registrations: "registrations" + registrations: "registrations", + invitations: "invitations" } devise_scope :user do @@ -52,6 +53,7 @@ Rails.application.routes.draw do resources :comments, only: [:index] resources :events, only: %i[index create update] resources :feedback_messages, only: %i[index show] + resources :invitations, only: %i[index new create] resources :pages, only: %i[index new create edit update destroy] resources :mods, only: %i[index update] resources :moderator_actions, only: %i[index] diff --git a/db/migrate/20200712150048_devise_invitable_add_to_users.rb b/db/migrate/20200712150048_devise_invitable_add_to_users.rb new file mode 100644 index 000000000..4c04ded67 --- /dev/null +++ b/db/migrate/20200712150048_devise_invitable_add_to_users.rb @@ -0,0 +1,32 @@ +class DeviseInvitableAddToUsers < ActiveRecord::Migration[6.0] + def up + safety_assured do + change_table :users, bulk: true do |t| + t.string :invitation_token + t.boolean :registered, default: true + t.datetime :registered_at + t.datetime :invitation_created_at + t.datetime :invitation_sent_at + t.datetime :invitation_accepted_at + t.integer :invitation_limit + t.references :invited_by, polymorphic: true + t.integer :invitations_count, default: 0 + t.index :invitations_count + t.index :invitation_token, unique: true # for invitable + t.index :invited_by_id + end + end + end + + def down + safety_assured do + change_table :users do |t| + t.remove_references :invited_by, polymorphic: true + t.remove :invitations_count, :invitation_limit, + :invitation_sent_at, :invitation_accepted_at, + :invitation_token, :invitation_created_at, + :registered, :registered_at + end + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 4b95f6408..edf5de1c1 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2020_07_10_174257) do +ActiveRecord::Schema.define(version: 2020_07_12_150048) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -492,6 +492,7 @@ ActiveRecord::Schema.define(version: 2020_07_10_174257) do t.datetime "updated_at", null: false t.bigint "user_id" t.datetime "verified_at" + t.index ["user_id"], name: "index_email_authorizations_on_user_id" end create_table "events", force: :cascade do |t| @@ -1244,6 +1245,14 @@ ActiveRecord::Schema.define(version: 2020_07_10_174257) do t.string "inbox_guidelines" t.string "inbox_type", default: "private" t.string "instagram_url" + t.datetime "invitation_accepted_at" + t.datetime "invitation_created_at" + t.integer "invitation_limit" + t.datetime "invitation_sent_at" + t.string "invitation_token" + t.integer "invitations_count", default: 0 + t.bigint "invited_by_id" + t.string "invited_by_type" t.jsonb "language_settings", default: {}, null: false t.datetime "last_article_at", default: "2017-01-01 05:00:00" t.datetime "last_comment_at", default: "2017-01-01 05:00:00" @@ -1274,6 +1283,8 @@ ActiveRecord::Schema.define(version: 2020_07_10_174257) do t.datetime "profile_updated_at", default: "2017-01-01 05:00:00" t.integer "rating_votes_count", default: 0, null: false t.integer "reactions_count", default: 0, null: false + t.boolean "registered", default: true + t.datetime "registered_at" t.datetime "remember_created_at" t.string "remember_token" t.float "reputation_modifier", default: 1.0 @@ -1308,6 +1319,10 @@ ActiveRecord::Schema.define(version: 2020_07_10_174257) do t.index ["created_at"], name: "index_users_on_created_at" t.index ["email"], name: "index_users_on_email", unique: true t.index ["github_username"], name: "index_users_on_github_username", unique: true + t.index ["invitation_token"], name: "index_users_on_invitation_token", unique: true + t.index ["invitations_count"], name: "index_users_on_invitations_count" + t.index ["invited_by_id"], name: "index_users_on_invited_by_id" + t.index ["invited_by_type", "invited_by_id"], name: "index_users_on_invited_by_type_and_invited_by_id" t.index ["language_settings"], name: "index_users_on_language_settings", using: :gin t.index ["old_old_username"], name: "index_users_on_old_old_username" t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true diff --git a/spec/factories/users.rb b/spec/factories/users.rb index 5aaaaa74d..e11365331 100644 --- a/spec/factories/users.rb +++ b/spec/factories/users.rb @@ -20,6 +20,7 @@ FactoryBot.define do checked_code_of_conduct { true } checked_terms_and_conditions { true } display_announcements { true } + registered_at { Time.current } signup_cta_variant { "navbar_basic" } email_digest_periodic { false } bg_color_hex { Faker::Color.hex_color } diff --git a/spec/labor/email_digest_spec.rb b/spec/labor/email_digest_spec.rb index 92b3c38b4..dd49df813 100644 --- a/spec/labor/email_digest_spec.rb +++ b/spec/labor/email_digest_spec.rb @@ -26,6 +26,22 @@ RSpec.describe EmailDigest, type: :labor do expect(mailer).to have_received(:digest_email) expect(message_delivery).to have_received(:deliver_now) end + + it "does not send email when user does not have email_digest_periodic" do + articles = create_list(:article, 3, user_id: author.id, public_reactions_count: 20, score: 20) + user.update_column(:email_digest_periodic, false) + described_class.send_periodic_digest_email + + expect(DigestMailer).not_to have_received(:with).with(user: user, articles: articles) + end + + it "does not send email when user is not registered" do + articles = create_list(:article, 3, user_id: author.id, public_reactions_count: 20, score: 20) + user.update_column(:registered, false) + described_class.send_periodic_digest_email + + expect(DigestMailer).not_to have_received(:with).with(user: user, articles: articles) + end end end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 37cb0a63c..70baf135c 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -724,6 +724,12 @@ RSpec.describe User, type: :model do end end + it "does not enqueue with a non-registered user" do + sidekiq_assert_no_enqueued_jobs(only: Users::SubscribeToMailchimpNewsletterWorker) do + user.update(registered: false) + end + end + it "does not enqueue when the email address or subscription status has not changed" do # The trait replaces the method with a dummy, but we need the actual method for this test. user = described_class.find(create(:user, :ignore_mailchimp_subscribe_callback).id) @@ -901,6 +907,11 @@ RSpec.describe User, type: :model do expect(identity.auth_data_dump.provider).to eq(identity.provider) end + it "marks registered_at for newly registered user" do + new_user = user_from_authorization_service(:twitter, nil, "navbar_basic") + expect(new_user.registered_at).not_to be nil + end + it "persists extracts relevant identity data from new twitter user" do new_user = user_from_authorization_service(:twitter, nil, "navbar_basic") expect(new_user.twitter_followers_count).to eq(100) diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index f711699d8..dd25b9bb9 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -138,6 +138,8 @@ RSpec.configure do |config| config.before do stub_request(:any, /res.cloudinary.com/).to_rack("dsdsdsds") + stub_request(:any, /emojipedia-us.s3.dualstack.us-west-1.amazonaws.com/).to_rack("stubbed-emoji") + stub_request(:post, /api.fastly.com/). to_return(status: 200, body: "".to_json, headers: {}) diff --git a/spec/requests/api/v0/users_spec.rb b/spec/requests/api/v0/users_spec.rb index d9679bf73..5fa2a383b 100644 --- a/spec/requests/api/v0/users_spec.rb +++ b/spec/requests/api/v0/users_spec.rb @@ -15,6 +15,12 @@ RSpec.describe "Api::V0::Users", type: :request do expect(response).to have_http_status(:not_found) end + it "returns 404 if the user is not registered" do + user.update_column(:registered, false) + get api_user_path(user.id) + expect(response).to have_http_status(:not_found) + end + it "returns 200 if the user username is found" do get api_user_path("by_username"), params: { url: user.username } expect(response).to have_http_status(:ok) diff --git a/spec/requests/internal/invitations_spec.rb b/spec/requests/internal/invitations_spec.rb new file mode 100644 index 000000000..ff91a1ca6 --- /dev/null +++ b/spec/requests/internal/invitations_spec.rb @@ -0,0 +1,33 @@ +require "rails_helper" + +RSpec.describe "/internal/invitations", type: :request do + let(:user) { create(:user) } + let(:admin) { create(:user, :super_admin) } + + before do + sign_in(admin) + end + + describe "GETS /internal/invitations" do + it "renders to appropriate page" do + user.update_column(:registered, false) + get "/internal/invitations" + expect(response.body).to include(user.username) + end + end + + describe "GETS /internal/invitations/new" do + it "renders to appropriate page" do + get "/internal/invitations/new" + expect(response.body).to include("Email:") + end + end + + describe "POST /internal/invitations" do + it "creates new invitation" do + post "/internal/invitations", + params: { user: { email: "hey#{rand(1000)}@email.co", name: "Roger #{rand(1000)}" } } + expect(User.last.registered).to be false + end + end +end diff --git a/spec/requests/registrations_spec.rb b/spec/requests/registrations_spec.rb index b890ad1ee..b59f9e550 100644 --- a/spec/requests/registrations_spec.rb +++ b/spec/requests/registrations_spec.rb @@ -5,10 +5,22 @@ RSpec.describe "Registrations", type: :request do describe "GET /enter" do context "when not logged in" do - it "shows the sign in page" do + it "shows the sign in page (with self-serve auth)" do get "/enter" expect(response.body).to include "Great to have you" end + + it "shows the sign in text" do + get "/enter" + expect(response.body).to include "If you have a password" + end + + it "shows invite-only text if no self-serve" do + SiteConfig.authentication_providers = [] + get "/enter" + expect(response.body).to include "If you have a password" + expect(response.body).not_to include "Sign in by social auth" + end end context "when logged in" do diff --git a/spec/requests/user/user_profile_spec.rb b/spec/requests/user/user_profile_spec.rb index 8b1397f5e..bb0c43b29 100644 --- a/spec/requests/user/user_profile_spec.rb +++ b/spec/requests/user/user_profile_spec.rb @@ -46,6 +46,11 @@ RSpec.describe "UserProfiles", type: :request do expect { get "/#{banishable_user.reload.username}" }.to raise_error(ActiveRecord::RecordNotFound) end + it "raises not found if user not registered" do + user.update_column(:registered, false) + expect { get "/#{user.username}" }.to raise_error(ActiveRecord::RecordNotFound) + end + it "renders noindex meta if banned" do user.add_role(:banned) get "/#{user.username}" diff --git a/spec/services/users/delete_spec.rb b/spec/services/users/delete_spec.rb index 0d188db38..4863d2981 100644 --- a/spec/services/users/delete_spec.rb +++ b/spec/services/users/delete_spec.rb @@ -129,6 +129,8 @@ RSpec.describe Users::Delete, type: :service do next end + next if possible_factory_name == "invited_by" + record = create(possible_factory_name, inverse_of => user) associations.push(record) end diff --git a/vendor/cache/devise_invitable-2.0.2.gem b/vendor/cache/devise_invitable-2.0.2.gem new file mode 100644 index 0000000000000000000000000000000000000000..406f75f3d01217b4985a52790e059b03dfbcd4a3 GIT binary patch literal 46080 zcmeFXLy#s+5U||*5fpG)qRW@a}3N5lVA|G%>T@3H-l;QpuX|6gqqMZtiS zdN%5VfnRIt*>7^9`>xjQJtSTaBpMfQ8|M!Pkxzm(6U4iCuQ9_UyD&H>h{YBsdJMj7 zx=Ty9(qGb9&l{&7_NR_Hk4?%xJm}7tvps~;|7#f?+$CO!vL3^X;sZs|X^i+g;!IVB zLobDZB=@`j0lL&GJ?Ifc+32}27^tFkjNAx1t+$)QM_S@FZq`a1b~q+nT(M^wKn-iFhcs-Kv~U)GZB+Et-+gO4)Gk0S_4 z))^42V?FX7wLRD$_8el>`bQ6YG0ZiF(mPGF_Jfi97J*YWx=$>HYZXLtlY5h+RD5#n zXcz2CQ`}Aiaxm#6QF-yuKfIYRp7a?2>9XXhxz{^!P6?G6D`x})tAxg`uvN)3=337r z&~Qfq;PfQ zAdVO}IX1Xvb59X!6x9kzMw8VB`KHEzu7_CpMHt|#i#8U^(DVPRD)b%eeH2TU{!2#4gl=yFOJql2A2@P|_G`+on% zo3H&xhOm7n?X?rUrDs8tT1LP1D0d!En@JE}ON{Zx7tpHMBFcwy z3<3ozvKNFea)+(V0e6RsXJi^5B!ggX>5*?a!~SV9ClV=&eWCv#GKieN@uMRK#ea|G)7W@^M#ybb89Jxyzzii0c8-4%o+L3h z_hS*nf3OQ4EXmF(9VXTIab_OO;8HkqCdQeC5akHDatcAHbf7bLMSlkmC^mZ;jT=@6+$`@+||v*9?rm_SH-1*Fusv={Fx@&DqdV-{n8-)}!%>s*owDMp9gmnp}EOI7fP{l%XH{nN)c7$TowMyI9bUk~|?t^s*^WT<0H zWn4#VLT+yMO3d3_rb}zEA97Bc5RNbM*r*)gPgzwoEdNlYBy5Bb;1_(1E_lYyXo1G6jAkRmd~2#uEL%j)nh?{r?g0|26)z zurjlD!;{*$nIcVn&Nm8#*V zZ1#?8GKxC5L^pf%zPxn1d-r?#Tj57tW7yoir)#~P&QES9(#FjR*5C?Y#6NxTTb2A= zOjm53JR_R}xA7i|{f8oMn0@9ZcF~9*b>1>r|0zkD5XoF?Yc{C~*5^X9Og#I!Xn;5u zv+&u=_4V`laK%$e?r~>hLrXb(R-rT4r8^a=@Ln3T=GS4HG@0#J^g+wH4ZC)* z{&j1v4*{hRVDx-bvyO!jZk=tWk^M66kUwtb;47AUdZB^(;NAtI#!361`)h4u$gP_p ztov{heGY(XNo)&ibK9FVPrl4bUDx8w+x)DE8t0h#rj+ky#x>1nXtc8+jKb2+#l4Gf z&e)$DecoY>sgUbF5-GS%WRw0aR=_5VloJ0J&qSdk0&ohFj5oe5nsFOZw{yih-8LJ2k|F4` z+Sb2~rQN-;_ujX;2L-7w+``rbFSp6VqTSiv+P&S|$*I%N{;&%TUPcF1252nuo;woF z7u`klxmeD9I~e;TUcbK41839f2Foc#F{pXAE2fIB<-{tQDMkKZP$k?|cAp=wq6b^k zie2-Asd#`eR)bAtfg?l=0DG($tQo)8;in7x4i;!|7TB}@)!Nqgd9(BS+FEq0(5leZ zR6eUZ>AgJFJMd!u6Bk;{*9&Iv}g%&loKmcgcPUzvdn`dRNDvi?HwT_(eJ$_C4Uq&J*>S)GVct6sQH|BQR97K*v0JG^`sL zVC!SCPt^Otv|odHUX4t}f*&DZ;7GXX42CgmZ_X27mYkb2EM@=vXAJF%3@bo(7PLQl za`0!|45*W!;242#kC(t<$v<`=^)n+cE+|#TGVjhO_p8oQQ{>d!WlstN3!jB)bdLQI8s$ zCT(Bd6NW`4hrNY+exQB`xaDJ|#oNB!x1vUWGx}%g#1n>b57TjGb`P9Nq~}bN>U)c5 ze^SVmF20;YJd{R^u5dsBIsM}Ub`XL4%Rh+?0Y`Ai9x+4xpW6s(C11bLxL(F!Lb)H| z@u>$EHjFzt?&|)jA6?)e=uc9-dqlT|xTbO7(DHF2cI63MtV<`d3dahiXWnwSAc~ghNB1eP zen5zuAmc#`38O%G#D)MKulK^J;*_B}o0nY>UPJV2%7JP5I>vgChRxUqhmZlLAA_ct$jmv!en1A$fiNw! zt9U(B3I$Ne**!TJa|fw}9|Qsu-4y-BQmw@s2`UFa2+p_)5{Day)hGRn zR;*JH-(>!#+HN(kRXp?6SI%KU0*FIcG=mzJ_A~Yew@wvB(Jf zcfvr7dEppvn1Ke()E zJb);+xIMYeiWwZvs{$6~3R0{$B%q&OiM4{yT7E%6Qg~D`QEAwas>tQ8G){HnAd0Y( zhgM?1nk`F8xUv7v`zLDPMKdZ|t!HmVjuvcro)L@kyy*8>~2eXS*rK z?FVAaOY##-J)#vdsZzIv3ENbD8x%Yq-=P|fmwkwn$O&QzrO6oIf`++>J{Z-)AJyB` z;YyaJr;)Wim(^0Ska#pK$a2e!SEuaN4}FesUnLX~!0loK+W#&dZNY;|=)=WZ4o1vQ z=0$2BF%0UU5#qv6GOmV5VOf3|Yz5D40!!(dv|j~vjy;=9Y%P%cML5FDW5p=;#Ys7C zIh@3#el|_ts8!vrAAm`Mwaa&c5J;>5)u8F>DR$<_oonvkt{D_iO768aWasYD)-2hf zV`!9DHU>W^-!()=;uX39dSJqIfD(=D?)vF!<8Nd)8kUa(fS|mLkVGJH9U0oyE`NlH zaO9nY;>@DTKxhvf8>=UjnQ4t|@~jji`xzK2TF2(egqQ(`;a~71^RyH+=#g{LeByK3 z9BMow$5uEr(&*-uOaS5LBsG!|Chx%$qBoaQH@g?G{5p64eto*TA9zfI&T~O*1lnk< z`>$}VwsG-)&Yobh_q70h!um$fpIBvQX5i;>n}<(6K3@B4Ei9zr5=j@5Bn~YMaT58{ z++iuO_wE94z*+gP0+O-N?4g~+!sMu(1d?RCNqAhO6oo5EH#o_hKMkZv}O-kgG5DsA|43woS^4MqL#Gt6x0)~O6 ztk~0ctz1j9MW)r~JT7I|Xc?jKy9KUpf8$hEOaekWWkX%Wzo0-9OG129p=*l&kC!hu7QH9R|Y3MQA_l@t<($x!ci^rr@Gcp@GqFBE&qx-VaI1 zFORnip4fLXgia>E9U(8*x3{#iPsxK*gIGVm`v;Ix{{@eqgZKSMqBz6UU<>G316L#U zbQ%0F0>Owhp=%*u8I@}wSd~!Wvw+|Iqfjg&@MV$iele;{5pi#l2oYBi>DAxEt6ycH zlu&3}$Y%vb@g?sXcpc!?6QC3e!I@O``CJD60e7+)4&?W9eLL_3H+*;-e$YXI-! z_bTc7@p^m6CwqxQG$0lBFRqty?(Ku*_|KwNwR?6Mu533^99m<3%RXu-Cn1D3Egb>0`rdT6={``Qo83n z{DpAxBKh&IYN%Nb$#HXs^*rJkyQDi)bCN@%?S5Wt%&rGUSJzAu5JyRw>2*2|qP;5Op67V|lq3;G)!(sa2pHafuUo0Hd62hSlUR2!1kkQ3iBIMEb8i0`W0c zVyKE4+r+?3ku0}Bs=GOWYFxG2Qst+>Q+^-{lH#x#FL-1Ih3^u@+ulK}V{!~C^3O4d z7C)c}pGS<{bbA-=c8vy_TMS>6A?!=nV?UhQDQ+znw**9!*r1wlM-{;*#mI-_XceDI^u<$bUQ!Tack`$P!Qu|;zVpD1KQfy30 z@k>=ze+2jn{Oapq51KAsP`x0h7^L4nG<31vxg`gB|FLarTGc@G2ASCiBxqvoJ9`i`I@|BLbY3yUD z2}=<%7c9V}T^IZvr4BKc2OD%GN}4^EU<;wT5&U5&OOtqSj{W!D`P}IDK6l30tRqW2 zEflNQJ~#I-&dYNQB^^gzzzjR(ym?kf%PY>97`Lj;K4=`zgi6IFazY97X|OywvH`w; z3Ody&Mf9fLNT0>LsRV0EjE_*M&0_dEW8r+(0l%z;#i^M45g@{^ex@nF_2E3X(yrZL zF^;L=>TPXAuv4DIF#kdJU*v3DA7TCeVo-vm9`Qku)PK{?Vhpae2{T_je9i9_l|NvT zm=jTbbk6+(G6gduB|Lqbq%*(yMokEUbM@>8@(-LLWFh!DOgK+*PgW;u;%q+2Pv;?t zghT}7oHBmRB+BCAi2V=14u`e`&qyJARUu-|LhiOf-XeV=v1^<0yAHXk>R?VA@drZ> z-2v1~gkkyY%ms~oJpQK%OLc0wEeR3#Iffo6u#G&8W#NFSAc}fYl_>>GG+N$ydp&T% z)d?67ML9yrkPEx#gY8zp!M~f5O9W;N3#dPF?kVpLM;v)dfeRE7W-ak8Kpk(*{yng$ z&x%F=knyhBLaJ4|5Vpo#Ka^)$8Fi~YuSn9WuHs)U`P7o^aWUZa{Q%7P>QjXxO}+>t zz=AsQKqS2CHS#SYAoAxN7s;x#him0VRFGaL-3l5;i&IaVJ`@-kU=WYsnwhI;H$1WF zU>opOQ(YE7r)cvcamz7LuHrrHbc%oFN-LHTN#FiJFu_zcq8G<2?yc%`F0YRHey_h! zPir_Zvpi;9WH85bXaHQl12GOGUx#E}_m16O#P>()u&pQMpNG>IXF6b!y=|1MwDy~=Gpz&wpt0j$5Wz|Xw9U@Ao z?qOo-!zq{_Xf0a4_%qz*QpU=>&MNwSL}}xG>7h%M31}AKO!a0Hu*gfE5poJBLIKc; z0v%WdjqqwrBl(ksmq=W^9)kbax0#xT^&^)ad5FTtGqUO(PzNsJnjR2I$1_e zOMMcobk5(ok2?`H5?DIGHC&XBey0jscAvq72pE4CLsl~D8uHU`(trX?i!K1Z8|e#Q zBCD|pFUBqoCRO-SR%et!%7q7gqJ>5p$dW{w1PYM`b&pi4+jIs=ZlhSto{ewY>?AEL z2dKQZg&1+_odGZ=60bezvrsPpPe9V7F(E|R7My%E*9@puvQvXq7@dqMPZ%OA>>2qDHBC;o|d#Bp|gh|4u7+Nzw2!0 z6cA?UiiM}*MdPRUy#~RK>1C+bL{NT0AG9uW9RZdxppQO9YeP{*2oLJhP-HHBRoBO? z1O#H)Rh5yppd17B9I53O9*&h&2aV{OBRAj(U>JUX^k{ob;a9$dsbL>5iZqXXr{6(GR>MEwBe^3s_2B4BwumQik`O_9A*e!#R(f1n_U-4~Yq8@KnOPgs5E~VkC*iwAO zSG2TfZUA--0pn8(3nQ?$$OqalX4rVcc-~m*kkF8FuQZIXNl6{oS+Zy!f8?E5Rw14; z-vXSDfx|nTOZp?`I!=O}JDg3z1typaC7kZ~?0w8|up$Vj9II?sx!KGeT%AL`@XoqRx`rsTRb|;Ph&oMH(AjgpvR-Op& zb`c7+-3Waa_n|}ie9-9BUrBQz&v-$kMyk-pym4Ldw8Lb`rq?f34(9>&v$#o|UKoNT zSfE4#4V9FD?y<_c^TjG2{-tE-UjuX<{)}1FS7}Vgd(ZrQzfkBNG{wX6lK}#7SRq=U8x=C&#LabF7sQS1 z4Aj|*-}rG4YwXh&DmD7ayk?I`3%&(-*L?}k^v1TKrU;MI1!;`Yyo4Jv_Dt`@T<0>I zrwhsH;Or346UxzFp=evs z`?>5ajnwRhk1i5mEZRC0P@ftzH~hshdkdD+_gyf9QV}qVZVCr<@j~**N8fok9Z5r+ zeCV2BN6P1P%+bdSL%ir%6`eb|80Xp%ywizo5ycZOMH$RTcE;nz$lDG*pe~#(mC1tQJST+s-Cn@S7z@Pa$io=Dm!Vnb|gu&WdNo+@M~ z!+m%$S#ikgmO_KEpG zN2v-pajRI}v-)sR;Eth6d0kW-DQJcekDf(E2K@q+xhOviG;;+1Z7Q0o8B0!-7T0Kb+K%5P5DnB1C5-4Jb*hW6i)R=2c+=YoEv~)<-@AczNtCF&^O^-@SvD zN@PkEhjSD{PN7)~5uQX(Xlq4D?U@y8gnCv9nxw1t zlEku4EB9ZKri!IiPqh?THA)wq-+rRL2lPAFsn@xr67T|ZJ+{HeP9Uu~cnhH+)PUYz zxe5m+lxAWL@uuHFES6Vg4~flft(kGTx7|-1lD$p6>X8aiHMdfsR01E<*Po4XaP=|lr4={{9I={aTEQHDVL}fNPfKd8$r8^1ToWMx4Yaumi{^*od9%U%E&Y) zo(iEy|M4`Q8Yo*;0gEi6A8Ik7`QQ%sC3poKd{iGmi{i-f*9KY`ziEK~9}U%dZIfHC zE{Ud-@O5aBK`qG9!W?lJ!BBTp5KJrbo4J$jZ*7od3yJ&ZFLWJg2=tAKt`O@s^@Th$ z)Y15_qx&<}-lYgf&J~M!aUm(hu0o@VfBQt(hB@X+srd;~@YPrV(Ri7Asmdlw^R_|F zW7hl;kSK0XI+l8`=bFZn%=DqTpiNZ%lrv)2CQO7gS_~UfYF*qt&C`-w0-O!2cWrqo zxF*w-y`HLgsagVH%tZuc#=yTJ$GQODL5lY$@&$6WLHjfs?8R4sEhb7~i1U znzLQd(RHBJVPj9)`yZPo?)uTyu{OP=fGoXg9yaiTTNM>8m*HcUQ3x2<#xM|Vq8K_q z@Q@s9VMMZwRY(&xH3Be_RN{BiV!E-%!^btlF3i*^A8O0`)tEBuksB5^rp%PRO6czO zm-OMDc9b&U??yR!VKwszT>X=E96!T$X|>c#u0uYWW(G>pFuK=_03I&ukGoHcrmQx$_N^Gx%JB|uRxcGwx)xT6E2xuZ;EOAk5C3galu}1~ za1)g6l0^VfjwBY|x$GYi*6>$^YnG*!VVTfXioBM?SaMr@`FS5A!?1m2dD{| zSmI6H^SC!y9s^$_FhDx1tP(f}#q9tjAZ+(mf$^s&#SRL`DCo*0=c~G*j;$bJ7n~x4 zr=;45HY3+9Hm))ewb=oQ7F=wT8uwZ8__`(VTX>Sl;34)Uc(s#urC2vpV>1;S{C!JP z^;{CWE?^-irGDmFPL;tHcL24B3rt_|Q2x5P;UjgUfVn7X^1Pq=$VpY3BdA=V*21!x zd{!zuT*3`8Rf}(QrngTG28)8T zEN?qi=Vsm!d)Om}!kjHQT69EMjKk@T^yD4;hnRv@I-A$DoAk7%m;EBam`UHC_ib3t z(_}HDVnT~he0#q7 znS8YNX|xA{*^-+5Hm<$ClTg{sb15E=g7ehx$bBpF&q8I~=~r+L)$BOuwxz&5w3MZC8DwW;0n0 z50Zti%V@hq18^*@rV&(JOoo4V{rCwCB%5Hi4we`hCkM_X(uu5d=``j!$wzM{p00G5 zMr7u5Ay~K?*P^`NViRP4Y`l$nyM2k#avH(}X?gIw@1x>9{liG;>wtt;6fi;1I_1|K z^-*)wx|1v3bw8f4B~RUPr?1JIaiK(-I+5jJ!r+LJVWbV=l>%oLFU2ka_nr0qw$|V$ z;~F04!2iW-Jj}Z2h-u{)GINOX@J*LqAEW1S{!9xSm6$eO1CN8)R`Ojq&eul2jo}_6 z>2b!!<~=`d*xt}V@yttZ@CyXr-gs6(OGdSIHmNE)Rf{ZY>t0D@yWAy^`&WgsE+{~Z zZ2SfEyg#lO(=kn?Lw&GZalwuko>=G{u&LeXS|dQNp5{B7tdae!PP5E_+W<40d_Rtg z*ZEHwD$bZwd{ilRqk+qje;B=_ADDnbyxjHgRrxc&{VdpVlmzb%ArEe&8zaBQX?k1f zq9Q?#zf5^+u3Gy9j`v&pm1n#LKgh*^T48w$XV)516sub36p9%9RTnK;7zy!h|0>w6K6^!LU1IAbR2&mdp=Ig{Snt} zVKFTqHbFetbDxfRdgcRPNrR!k4QPg&Vh&f|Zuo|4p9sL5s6-LVt;K^I_@{lBY^Gb4 z#b_|v>Xk}jMT0b5`)hWluDOek!RZe>fNPexNAc4nQR@@WdbBg zdmI~t&tIxkft>gl8tX{0dYv*1Lu1dr@r=ZOk6p@(8}pWD1jRYY-supR%~2aNORdjW znhb0O06+HRy`bq+9}*FF&CYFI6C?TV!|DFYKbC86jL7BUV(%^?oi=J}VJQ@ATht^S zkQuF7LNj)E!i5%BpoX3gDXPdGk;wD9H2I^L*98xRa=xnwdoIy2(TgdbBywZ;1B5P= z0FQ+x=02*r!s{`!Znv*rAa^g|FG3)^dkCWj$TI$qxqfeJwigYRFYuyX_-L1T&Hap? zFn@hUqp3i_x5r)QJoqKA*0X2FB0y{?#-yI$3aNt-L#KWXMsF`zDx*rsG_3W@E4IEL3Sd5ys^_VH5+Q+=BIyQ!Z zH!7GUG}Q=f`=^)daK$pZRK9flOz=peSW;Of|GJ$L7zQ}gBRwfYg8tzl;I7n)mVCeH z7F$+x1tSG*bx?~>N>CU#LfCL2Qw0dR{&1WQqdkdW4f7CxK6$u6)pE-Uh}Xg^CclCc zds^RX8y;Fb7JBgGV&zq_99>fV*@KaGsAn!?1a+{+ zvrH@A8wPO`|Gr?iRl!O<6P>u8kP|ndB@D`pDDhlveNn!f>r9!FR_l6?z(}p0rx+u% zdZ^CxNJwJnm@EK?hRz}lKxN-Mi2^m}OJ0@Nv(ejZY7mhA64`o|BbBI%T2ug>=e~ME zx*2e*#t`@yVrL{w4)Eqs(v>t=bv@DyX!~YRMPYR@&)2*JE@dMDl$$Ud=Npsp^I(Uj z-1x)E0;q#tjfG6lrwB3nw_5C=rXaOqw=JuD*u(ZLFkqCvJbXB%4q22jjZKH}2Veia z4NgwhbR z^w6s>YU?|WI5l3HvdtLIl)15z^+}}!O8cYqMM2{a$J)jO03I9wvH3phV<3`Pjharj z;1&(RXB+XPieQbtpi4}W9E8p^HX zn^Hm+`^qKX6~6%{c8*>^Rlq$0+Y+R6n)!b(RNrr^>1mn-m(uKI&EM~ta^_w<&*64N zW1erqo9-IXjji{aM6SkPErx?FE*7OxdhW&C_w?IKk$}{n1Be#J5$wfT69Q-#tXM%T zLGF3uT;*crq@&KKwzmfH^lyw9FG$%`CmS&uWhQM8X7E0AILl8@9{p=??QXW5AN}p@ zY;EjzPJmzd{$I(fX;#YfGz!9SnI3R4tHl-_`Exk-Anu9b|+I^LE-7fZDoJz54$p|3f)+4juZW> zZ2NH5t1!=?8)3GXHF4euf(mXJ@q9d`ak9Ehb|=9TtA~V%sBtDifc7~ZM5 zDLYld_Drgm{5HuA3SNL!_T|sl`|OQ!SIYW1#Ij5gs0&3hA(h-{wR$i8wogEM;L9z@@S(Thv9)#jXsG>Pgx0<_R&R`LHM{F4R+n64h#yE=Pv$mZ72t-mu!q6idXNHHt{YGJ|YU7jo;p1&q(hPL=vF zbysO?g#koOgQ-fp|E_rmkNQt5dFQQdJ)#NGp-z*_agd2+vD7^nFT}hrZM@X4UXJZE z5If4apm?Pw`!2lD8LB)(HX;=b-S=HdrcZx--u`^_+LedCx4{iX1{g^Cp?es40}wJ2 zVMB?T{KC^bOKYsHMQd`S+y*q>^HaFnSygLqg!Eu1Q^U+s=8FV~(ly#+Q|et9SH8;S zp6gPZeW4h|-cIP*T+Vr+%p0EWd>>j<&_ZNDE_dF5Z6BGlMZgC1PF+jxu2@FP*dGfM zlhrCw$(zH3}r_*I%C(?VyyXMS(3saU1n%O z=zxw;lH-z7TKQLCT)@efBkDoA#qQjkm6l*o%$m){H|#xa^Jg$zfDUJYfd+VSCM5m~ zf*oj04Ro1}x`!bLCRNR{#QoUYUU@wE|Ch)VqVoa7`MJrn7iWZAvi6oTj!eo zGWVQop9q)9|K&U~NW~ebb6cq)9kx^Z@v{1+APGsznBG6(TWNCMc+HJcxO+ zvMn?4^l5z{{rmbA5A=NU_jO-w1KQcx-tH^_nG}Jrpn`nwqhsS)r9inve%au0-r1pF z5%TQZr;+!M(2njjyff&mQsPl>dL%UEe=*3@-R%R64=NaBm}Ot{a+FtTeU$3N5Kkt6 zn^`;GyE{7_HNX$g8(?x>p9Hxe(C+wsv`DG5@Ompw7tcMbdqDDlwv-?mHP_3QVf zODicXjCk$8KAB(>ehOM;tlW@wwrXMnR9_mjWirv}2^?}lM=07R7-Kl1Pz0qxt?1wP zKBuV=Au`DvG>Is}AmJh@Hj0{)bhFDS)mBgs>$-k~6{U8q&o}fhw(Gy@XZHYv7pbEk zdiVa$XF|OuT=Kxx)i&8Dp!yF`?g?lfDdZf^CI9TXWIca1eExh3(BupG0H}VXiU0$F zK}uR&{Lu5jYnhL}j;8Z@U`$SpciR)N>C4fykRZ)tB11kWUpab0cK}2rgUN{28Ek_m z&SeG}2QlU7v=kpyk{+qNQpLSQ38e-%LS9dc@xr5og^AYHEHd4MBQsV`ufuDofh!fQ ztaGfs(mw?cDf)rmRpjJZ5j0+*mxTW??YrSQ zp{gjAf?A9Cww;4%hP+;2vE7wOG*eHQo#FLXy*2?^my;!hO=D)QrYr;^4DL?|uxfrr zTR)>VRCq;2Aw;II|OIWY3w670Q3$0fIsowuYFPdrFeVsSRF4Dw z@0;BHQE*~vnoULmIPE<=;M06!qz+nuMlXsfW4?r-Xw${beXvo!*}p4DhyxX{3WJBT1h} zr2A>3+boNNtn-OmKprjy6-8d&O-u8)?4nhjRtN*Jy@YVCj)T8YbWQmw+%_Uhv zv>jH0uF6YF}#Qoo%#r31gjA*^GzD-~|n7Jn4&;$)lJE zh}<2@y&!uN4s@%qoMucuhRK_#e}Y8kNcBKM>3laau)EpSxta!r_5Qlq!soUu&t-db zv^6#0(U@!(kf?!DF`ieC{%eLv#zX3Y-eZ8o-OK3Yf?;RPEQBvxfUNy)1%Bkk#R4B9 zem>iP)k^5X!#RPyFFE&NaOm|T#zCB#akXlHHoDk5i9>#6(FK;O0<}%NanMUzArj_b zborM@D@)*X$fWHYIC?azH*jw0WLPFQOiIDrR>82NS8Dln>r7i*XLgv>R?PzcuG8k% zvy-oK+Id4?rH1@{NcEP;_T7nKQEGmZ-(C)DLRhw zCEE?wW`%%=XT-gsr1Mz}m{fn)he&Nvh)WDw?hu!GL&>5$_hH9>p>W=4TIj*0>^L=Z zIpWVF@dWiBYapj|8n#Q=$n}u-shAQTpyLg5KSrtrB8>w{K#zHO13iEIUwz*FolRbM zK5D*z$45Z@S=0n?nN)F{XT88^tW#O<@s5si7Ipm+IjU?@Ecn#UVIfS}!CCuX;gFC{ zt8LXw7$psthg+gW>kO49xtFMfWNj-KHQe&*yL^nXM7P!P1FQ0Z)uG>Y_diwjM|cb1 zGmpTv?WZq)F1NNe`)hv#bs&{LcQfhXslFfZLnnujEa@kjF1?)D+mwO2$y4~BI?0~G ze@T_tgn)<_9S7l!OXXJ+gyAwxj!A-cClC4^d5U2gW-%I*h_(ecf{OP3J6~B979qU= zzy*9=5nUBEfAb*@o!i9klja#zDWZtyN*JN_5_1$gd$gxhN~8slN;n;)(&W*91^1d% zV)f=dDscO{=whsJ4b8s*64}lt>i#&$b%$I!6!aI#puQS5&2(VoY8M6-5sbTV1!OjP zD13Cz#jWkl)>bc0BHzbBbiq#sABfw@IF^LKwwA8TMc{PJ=G*P;!zm8pimCdaT|;CK zoVBSU%R@9qJUm)d0@9h*jF#cbyVyUTPk)SCiw21*h6LRJ$dS$5frR-QEZ}kLcDkp9 zUkOJ-Rvw>nU%MYGJ*>bsM{i&s+>c_W#qnlN_i(by%%nUn86A_2k4$E&!K~`3OJ~o^&Hfg6>I*dc zCzfGP-~h(`cVoq@;Q5;G=8gmruYJ*Nm4^zl~B2gs^QdcM41*%hfFm z%5m9Muj#?$(c<0kkqf7E$q_lS49Gc9#)jx@)h)t*ZMeptclJX~m4(zPDK-h$A)r=G z7w;my<9^xgWq%Z=Bb;G4EHklfnGK?y#0ydIK;%{4Eh;u!F8iBCAZy8csAAloAH5~$ zAP0FL<3=4G2lJKC1_NlZp2)V2DOtd;q`g{zUZVN8^D^=u@b}-$MXvhZ2mV^49xvxQZ}mx&r+g0; zjwJfx?|QRU`vN(=0fY9pyqP04J=}Pp519vuOYW~8FO_Y_?3{rf2d}4Y8bis+xG|z4 zCB;jMZTau-y|d%}sMDUErf=Z$ffWB58s7`e=AUG7dtHzTCQ!viP&K`_eT@$k|3!m| z3dfm~o*OUs9?wt$$k`p2R$pA+gH8L%LCz@{&KP0^2QxEefuXeIRJBI^*3U0$+&l!bcK7UqTDhNj8FRe>JV6N^_=!n+3@4@<7La^D zRTnCj1uy#!uQh=3lPgI5#LVm3YIE|4)x9(w)m5kkLflo^qz<~N64aZBD$o_%Yg8{p z=1Yhn`%W9AIvv2a%iH8~D)KcUT%#Gn;b%3-DX@ZNt=R1*!sgYeEQu3F(3WEsQfsYF zs9TY&o|UJSixe3`19dIqzzC-JuU7W7&N}U()9*L0ds@^4CRr*t=XH z{%0*zQcFOn7As+~k7|_&lv9TYgH(L5624h3^As!IYChp#95w{+E-z=Z8O2>)^JnO0 zkCbSW_AIPzEtc}D!lx&+vW@Z6jh#vloSu{xUbAbHZocdi#xf_HvIl6jWj?&7CN0h^ zIoc5}u2w{V8K0T2+nVGC1>@eOb%vQm63h(m z*;UIV+tK{yT8Zj5X(sCMV4jQ%;;14;&SFv*;f{NG(PAB3yo7>5(4UoQA*}P@-B+eU zp8-5`s$~Wul<(f5-`rTYw~k@U1^Xzm<+$d>dLk*fS4RtxJ94bqBqz8;;@X%C4EZT* z+2I92t(cN$85fgu^2UI$`m{>2UIrrdJ@jg`rR_H4*gphen4K*YJm%6P(D_4>l_gLG z6F0;isa;?f=m@1FpH&Gf$Vm1aG1?*eP^y`%?JljuGb6F0_4kt;u@Jch>M`b+9#jbO zSU#MSKFg227ZT+Xlp?u@(^~+;6A(j3D+xJg8YMSNU<9LkWx~`Tf?|`Ie$(IK)R>V< zb2v!QtA=C~7oic>85WZ(H6o+=1*7b30_A7kClcrtaP>$qJ;|_Gab+Ia`){v!_~T5m z<6M!f=98qGo1=371atdB1n#PW@7xEnO?Lb0q^z%kyY@9TO=JQB+6{*WxA-L*y{B=# zVn6$$`RGDD8-RsSx2AlAFX#uzWp8m}hbU#w@ETIuNy?x+a3XHPK-NC;r+>IUBIXI+ zsDe@ks9$>;dZ~C^MN)@AT3%M%99x*l^G9`i|JT>*=$|9nm)b2!*7v~G8C-dQUOgvF?F zkSfQ({DfI?(lXagB0474wZ~zeYQa1SUBntU?mp4Y9cICo`n}YRVz^uSM|mj2ZCnlB zgZ!(inWwR9qPwEOsNXXJEAbm+Lcr!zCw)$T(p`|%QY!11`y?i^NaHl7InnVN7i4Hv zZWP_b0R&OqHS5KgcgUeKC1$C_X(>?wY^Q`VY>G-8o2cC4uCT&e9WPx{Pt^t5I% z!^arm&YMUh{YuNC_FyCea~Z)mroifC*bp|x15Ebf3B#%WtLyR8q#((=HqR#|9u2Rh z5k|Vn8BI0Q4HI0tjCj6_UZKj00?mCKf)%KZ=up4^vQ@+fS3F-zsv>&*Lsyjnl0QbO8j%WP5@Tr+u{(Vy@npIBkpK4r zm%ixtk1~rIG9`9}wwRRtnU}XlnmvEB6^h=Ol5C%k0rtP8rNnxyLZcEXIIX``Hv+YL zT>lmjn762djzI;(ambQ#zlu?_GGl;;}Ne0A7LhH7}dnM8(6Eep11Au1CQpOOFw5^&!s0wlcX);R%&zg5=MO+)fV?%L}G& z6}c^ks=R8vZu_uW3oS2L@uu3-zI|d`zjbh^0ULL#3w?`T z_T2ac5@3O{x4@=gAfu)rg56R`?4+@!t`NLW=47}vOAL#^CnaVT@iDTI$woHDxW@og z5=>{sPgN!fL zWOa^VwmSk~jmYhabG$-ON9TvbPZf8p$&9KP?jzGx*BCN%3~#HEKqJsf&dHq^>Ynft zMM|iscCuBl5LColJr~J_6bRn15VmjP4yas>x>i{u-0fMTYO%IGzcD^MIV(m< zq3J0+N#5>CXV{08Bl1RGzd5#t_H>M;e6DDT*rWT@C)r6Q@#0G6%&)h6V08RR+o5s0 zY*{hFZ*CoPxJQM)+iENE*>ur*DE;K)w?^31>2HHd+rT(`@`ctt7XV>bGCIoi9L)4r zic80i){9ZGuxb?yTl?qE#}2F>kZqZYtaj?HD&<>S=uX6!?wxSZpiUz6mER>8v$@D1 zYn9E2Qx<$_CWhp#@A&Ix$c;6Y1ByEpb^EYew4xo@|=jj}VQkZ)Xxw9u{(p}>QV^5O1? z;0PsTta1#$#sX-_Mdz>fIA3}N#{2%dFt~{&%!R@dX>Uj&)n2SanQd%eFTu`Y3v?L3 z+72atbg79v-}2@ATJH5%nAna|Td?~A*;N;YT%D!dAe5J`LV3%@N=>QyW9edImSmb) zC-t^wDX`*)GZF=vTmBYY5=46(r4y0OFvqsc&KymrBWcI%dMl#Lz7*dKkIXym!A|pz z$7tpl#FHpX(!3{rmeaYsjx!)8O3%b!1n}3$WJNg^XBO}ZCIz!|BEKA;q$773KLX1& z##$}2$t<5mBVN7q8)TwQ1!_)WfsyH1G91RvPRN0}QoIzR8SQV}X~MeXGku!DgD`{# zhMuq2>m9MVejH>d>?C5hIXpgF{trK^!QmG@l)`5(OD&LI|?Hcr2w&p3fcRk(3{}>4^+?jg!!L0*SEL)`w#!Qz5icB{;zVm zrG_7Gu9!89l+>+C;Uwk?0#|}Jl(e##r@80y=f2ZTWh`51oKn+vbrw>GHC%W%($$kD zZuu_YDi<2vp1dnteY=nPWNut1d>N|)?&VD~akL%C#R>e<`i{I)dnKK?(7AZ}%CppR z5-Zt_+Tx+#q0~n7#OveI%=J-*uX~2S?irU|OX(I+#vHr4eRo@~cW9+!x!sMiPw8t~ z?mj!h$Jrq|Dy{}_QRRGk%V_^``G2Yz zcXL%B=kWg<_qP1{kGs(R_Wl2&??2aQ*@zAwSJI!&+W8D$8Pm7YYld*{Me3Fn4Fa*k(rI&SsWHAQct3mkFms4~H@4qYp@VWB8uLttKZ{O;FzAXQzk~i*& z=4N-jyV>lXW^w$V_*NwRuap0@t4zKoQ&F-jSBWrKPBqJrhI#eB*6*!*`rjS=`S$*Q z&iwBr^H-vR$>?rp$1`f;zv+I=dlS>(5f$C0lw;5IcJl2>9U0c^6F(;Ccl2%=Q38PC zyw&V>*U%*r^EzP7&wl^M!>0%Re%lSDonER);&F18xoS%NO$WrDKgOpVD6WtFXGesu?M%r3hY^(pQL;*Q@V?4L^1RKO8=X zAC6ZVw^sLC`d^~|`hKn1*W$+Ye>*!K|F^ldy|Hsk|3AlHkW%jthp*E6-|PuP*t!Gw zFVdfU^Poh(pDFCXJ|zkUJ!GBM*_2-g&fCJSragBa93I0i3KeuwQ1H;?tdu#{j`=LQ zC?=97O;lv7o!8QJG^lJ=PCHt3RVa8UwXJTH#WKDTfPh2#G6pyQ4~umzG2 zFO|kvESw+tckz09^uMGExCs$pp8OBezR&;f+}3~lI{ZJDW$Yz`X>;_?;c^cxwx0B0 zlpe2CPpWBH;d~&P@D8cFg#=>=f(oaClTo)6C&vjpx?+Enr#eX9yh235?B}ZKrn&hy z4B0PXIqD4UItVCoh&`4<{+%Y1Rx_GTK|W}*t0YPatM5Blw^5ehXPwAP*?YC!FOxxk z)63ErqjV5q)wx9WzeMY;?XTbTvq15zs=^E^0j!BE?<0Ac^iO?BN18mqn)Q>qd#HD? zWqx!2AGRt6bixrwLq)t4y}}#~)m_~nLaTdsjHaO05%8ezrWA_1_Rrt)Rk64Cz^y(< z6xNXG-zIBqH-fN~i{1C^DiO$v)plf@R8Oo>4YBY_tW3neO;*VF;Qsx)k6%20b@2RE zqo$+H=dNC{aNg=CD3rH)6NT|sl|kIebF|sLh~qa@BWYDUpN&so>FKFc%pScRD04KW z5#gckVvujy=Q`)W=z=^iC`E}(2j=Di$~$Jz1}D;ht2vucTL-{-pt$-V9nHo%vndQT zZ%ymgbj`xgz@2kOKZ0W2Kojx68HvZSJ=k;Z{*QDB$3+NnacO(vy^!S=*M9qL z|Mfo~9slJK{r7Lb{b!BJ6D{A350}g2VJ^at432||aqBUcSY_d{F8-^~1q29EvKmTr zhZ)#8tGOyWkm$5M!x)B@x#aV;xFatPU9To$K+VLD;H*gRj?yy_)FSyF#&&s%+Yu){NL8b?#8YE$7lJwf4=#E^sr)OXcaG$3=N+v6}epz-#oa#2CW+nZ0})%4uN2| zCNHpJmcff_=eGG8abX|BL`pSe-@EjFCI&l_qHES?Ocrtd7c4s@8 z6$Nzd<+Bq~N*Arl1K}op?g0XhJ>C6p@S5XSH>3Yu?EZsvSbhEH-~Ww!{{7#%ckBQ0 z72JP1*DrPbF#*C7_n&^&Q+F#Ot4v}y`usuMRoA+fBDmL{X^ZN}7msGgt9SRCo4)jT z3O9M@m%0CMllUUPmiBjk{O|VWuCM>y+Pc;MeqR5t5)$}FZ(P8ZRTe8u{yPr1xYAgK z3iWy_*1~RxgZ&JI)TlttljI>q9H(pW%QHi3g7T&c1I zNQ+xCjPERoabr93ssh=T1f;VItd8cJHsy=i4FS=ft|D}Gt%`$w^_6WG1j+6foXNVQTY=+PZlWnTSa|h$J zBKya~H5#8)fS4|eVuZYI}j5P~iM?JMyqP>jc47H0k>xd9Xl-@95AlYR`5bOZ}+ zpC=V}?D-_`ZN7O}8rbq)v5*q;PJytQvlNtQ$EE|2uAF#)UM-)W9- zNiy!C#*DHxDSa3+dQ`(SDs<){a&Dt!Ik^0sy;yXb##Qu;%8S|Qsary&D}F#DlD>43 zWyZmk%7u)*|Hjd!tmH46r)6hRM_mZ@Vmh)RbR5in*+6H_guExuKVrE{P-^{G9G= zMVzU~vRT)!8@Ol3kImd-UG}1s+LjiKZdB?=iIO}ibfP6)pd)N}K~^y$$#|g4p&FoD z6LF}Tr`26dXr$&ZR4eJHfOVf0T5%EJ(wWG4Okv(ulkYGqbXgDGl{Lo`K+_!9wx*Tp zjSRRtl@BM^L<=t>PtSN_I&d*HdU~R{q5j?Mh?69RZ@NwKQG9e2=gR|KZX8g8-ApG# zUJDjHQRRQhc{jfXAUa~*f`x`Z>;rN6P()*_=9r$OMbmGC#XRrScRZhsqe)Fi4INDq zWs`Z;;}*cKDui4V8Qn(85i+!lU{g%zUEkQLbI#>IL+yi?!XtQ-baMBaM2|Tt;GpUW z+1GIy-Ni0<8+}}`M4G`Y!?JGlmdjg9uW#tex=Xm;ot>oD02wW@_QFyF=e7pAxL4Kd zQo5(~`=XZ=p*+)X-~8L@xAm{4-^x^|be6a9{jysJFIQpJoKiA6M^`8;(AQ{r4kj9o z6PZzZ30}g2#6AwN=zCIi!DtmrK6iAj{Ksw4)hti-7djNhosQD8Gm6o%wVpt+D5~JI zpVo~--9iCQ;|*N?*81jWI}M+^wBCSg?{#HU{d=Wyubb=Z0c}|I$~%ifBGA_XPDwSc zb#{Sra}LIifdxm2i6MgTL8R(n=jZ8cG$g!bfelMn<7mEq9$K8k82v0tF!bWR18f4- zf2l!|^govE*K_*1zVUAX|G&*G-~PX`dF%iCRp@^(E@cMXZf%* zCJhF>HTnm)wEI?Dsh?bjBB^B3S;}qqf|6~rq`C6F&m8|bOzE0TSWQqFyFQ-Qtyrn0 z*+mKy#SYMA%UF)GnN_f~;d1=Sf9w(akFlwQ|KMr#UES8WRkLCYJ6wwg3XNg4at5LZ zd0*Ggo06sB$R(^|U*emKRtoEw&VXKHmD#K9(;DJ>o3#g8aL1^pEaE8B361c`e<`S1 z!nmJ_CrS3&pv2m@O-qC3)SMwxl{Z`-SkonC%Y|LZt@&=Pm45l(CMS?N4$ z|FgO4`~Pp;!<5js@Be4``#+w(*niUhdH>Z<-Pt5RPfm-LNpci^&^j2X+Jtv)4)0LS zCp52?aZH<@%zJGamvkW#=fywTKYaSKzyI^kv`3W^#eTw9fLPN~`|vcZc!^|L!l0kU znGz?I`#LJ=?g_IOBmIKxhH*mtcKvHG6szyug?z+<+H%-`H8_2|8V36S$@bwO{4aXS zfw}yP%-R(CLB9k1$dp=mp3>z8{CGCJMVrS!f2Upq2@P$dHoeN(0$q4l?W+Xn`9Q9Q z(cS6496?mBmb2e{lf7?ocA5_uDV`PW?zgJ6NGN;HZ$LYLn|y57jrf73N^791aPHqg zX#NcYx__H|XfFyR@j~)7mp1_>Gul-Eh0K;LCabuu%Y{J1u>&~7>iZ`Le?NSA(0_6C z?4yJOr7ALCi@iP3b28Vu>{QJGSm~XA8@}Jz{@Aa|@@#R7yaTxs*jsrqW9=pF=tDp0 zIXYCTOi%wC1es;bg762lv_l4P-^iqWcP29bH^k4g#HoDwz3`Zcx-op zyOk938?s9E{rF_xOwtpt&M}jkvSq#QaRJ44SzU?|YBFD$GJHw*Kurs#(OKL*kMjN? z&mXp$(^ZUS9-!5@h~E{ki+^CemYq!~%H?v0z1*vqdjo z5~pXFX6f4mYokf$Njl9t&9Z~DG;?Xh;rd@b#)#I*rR7kQX31GHanfb+ylstBwq0^Q zYLT`+fUZ8Thk2C~vu6K#j_Fo{|6R%d74QE|=>qEC|IN*v4L|;W zeS7mZ|Nrmo{a^6q4=7O|N7KubD2piVT+z?t9LomecU--zmyxbD%SM)ms81<7;?E}_ zR=sIkgcW))FL0WSM(A$Xq@G02f!?8WXpg^pM5vD>p4wkhz7JsYX~;gMoqj089TWIM zrtxULM7I&a$q4fQCyKrH*HaD6z&7G`Bm3WC(Lt>IT8 zuQmziFqvKqX@Op<*A@W~^e{0IE z@aJZ`@%3qD>JFmf7=WYGXEg7g+}zpN%(z%o6&838e^z6Jx!n}g;rDG0wd5{;{r7Rs z+qS0H*!^~x#`)y8Ch*NDpshI%=AC+v?FG(u7!^@~X_LwDzvRg^KB1ykF9y}Lh@$xn zu63Rx({L2VqnO6He4_hpZ}R|G1#HI4OQ>8uUQfeTk&% zfLIu8sgX)4ETV)i)*G6`h+C>m@{-Gkh=b)+2b-IN3U-Wo& zt%@&lzKb%=H-FZHQ5X|WDkf46{Lw37CB1TPLY@R<1@M^$MA z1SZvf_}RQ@Jy9R}=OBh(7bX{@__SC~yGF;I7qb&g#NbsAYQr9iSvJXA8|}ti?UTQ7 zRe)&26;$uksH(zIv0U(_OEO~9a1kLDV30fblBz)WT7mRCY%!dM0Tvw>V7rFnX+Eqz z^hp{#_r;aIpZ@IEuXgrMgzt5a|60spNs4}+I%UQ(M1={)KelTQDhZVGlBXpz&f!V@ z5(46qhqbl9VR_W(b!oWcG7z^q;WM6hpkK9~ezD6b7rI#u0hir2w{RE({3#8ywLCt3 zs*+Y@m*m5u?lJrM2r0*1oF{{GtHjqWCVcIf@CU_sE3b}WJ&<1`B&P}0JB76o)5#56 zvl>=>`LJJ^g2M9w2*h&RLxkUvj?>1?r*Jhxl6syg-g{y5r!v=4uLX>>+-`9zEw84U zk*r)T|DYo2`CRn@(2wQ>+=%_nt?iwA)_0DZfoaeV8aim?Mx{|d45qKzQ0$+8Ia3@f zy$0q6PuZ*6XQ@qfF!xAosY&mVj7E#R(? z?rRrOHk2v|C%+4SZwEV5NtDo2&3T_beAt64b(Tz2#y#sL=5BXdI!M@5<)wF-yaqZZ zgjfyJ1YjNMcdkoGRjzAm1Et=AuzzX zpjTruvUo6rRuGs@4K?V-Q&ZtH{H8mb4C5>xa3yJ__@eA8D@ayX-#Y_*bY6YwNu$(q zE2YK+_>~F|pz3Zq1&!0wNeSaB_MTFmSIe_+Rs5K|^BI^viAKVrWJ39o+SxiU;i1E_ z%;?mEjZ>~uErSJ~CBxw;zTm8%JT7J5w4(JLh6#O89p%jHt@Vu#<_kJD8F?Ss)K#vj z%T<}%9lN0H%E7xSKijfqGncr5A+ZKH4B8QAz!9##uNm>9jdc&2~#t%_(m9~@;a!8eK?LRyy2`zB34*83UYD;%p- zPv($&AXYpZ(n{MMNk3hbJtfGY$bZ&nkp$Qj6>;rN>A#C&gfhPYAXIP~?V#jx;U#31 z%XF=NDG+WHO)jUgr&qy(>9bLhOrxyuha~?WvGz~h;J3ACR3NW~k<@<`C-5c?qwI2T z<@G;Tj{maqSM=h^VAQ=|Uj2CYyS0vxu%y{3FKbD|iPEOy?qzn$Cca>ul-GuvIF_@} zOYMyfC?q7NP^k&FQqM9^q;*CvBb1r}hp3oic7lQJJjvn4cb3Mb0N$!!WYHuaNi}~j zo^(V{R(t@wd#?)r$qyBoc;v5YD)>Y%E+!WkT33yd6vF>$+lI5da2L5hIdm0ns+o~X z12JF#W1L)aX4Ox(UAB^2d&bMV_;&r)i|zCz9cuL>IipIw<7rxPLmVG-h4z>^sgJj& zi{k+W7yzh-i}pr!L$G)KN=M1$4HsM_@q#XsWM1{!kz+y)PKZj80jUoRQ(s5ZU~|^p z(`;mf5y~U>AFE-hc zKeHHpMPoM3pvw%#A?4M^Mamk4WPC>&@~)3;3t|y0wU0}6V7>Yt-|BAJkp^2wMMrSa zjGpM31jWh7ZR#Y;Q?B}x0%RlZ3=dS`0KDZ~0fl}S8}e{T#j~RN1ld@&KOi}h>@lzt zRJF(y|KcixR6&c;14IZ_0O^@sE}2{2zYqN0=u|6BkZ-TpTZwo2s_rcZ7E$& zks)uskiU|9dk%QcdYEP@5La38qT;xl0!g(ipnR|`ZmhW z^7db?w%9Wzy3POmne9K}{E1p^Q>m=cuG|jEu2qsim)D-Ls4QBxI4fR9*k8JN z5^MzxJ&h;P0GOg-Hz25nMw4AB0fD5a`7Dd=5!p5OPFa>*6|yESRS?usia`IlPz*Ma zdz1MNV|Zwj|C!0O_{>W)>FqPOjyk9H!1sSqp0O)mAf@L2#AIKv8W$n`1u{AG@sQ1x9~41*(Dj$-)j+E-S>BKhlPw*cNc zr_8y@WEj6QdDO!!JKHf<-&13o_#^9X&(&`D;)bIRv`=JB}N$go}|t}SU#F}8q&Ao6rPqjfjEAniZ$1cX8cNc;w_C4 z0p}Rk57#|cG!fgURwBvN zNV=$$-)!;g9(3Ohz|t$tw=iwOAPd?FkUVr95+QJi+Vw(@4(`>2%~B_;WRAK>E#m{$ z5v43|ZB=Hnn4Qp)OmUW+Rdv*ib$Iv@amyC@{As27SIur>N?xlaAGa5UcnB;V+AygY9Sz<}Pd*rrfnxF=R&s zSJbOsY`dEHcoab1S3ZlX>HS+Fe^_7$v!qTPt+bZ+B>+`Ti|B)eX$rfncv`Z2IQ|EM z0<^CaG99+Bm@DQuiFT{-)pk|2FWwYA88LuBMyvE#zBa==yvbE;bT8v5)3@P<*RcK- zz+rtMC;(0PR6wA?zXCukx{ghkKuQ9#z$)tBS$a&VQ&L>Y;7N?_GU1W9+&M$(o;S!FQg;v=H+$Xzb_V%C1l7nJ=g5)I6`K8G#gKHg9)*C6JOSyHT-PO zS{|rmi&-D9jN>L?W$m~9?q7cU&)V{cVUMzc|KnsfEt5&KL}0<}p;I9|9#4x)DY#G( z-AZX|lwJrdcApoNE9?_OXPTv01F)YAd$4R-(-#B_*i$;V3ZU!(H$EklbZKmfOBkXo zRP|v6F^hsA$dH0y6l|77p9;C=fE3eeqE@m;`h)frU$QEQA|+sD-b4Et4}sLpTf<+! z?y3D8SGF*_t8T+vJgz;}*X#^_T>m z>pkO4&7Gh(x61aBYZZD91vE$@pazoThnc~od7f2FkE`v(>W$uKX zNO;*YJ-TT*U+R*TYD&ChAjH2kKL`1{N)we)sfy{W2LLK9;|Y+^0db#rq9-Hk_~iX& zLLf~pCi9e@9;k~NaF zwSp)6wc)^(DB0p9tin>XBn9)cJ6E8{RVmsh0c{ofD9;gIK4p_V{aTTO354TxcJ-qZ z9~UG#H@pcaE#wk+z2crTfv33CPfFk^e*)*aolSYuwwX1Qg>?br=PxXjhI%_!%lX+T zX-lecQ!^uwUb{TULPR*w@|VDXCzsr^A|dV7C{36JwG8#_2NasJu6><@L`h>1KK?~S zOvY0b006mUIz&$QdyA$2jaXWp)PA{bRbc5HI4q2!HS99HegB^2g+)(mu~el#T1aG2 zP+D)cuW;Y~TM>BOkF70Ya+Fj*&YWZ&X;F+XcPcPS5EbCx(brr0mrgjvuC5^dtsmn=4 z9m%5?x)L&5LYbPlv+Agn+DUyYN|kCV8jXa-TdOqWytiwC&$fXyNy|u)mRA6)1dKI? z2NN~X&45K$&BD%9vBrVWxpG`U*Cz^RC9QHua~xEtGXAw2Uw_;~~ ziF2r4R&XC7pk4`}@#K@iQ~MR)EI1vzS^%8BA?*er>!7QtO7us9>Oz?Ox~G71{52po zJz|_`N>=KVZ^9QpmusQ7w%vY_yAWg_B%6?&$}Id5`b*3vlsGBA>#zx`gs%f1jB2ds z5u9$s8MoAu9h2bIxeqH}xieWPbyh`b+~ba@Iw^EU>R)qldW>tWP>-^dGeG@$Q(4zi z8PnzQV0p9UCp+Vsv*biVZbXU}r^B@0u&Le;A&G{y$MYgAEE3<@u)x9>Y z*{`(ED(U%?Y;$3WdI=CLDo`)LeTU$K5tFL&QJ`3@IEgUzR5}sY3keK?@}^v=7*4MK zK(6G9m_JF&fs%=QIcl+Ep)Hllj(@tV;ZJ@IY7hd*e4@7)Kt4GsToMioLg1X!n`~lAg9k_^3ToQ(Q+xL`FMS$b7W^5ay`T1yo^OMc7KE<6}A160Xs>{mn_Z6lh#_ zCz|hg>I~DaCpwbQpi$n}Q&G|eQdSvn=v)NdfNt(279{6i{$NDlD%wZ3jD{=dGA8}| zuW`hK^LX%vYJbbP`jbm>?NZP__9@bTaw+AThrjQGth#m!>|35fi1?K*7hE{wf?Itk zw-?Jkj}WU?x=meT${sf6`(fhfqRL2kor_0KxH6YF!CU*8SIRH-d2C7^A#(J9p|ZNR zG&Rr*SQS59>UzYUDP0yVCQPG1Qvn<=8`5VJ`YpCi>iImEgOEdqDI;scJ6sf7F8J5r zDrJp~h_f|xNbdF)U7Q1BI*ulnrA04RU3as6g@sZT?qTva8EQe%UUqsa);mfDX1tL? zMRhIj1*&h`Ya}W#s9 zG&!r1W$JOl-_#AF!P?_7X z5%vmMWCAL`2UQ7lN*R=5KMa9SVxfUhT=9s^Mlz@z)rMl4 z=EYsf91E3N>ADX!t_xXl9aoiR1S&O;pDtWy)>t}+7!@3|HqDvwzw$j-$*whQj9Sn_ zS%QLqt;r;|2sV8=z$&g!m9K7o!0qSb=j9ubfimP^KSx57BU( zOde5Hnu<8sxuKW~C4X85@m92CgtbBB33f!uwAax|Q)GCd$)aLwxmSYqs@FRf`Z3TD zU(^6yntY*di*_lkUv{X@vL!uV!pX<{#?geVnfkeZ%#Smq0V|Ba9UY4UdM-_j=sy84zp+;cUjig?wEo~4NFqUG#-Ht6lm-xa; zeC2))DB+NNcI23~Wdo?vrWMu39`zvk!JUUqVWoRu+w3&CKkvVM`Rj|LC;cB^96j5A z1uI_rZC}fFt%rNvZ+{!MO(js^MxSBqKTxA0O3+hrn4Ber+v9a~_dmz&${wTig1A6n zY4wV{0rv0yU)2Ll1hL}vBfReYGa5~1 zP66nX;YbGIn?aiCWOPXYz}1i9f=;7-170V=EU-4E?-K2Q_(v#pYSn+c;i_*7*ZxzwK=7?%dXY`*QW){1R{CKHW7^vygz@)3pLSR=V__*dh1bP9SoH)_Q_pquGadk{LBkh^HPBtG%Ugw9~=!)-JUyQW%WIM z{wR}DQ&X|}9)A6}(s0y?dQ1CHpNhL`=62oGcNUzxYTl|e?A%)FXyihCuK^EyLgttO zWQs7bqaV$Hf1DI36EXVrviJlqX@X@i%Hz1xFSiXFIMz6r-QC>=d{dg9TfGnDZU0!_RV&mE3p;uv zwi^}mxXW8+n;~TLDkdqPt6wU;@qVfF(ppI3;el#L3N0zT%`&dnM9c<+2>tkV=VuAf zbj9-0t-P`z!pzP46l!P}g;~WioAo^_i|(!JETJ;%(Op$-FD+$6svLc89lYC0%)cN0 zKh4H#Y^0EZYd0$e%;Epnceb`Y{{Pd~3EzCBIyV**4uem9NC z;$57#n%(Xi{D(&BN7L!r@MLXFJ>#`zNA&wY9zH$j_uK8R{I($tvZ2Y+OtrekM86yI zr|$@BGO7j2ut^EI4{+`KWSG6l+5()VeN`-KhWs_d-?*~UO4_`b7 zX}!7CUGJ{5VT;>##}Ue4KnZooa+++E+;ZFh$!t)`UO zH3M}Q5%O+6L*|U+Yvnm1ry20Dz-r9zt2+Tt-JcZ$+q@=!QPO6Xk`=E1@7(s|ReX$8 zSXApMsHDAB5PlBGBzrqVttLpcy1eo}nz{V1E#DD!SkqZ{=6G>mo#CSUZ|K=k^d`n) zOE-W2x2x~}_J;rd@9b{v-roPu@wW;{Hx!rYEEDMk=D5kxGj<{n334$VMFX~!kCKx$ z{!u5q5+Gu{i93x|60YKR(HQIM>4s~AXqx0j7EMxaNaoMt9OTrC1k;<$Knjgek%KYa z7z6Yc&`<>eAtH7g4W*t8zQlZo*8p!v(#OqygLU5~&faV^ju6ZDr_)|<-}LCBvt^$a z{D%}*y!(&;kG7vI9gX5FUn9eN-Zx)eg+G}0{%>x2`v1+X?R&TH|7ZD=FB={6V|~i- zUMa2dp>a_#zr&L=&C&uM;#g{#NdazDv+`ArGv(dt{6<)Hr$v>~{P46}y}?&?{|%+Q zIuV%9|8IHse|u-+_Wl2y_um||Lj>4G-h9Q9zq$Q=bkIeGI>`U1ZDtqFenj1*@dbs7 z{xr_UNx_AQvS=KmEx!Yta!>Rm7jLkOf`~)pg`7;Oh5>Nu$OYicn6)3cmx0b2NM)(` zt8M~V{gA1fc6x}1Nx>AT&z2)rFfd!;4`wmnz~#@T+Ihkb@6#)+->>W`_p86}=Mj3N z{D&PApjWFW>ojz*fNb6qGG82~7}w>VdM8PGd;?T-&iy~l;@AnCz6ueTcmKC{Hog15 zvjfd<@Be4J|3AVp2Os*IW&~C-F;I4yPGU;1;3c&*7l{*vsJ`;%sBh`=iVq+37uuQ3 z8N_l*kE_Xr`Yayu{T4kz$^h)}2VhF(-2d}*9AA_BpD+J!to!f(*2dP>?fw6J_x~q2 zX7}L_F$CHfXBfY`wetNT?my+ixh?^i`~Cy7@7@2coqMYkEZKE$C*jXotm)7qL2DKbW)6pCJwN*(FHaD zJuNU>sIDOf48af+1IOR7L z+le32ZXltgg(M|VN?X<|OXFBzOGc7O4CVLP-*cH6jpR$Xre$kCyCt?pXKrWCoclrB zqXJm^JJYfS=bxQGv$8+`_L0WHtM30=-v77J=+qkP^M4&5cC)mP`+eH#_09gbJ^vgH zz^ebx;tp`d{a)5BAT_*ahI=@aLnm z!`{iGhZx1)RlXuBmlAt*8k^wlb}%m))Gb;TI~4$jNkL9{E1#mtKfimR`FQTvC+J+8Fn%3e6Ib1N)s zu%IVWiJC}O9jmPKD&7!xQ-5}Q(00hHt_7K$?}FBpYOI`F9W2Va6tZ$bEiY+14N}PI z`rD%rH^fOWnsId-{Eq&H_KXZS$YCZc-2StE{&o;RLF8f$j4Q9wVy&;Y_n`lwn}m~T z9M3-a4!DZ`*S2&1UyWL)xt9N~<#R(k#^Nco=NO5PsRPKL1GBD}lFxBT#1rYp$`R^B zqsCWoW&DlrjE|?LHcehr%D*T5=Xm_j;SGFM{BNx_$N%F$Yx;k!`oA?T{}@`Xhx8~D zO=^H;^I2TYndjOs;Dwe=({Xfn$bdt3l`)%ZS47=-o#bS#XtWk zmOQc$Y1xKS&`a2VA>D*iA@JXo_e21Z&+NDq9p1a6CHOPf6RsqsepPN|cE;C@u}p>` z<`;D?Fzpgj;eR>NxynJYxF`>nF?KErw!hoj(EqpM|6lv^LVNyKG1hySj7Jsu@uI+0 z_WyP>@Bdft)Ytxh*YdeNYWdk6Zn(Qzuqf$!T2OXaFzbI-kkIQ zX|>n>|JQc@AA}gr_i@Kw;YZeco>4hR2R`Wx(}P}Cw8sF#LFi6nW;s`#>ZKi;1dt2* zG!lsxij2U5J-Z=>J7Qk5VmKHS=X$@%dJWD<2FD6`*GeSEa@=Qe<2$i+Q}woFpn4os ziL=)aFHu}!06;aN!|YVD&loZfhem^xY3t?((L2VAl{9WKWk<>v(|cucrz@{u2( zZ~O z|3}U&oF*AB;4j4g*IKQ)`2Tim&HrBy{r~3R-x6w8T$F@>`ud z<93z5D{>wuvyqHpWs)A#IOkk+v4*lC_%EE-PV7ehB+cwfjg$>7PFuQhG=MuGai{_O z4EGQX;&wmuW~LS90SgiaGAWAz5bNP@T~vFz@_AYKR7IS=m9XA4+$p&>s>HX|@nVq9 zNJfcVd03+rk}HqtN8!MT>Xc3y}qXZ*YZ*Rzv%6O?!Q3rJkg(tM~?e)3tcU+UQYBHSOKcW z4ok%~yKSi;&C=HGuFBIUfmTIffX-p1val1iO#^|+6!W>+)^z7jI{z7=@6%mCui*b0 zwPro<|KDt{{XehevogVAKMa#hO2ss%A~PpeW&lE>#FwSHHe;%=yx=*ZU6l&YOanhj zTNLh01CO!|aMKMfE|x7<)Xj|JsCom2s7p-iZi+YFrZ2rKuJmVJ{Ei-2QOkde-$xgu zxx}yE?uk;1Grt0N$+tCug%!pWssZPQIuC>`TVc-PHmn4 z>3TluMl<)Qb-w~Ju!)}7I6VIOw^I63={e?L%TmHkDv>rDbK3O$RlMvvQvd7I9iEdD z?0n9-`!fYxil+V9xg5tO`$vWs;cuFu;6&{yJ3&6rHdpa?qkW6dzZ*#p{p;ZhLLLFD z{d~dm-)YR*|C)_;{^x7?c=g@kPtIWbN5 z&z$i4*UwIV?fmlQN#phIN&RwocyY9Qaqr|-ZT9rw;PJub-JK^tMD1F9>-*uYTQ7ck zc=z)0ueY8JN39cY$7@8d!iVm3_-pk2@W+$s>(e{?k7l!jv*Skfr&{#-=-J7wlke}{ zdwKEr$6NOg8m{~3&9kR>j_>ar+^W?a?Kfd?s-syvrUv|S}@KennUpyEF@zaOTPA^{#eh6Q^K0lmH_h%QUPn$tA zIGViqrC#+eCI`{|VBAXj@zKS(n~Yv<|I|3RH*I@v=YI3_&5O(S(ecx}cUqHZxAW%y z&i1YI`!AkN!w1gb$>sNl$2)KOm-lATsjQujcV3+OE$HgBd4J?w$a{~fzYLF$>hZ0~ zsPXjIpI+@BzI^d;QX6h313}|`wtF| zUO78I?d^S@^REMK@bG_e{okVa|5|gc|GOsr-`P(OKm-snqfIvRTlVS!v9gP{12`8jo9b~Nrh=Cuc z5`iRHprO5d&f4!KlDze_}PBhB|u+V*SkUPda%@ z4dEdakvI^}=xW)TjEre{?TcrH{Mb18Y}XE8Ry zsjBE*-(hx@)nv)41vQ)r@oG6m1RU!4fj+Aytef~&q)DNNAQDRe$3VMPLNtvj1hja! z)LkheRQjk5>Bi4_LtS&yfciKNF{RnySS;o|R6!C5cByORRqH*^aqd`WI-B^*S;T2e z1G=JO&q^Fp&Yn8gWy%=Jai$!FyHs!2BV=OH3VVzET&_W7JRdFr|JO4N0Uq_2be zr$VBv{xGz-qQ>tC@$r>C7DrGFXrOX|s=KlCLet~!dJ(8S_$r0>h2tJ7I-i8MnW;mA zk0a{Nx)Lz>_Ku|Z8M}kMiNqCCj>cpc#jKkItymjZQt;nrre}DA87^fvx{4rAs<<&V zo*LZCCwFpTJ4t3!UzEi8LxDs#H7|nlA@%dX-&ll1l&x9y5n851a%5?3M51?=FK2mG zm8ZBps)U!rpjk)rv`8&yVr+)WitiuoMjTWdO=OQ9n)9i^nk7a zbCh5LtjqXF>YoC{kI^TBJ9LmG&3ja;X-w(fCda~!l%gj?p$(Wj9-wy^W+Hd8QTCd&*>8myXj4SJqq z7BX3E2qPvAKN_G+cdOJSR+U_;3;89;Y=tL%OQ1kzLh6g2u^PO7BWQwjGYh;Z^e65; z^+F!L`FM-ci~H$v2hnY!CPhdp5}qCSMtuvFKo}8E`yE|9sPILJlsleBGM2^%F-^Kh zpl{MoY@4u`1Vc~|_9@3XP4!Czi#yEneT?)Zz9|(;QIGE=k-(SEDYq@(pYf>F-=?ms zSWIW#xLMMk5WonBfU*Mx0!z`1YF`~;La_A9k@6gr(GYm4<>F4yto@rw0Odb8rv{Wq9*5Hyr&~ z&kb@PFGE-jpCf#URAvz}kOP+tpoHN-XyIt`FBbW4sZw0_gZyJ+98a&robMZ?mPB#t z0t#IhBu7L(D8-*5%c$&*be84=W6ZH;TW3lg5oEFW?Kh9N?;DrohqUL_&am8pX9S#7 z>+p5^Q5uWSkYLzB&>6rLh55+HJk4e%C{2^CEWM%)L9)%%TMoN2j#)vlZd$XhTv2~p_vB(TfzJeu(jA^xiL zgYkXIUW8Wk99?fVSJ?0qURhiJoEZKzAo)EI69@o&|M^m;yH_OOWK{+osY%9BwZRYt zLrL?L5+8W{<@5I!M9@&}g`yu>)HZ9v`4v8ij%%93F=c|$J%pFlLq;B}hX99H*<~1B z=lngl?8Ex?QLZrZqmREK5<4Lc@u?#w>&(Ekyp-iO>X3}>Wf*usohh=*GQ>Qp# zD=sU1Nvo=oUJR6VN65rxX9AgHC&Lz+*eXR36bLB9LRDYECNzw8?-f|RMX`!(*^Nse z85LpO*TqC6p79bp9%&?wGuIlD7+)ThW>fM(QK%HfPqQh+7uh1=-K5Nz!wJRn^!Gv~ zXZ`!pYY_cI%N*>3FiGmfSoI5FqJ!j|WAmi~JLOA`x03R*yMFi9-kU3g@`3z;zy0(G zn46&4{Uh~^A3VA+mroo@X6yfx>dkr6{Sakhw?7bBIM0Jows$NLItS&(O<2NGO(wKc zhX61sdd$lp=;$l!;5HbslQ3tUT@kB+WKbvnd}3~d5PBD88flJQfqA1gtx(tCCH+2u za$n5Z~-MoXF+K6qu5Y6;!Y+UyMW!SH#1b;2g*jj(pLS_P8#Ax8+#SrsS zwOKNV?fkwLQ(BHx!Jd{G+|54mz^A=E>Wz~t$@Vkb6kY0*+^0d@PT$>_o0J0t)ip%` zhD?LRUAnXu@e=NELn08HmeY+XZ(;O#MPo$|bEod~&D#zr_5XTIl=$iUzfLYf*fwfbfXkh(>L>-C9{C~vdp}y>ulU( zB)Iu$dDAg|D2XI1;#Ox7G+l~<>ZjR}yka3R9_RzRGbV95T{i^7@(s;cdxx)#iFqrm zQ;RV371+$AjGB$kttSc`v{rZXS`qMXKNy`$-#oMQy`^}0h3#r65_{=w&)y+wzgJif zq7Dw%{923U{VG3H2Yd4JLfK4#`(cHnay+~sJc|)eotujDbw5OR(O&oNo2WX27S2co zvY|#pB<*q*M%`az&Ng&IG~8+QIw(z)I}{rvCj$$)K8`+SrmTvV0w%{I5@_ci=dO`Q z?C(g{9{m4)#{H?O0-~w{rI$W%iGEBxW$hHG0DkuIkvc%LfW(l*2bJ>j)zyk15n@$B zm}KhA-A;!?!_8q>EVyJHZkN&mgn4~2nSLbV|9Q%Y44xutW-KE%9nX1EJlgt^BQm+1 zfkiPgj=xOeTo1Pi-@kxz9?9zlfwvQ8BPBYsgKh=q2m#bYwf%x~GmzEqncKR5w2?&oxhpS1L8*EtnUA1^kAlGm5a3MJXTH8s#r1*Il22~d5LNnn4EXC#cq@1 z$9AWeZ{bHZzfQ2Rcb~@_mnK>jB!XK*_Nee@ADulxBN3Ipu{nLNKWF-a3M=fdt?HSZ zx)IY^2c1J{ zzC3(Nb-HXqH5Bzl9RBW-jL*TKEE!yNzXAN!ctB&3f|{IPhB1E7 zZ!dRYpry^9E;ow_t~i)G7q@4i1O9J!*B!F%ioL}g?;(OM|F>{#x~SV8B`0;zF^U!2YSy(B=Rk1SyK!{z0;ZthoHd%w z#R2R1WnRO#=w}nC^qx3qS)Qrxv&fXLx~Fa-ijX7XhXx2FuMwbjI^7JSu2iQQSBI5^ z^k|MWbvLBftF7L8>UmvdZJta{@U>k2<@l-wBJ(d=pNqoaAz0Q14B4W(Ry+*WQJjMk z`t&v@xP3-DJc3LCa!B^2hr~^`vw~vv2MO{xA|f~p2_4`-7BH6H??kyQ+m-nXXq5|D z?w57a{#70HXxJ~xsFW2??#r-p-v~4%<9_m+vbZs#yjB4#$oP>#hkC8R4X%iuwuKN= z(n&V<82D|xnK{=;1esAtQ)x9=$J#; z(44?)YX3Zb46pE^pQhRsD#!IWgk_( z?w`n35~IAYIdEuaE)P$0V>gdzIT6xfAZ21%_#TMxhT&Y6E6rrIO2`@@MG^Yi~qD>PRw#h_k<1jJ5Qf$LzgN!&ElUAXp9U%`A zr@X#fw{)*N6}fqM*nVxOxbU;DYwT*gXx{kfbtcjPa(7*5`4WEgFm$!I2m$Uk zej{IGzDsQYujI73`RyG+-m^|$K`jRtoFLHmO(k>hSv$0h=JplhCJ#MKnI!Eiv~AUn zy=v^7g_qFsxa#P)*+zLUd4Zjt_GXhfnf0Mxn>53*@fpS}3$N{ebH-Y|FzDB-3X_Np z+L-O_HUgCsPRtQJedZ2$_%vpDD+i{gV#OzMbeIONV#SPyB(Wq*AKfHb0NuzG z8u765Ex%COS^^evjeb8TW>G4b`QrZ@G?`Po#v*V?tdgK~kSogm4x8~yrB+HQ z_J@0UhtF1Y zDY>lGqx;zGOmA{B)QK@WuHS0AqRE)NPEB3yA*O6P1!_ixE4O63X4uI??i*|Za_M@} z{UPPnDgX5L8|HA|lm>QKd1-X;FP(WL`r|z5;*Na1L26=5<=^?Ibv(bDQD{PnTnU9tgGl?06DOT8IxA8~=d5hZSN9)U6HvzEUDxY3#8iRot->U39 z>as<@LYhfoX{u8U0;uezMT|(aLK(y@FXjs}$cQ zJl7ryA_^IGP>YtS@WO7gV7~hP3?dXxS0%E{hqbIB- zN`WqhV(aHxv6?{-_9#|!Jl{_etMH)Y3Cn3qFdML$iOA;sTXYzYz1!doG!V-Leu$JEDY*$j8N;;&dA1I zox6*nXoS(pIq2Vmvi{~u<0?I{xohk7az#%QV9 zenRvk5crElR!++k#c;p##zH9EPPY^&%%s}^dVJFV)Z56eS@x&>+>_a z?Q2d{t-+7O6k}Nv>`|$x8UnL=oT&t?hGK0}GCt-L3XM53Tot6Cq-QMVEKbfke)BaE zZ0T#9cJ>Zie!t#GgTt+keSHX4G#t^FoD9Bm!{RQD0fXkulXm8HWiD)Q>H4isIBG`} zavl{+;jArw3rG%=^sf=OGMTrYRVALHL5E)+j#NZ zl)rDdQ`xA#T~Nq)hR)uGQXBXoNsF zMP*7Kfgg|D5c>7(o(l1&{ku?K@db)Fwc<_FV3baDsyOLGy?3_Ri_L;^-%Y z3FSyRiR`IcB=5sgdz6O@_{hxHl@%C9cIj}(QuJz=oeP|qC1sb5F0HdWk}{~uVd0^` zr3|B^&$!PHjg3&J@xUK+2C`l84x45^p}gb9aJzDP5`0b?`fH+n0*J6^R8Q+9oSPU1 z9OBNL3+J^WIUEau(Vs7fhiXh_7S#_*DhHYqG&(6})J;v2x=V0?x@FB5N zz2v)i@-K+EV#n62+&sWW8{$ z)aEANZB`U1_z^o$hv48KzsO2k5tnQIlW=g#JZ+~9=`iP!kJ5qNG#QJ%n4T-RUY>@2 ze6Fvt2$GK?@v2-X)C~A~;Hj}(D@9NC6H(So7-o9CO+l#Brc2O1L=ws@;?gpb6rl4S zetD2)qx>itZDqcIN8bIXW=TCNEjuuX@=!kSA)U@<)Dm_r7v+Xn@_4TZl2UV5n*2gclt$5?t|7Il97s)MB1aW-5b6^3JL?#ylesEZS zzjyO@f=7R2ckI3GC)!K^h5n;h@KKI59$x!(KYT+uyt|sh8)Hj*b8!NfqI|}CJr@_y zEKmIMlvh;izdZbeu+dQ5Udr;8Dy;gC&ANS?uRa+5G%8yD!7Zq#h}rSHKxz6k9S13& zry&hAvqPNzFj3It@3khtPg~wIjd~@_?_(hD&qIyA6^Z%7S*heb;p%}kW!WXkDx-P( zFYVoY0dk`DZ}T-~TDPS5&cyNnAy50-hlYm&ITqR7(0B>HU$U!zxA4PExt`xB@u{|;$BYKtUnG;|g<0Jn!f?aTYI0d4zvxI~? zws^X}MF}L=svI<85#i>VN}xv)O2FT%KYfO5Y;?x$t4x1YFb?V4^VfxTYs4vK;n*Tv z%CG!YK3wjL*&)>swH@z82zL$+MJj~RpSRA~X)OtMOiFvDE3DX6E*h%eXcPZdaRvs9 z{czl=bqbQKJ`XJ`gqw@L8|7i)Hfb}9AB7C0Hf!wNzhK}8o1-szVPk_W|ACh>?%SSv zrXSirMpoQDMkuE#V$o~44;EQ~4bWBsW`fgb`M{P}z?xPQ3suQg4-6+v0z6n$m`e^W zYCQ+eT(15Wsw7!^k1 zod&8u?cqYQppfma@rMZCO9%Xdis0yMr!| zWP`@E=|kEkSz783t)b7%km~9Cw1$NBu)!T2>j|4CD`%tK3oh^i9ZIDai@(>QOP7e6 zIz2k(YP@4Y1)Shx3=u`RK!24cZU~IJ7EV{bUv#hhv+d{zzPJ!uPoA#RlbuanqIY%IvJtva<1R<*Q5~_H@nL=9e<}!81(McOfR`QI z{@wLx>L2+(e^^8JM=YcT*->~HI9l1XZO-ns2+f2IN~`5U5C z-D!936`kZ#*YSAVYcd`U(iNJz$0uL4`N4Pz9QCJ+{=BfQ?H_vv?{P}j)!^reTreSQ zatB(E8c`~AsXD`fF6O?*Pmp4?c8j;%@?vR#MiwdhG;k->-nRuBABnoYUQ9IbiFc0J zUL-4ddLfi6ek5tVKEA50c9RETMb^t+qWN*nIK#*_f$_@GKn?j+O8O!`A} zcfd(;<#k~scRM{bG7AKpSs=u;&1P|QN_&B#_EN)<4s$I-ZUv`N*?{NX|^ZnksnKE#YGMxWl4*f3np^{{7GW^mkFu^BBfXt z`ynukXeGzq89@!dBYKZ_SiB;%p8el2EOYQ^(zR?(8c!uwkmC;hzozvi?ti7v}O%CNzY zs5Nj9E+vn1F8(A_3ZM!)G{a`eU~tgzFw=zFfS9H;rn6+Y&PB0Y?x9?6C;g&YC2LI+ zm({<(9M+t4{5ZZN4mmzf(hYWjO@g;{PdcQvOH5&edx--uWZAQ zO0m@QrCEFkmkBjNwbkh4+a{M8DYQ^vMtm^S^Xig*{B1y%d~+@|G-F9baPsv(OK#xK zi9x%3rB>119K%1!&*AK?L&QQj7Yd<{+JVxl;jAZ4*f&HGle&0eb@@ zq&{CWr${fX8pth&oCH9b*m|>nFliLgNaQ=>Aqt`H(rUr^0DDtBoY?W5eNfhaqPxi{ zxXlucWwF)Tk#sihF=#NyiPguKrja1WSZ1&MU&oYRAveDX_R=WG__PA%g%S8!z?@he zsLikol%>h&xCtnX>c?lA$i^>xuTT%Y37{B9xzp%R$dSngq%|bXUf_r23)`Q^zFXSl zxB&b``1@my#=`l0+r?0yl^W0c$cMMP%E~X}#!hJBmAStApBOZw8D<1+Rte9bJR^F` z*E<6sm&=gOdA7RmpszNFxaQs}Xg9b@XFKJw#ye}!EHT>5;3;=NlCY5+b{>t`IlfIC zmDW#Jo^oUFW!6zXYccnvu9wH!ecnm#xyCSa64+ItcRwEwXrXuHkHJlE;D{jb>lpZc zEekANt*gHR?xubQM-$bxqNahRB$e{S(U!i4^*xWqh2gl|0V>aP)rN4*XodNl!Z7sx zR=YIG(DXp{tvU4dRwwAYE9?M;4b#c+mJrGQ3Ry}^_w&3cc(6~Sv+@e{w|Y#RU!F?} z;<@u+TEzi$=1BC*(bp#yJe59ucq(=($VRlLeC2O$RLTs)H5<)u@`U$wJg`x zDvxg6yk!bTg0J|e{TsPJA1y2QJ+()Te^kXTdhq!0?7S3Q9bAZ%vp$9)g$;2^O7D#6 z`Am35RFmo8Fsq9LJ86***lq+LG3#X~GUY1FOya_y=@|vk)rOHwib8jEHX-m;jZ&8L zC9eEGy8M!{ZbxmQ<1Rd^jcU0(T0q{NA3`nx&z<)@+vBU#ul|et-@)MT&rYWu9ZhX< zeEx&>_4U2k%|@78Ci*}g4n-yCa@T&X8 z&hgxS=TBW?jT&oq^QSFz+l{7ij(p)C)i!P~>le+2HK;sYOu?&p;Jh|gR9vp9G;U&N zJ_JHjy`lJf3Z&tyU#JZumjI7cf|tL>qk^z2|Vo?uy!nYn00X9u2}TEX4M zPY@7})DQz%?iZlUsBM`Yw=N_7bE3yg1HNkAXwJpX7;!)Clb z1ge+kZ_$o~{XY^6s%F6MfBd`4mG;Wk9)l}TjG}DTa)GrDj0iHt8MrAlQ6*xhgt5kv zKRG3TGyQ@f%g{iJSAQ!I+NvlE4e~0xSWY?2SLwxO{NxE-MF}~hZA=~<43h1E*#FXM z(rZ5D&a3HOC2xmb-Cuqc;yu6TMs9!E#<_xaO=tA0mvAkwzm}>q2>j}f{(P4k5mBx}|pRo;;{)rbiNAmg*B!lYXw6X)^sDYBt z=|Q~@S5!d&OldB{3C@yDDIl1JHo@JEY0-Mmt)Zn0bBS6HfgZ01Tw{x-4t6wn${&Qa zXQj~n)-x~YP}8MqIE}n)QyA<}9HO_DDmUxso)yJ^IjvvY%QET88-mt+UhkO|Ek2^* zoO~|ckCjJ;AKJ7rwpBrSht_hmW!ltRecid+;(zsrd*5p~f(U2Mktxu57%y3A*NIUfuVZBL?GC@yF z(p(S)VbtywB=xNN!TJC~CDfW^0YfjZQc-3n1HtfTz1NDS$RM2Cx6HmXH6#-;D%;zX zGX*Q(cuU<6S!4Q`Z=hq1a#|sSSRX8xp-YEHR9v^m{ZVP~Th4fXD^2^K=Yo=oSUu&g z-O>|Yy#9k|uLYZ;#y{X?$%HKKaeBF$P|Jfc{&=iEc!w3{5csY0i_~u;GhuO;Sy^98 z==7fZk1&XyZeU!M%xahvHpN)48(2(!B88BAW-*oLkVtnrXE3eaw zKeYqYXCHzcctYHA5wI&Dik9V=y@qUuS5h|0?MqbVI10sJnNx{}x^Hmyqr-8yUmLo0 zmUHi|-=B@&-uxjTAi!W~2n)q@JO~Hq&)$FRzfA#i8%y)=uI~1(%s))+?U=2-{u|!J z`e~e;oDl!B{j~qRW##1JfMDa`=Hle!;pFDzhG1pm{--Mo`d}@qDpFq6m_=!ji^$)n;@l zW7MvfSSpOMS)U-GyPEV_)c9@W?#-inFCjl4{}Cp~xT9Nbe~BS!pV#N3ixVisDs88o z0D4&^I7vszFYBV?P+|6TIPISQPQ^a)1^s7_2lp5~J#QYWC!TncQ2bK_^heMI4TQdE z%I)W{a2Js+sZ)CKK;%eaKAvs=ELH00aG;@cdwV3)xzUE%Dl0tUfu4;bq2W*Ey9Xs6 zL)wZp?b9+G`L6-%c0TJ}a%4#6I{LcmCyZtWK29-J?x5h0skX)VZ^>yDVx|rb14X8P zK!WDH=2#IK7HkNw=fXuCuTSmhU9AkaR;U~BC&Igab`#`sCy<~Nz_00VJUm}>EIsPe hZjbj^2Dnv;79sz?{)+!`?mr6rM}hw+@c*vB{{VwPbqN3f literal 0 HcmV?d00001