option, only those that have blog posts
+ * @return mixed
+ */
+ protected function loadCategories()
+ {
+ $categories = BlogCategory::with('posts_count')->getNested();
+ if (!$this->property('displayEmpty')) {
+ $iterator = function ($categories) use (&$iterator) {
+ return $categories->reject(function ($category) use (&$iterator) {
+ if ($category->getNestedPostCount() == 0) {
+ return true;
+ }
+ if ($category->children) {
+ $category->children = $iterator($category->children);
+ }
+ return false;
+ });
+ };
+ $categories = $iterator($categories);
+ }
+
+ /*
+ * Add a "url" helper attribute for linking to each category
+ */
+ return $this->linkCategories($categories);
+ }
+
+ /**
+ * Sets the URL on each category according to the defined category page
+ * @return void
+ */
+ protected function linkCategories($categories)
+ {
+ return $categories->each(function ($category) {
+ $category->setUrl($this->categoryPage, $this->controller);
+
+ if ($category->children) {
+ $this->linkCategories($category->children);
+ }
+ });
+ }
+}
diff --git a/plugins/rainlab/blog/components/Post.php b/plugins/rainlab/blog/components/Post.php
new file mode 100644
index 00000000..8faa8ff6
--- /dev/null
+++ b/plugins/rainlab/blog/components/Post.php
@@ -0,0 +1,156 @@
+ 'rainlab.blog::lang.settings.post_title',
+ 'description' => 'rainlab.blog::lang.settings.post_description'
+ ];
+ }
+
+ public function defineProperties()
+ {
+ return [
+ 'slug' => [
+ 'title' => 'rainlab.blog::lang.settings.post_slug',
+ 'description' => 'rainlab.blog::lang.settings.post_slug_description',
+ 'default' => '{{ :slug }}',
+ 'type' => 'string',
+ ],
+ 'categoryPage' => [
+ 'title' => 'rainlab.blog::lang.settings.post_category',
+ 'description' => 'rainlab.blog::lang.settings.post_category_description',
+ 'type' => 'dropdown',
+ 'default' => 'blog/category',
+ ],
+ ];
+ }
+
+ public function getCategoryPageOptions()
+ {
+ return Page::sortBy('baseFileName')->lists('baseFileName', 'baseFileName');
+ }
+
+ public function init()
+ {
+ Event::listen('translate.localePicker.translateParams', function ($page, $params, $oldLocale, $newLocale) {
+ $newParams = $params;
+
+ if (isset($params['slug'])) {
+ $records = BlogPost::transWhere('slug', $params['slug'], $oldLocale)->first();
+ if ($records) {
+ $records->translateContext($newLocale);
+ $newParams['slug'] = $records['slug'];
+ }
+ }
+
+ return $newParams;
+ });
+ }
+
+ public function onRun()
+ {
+ $this->categoryPage = $this->page['categoryPage'] = $this->property('categoryPage');
+ $this->post = $this->page['post'] = $this->loadPost();
+ if (!$this->post) {
+ $this->setStatusCode(404);
+ return $this->controller->run('404');
+ }
+ }
+
+ public function onRender()
+ {
+ if (empty($this->post)) {
+ $this->post = $this->page['post'] = $this->loadPost();
+ }
+ }
+
+ protected function loadPost()
+ {
+ $slug = $this->property('slug');
+
+ $post = new BlogPost;
+ $query = $post->query();
+
+ if ($post->isClassExtendedWith('RainLab.Translate.Behaviors.TranslatableModel')) {
+ $query->transWhere('slug', $slug);
+ } else {
+ $query->where('slug', $slug);
+ }
+
+ if (!$this->checkEditor()) {
+ $query->isPublished();
+ }
+
+ $post = $query->first();
+
+ /*
+ * Add a "url" helper attribute for linking to each category
+ */
+ if ($post && $post->exists && $post->categories->count()) {
+ $post->categories->each(function($category) {
+ $category->setUrl($this->categoryPage, $this->controller);
+ });
+ }
+
+ return $post;
+ }
+
+ public function previousPost()
+ {
+ return $this->getPostSibling(-1);
+ }
+
+ public function nextPost()
+ {
+ return $this->getPostSibling(1);
+ }
+
+ protected function getPostSibling($direction = 1)
+ {
+ if (!$this->post) {
+ return;
+ }
+
+ $method = $direction === -1 ? 'previousPost' : 'nextPost';
+
+ if (!$post = $this->post->$method()) {
+ return;
+ }
+
+ $postPage = $this->getPage()->getBaseFileName();
+
+ $post->setUrl($postPage, $this->controller);
+
+ $post->categories->each(function($category) {
+ $category->setUrl($this->categoryPage, $this->controller);
+ });
+
+ return $post;
+ }
+
+ protected function checkEditor()
+ {
+ $backendUser = BackendAuth::getUser();
+
+ return $backendUser && $backendUser->hasAccess('rainlab.blog.access_posts');
+ }
+}
diff --git a/plugins/rainlab/blog/components/Posts.php b/plugins/rainlab/blog/components/Posts.php
new file mode 100644
index 00000000..3ac3c4a3
--- /dev/null
+++ b/plugins/rainlab/blog/components/Posts.php
@@ -0,0 +1,259 @@
+ 'rainlab.blog::lang.settings.posts_title',
+ 'description' => 'rainlab.blog::lang.settings.posts_description'
+ ];
+ }
+
+ public function defineProperties()
+ {
+ return [
+ 'pageNumber' => [
+ 'title' => 'rainlab.blog::lang.settings.posts_pagination',
+ 'description' => 'rainlab.blog::lang.settings.posts_pagination_description',
+ 'type' => 'string',
+ 'default' => '{{ :page }}',
+ ],
+ 'categoryFilter' => [
+ 'title' => 'rainlab.blog::lang.settings.posts_filter',
+ 'description' => 'rainlab.blog::lang.settings.posts_filter_description',
+ 'type' => 'string',
+ 'default' => '',
+ ],
+ 'postsPerPage' => [
+ 'title' => 'rainlab.blog::lang.settings.posts_per_page',
+ 'type' => 'string',
+ 'validationPattern' => '^[0-9]+$',
+ 'validationMessage' => 'rainlab.blog::lang.settings.posts_per_page_validation',
+ 'default' => '10',
+ ],
+ 'noPostsMessage' => [
+ 'title' => 'rainlab.blog::lang.settings.posts_no_posts',
+ 'description' => 'rainlab.blog::lang.settings.posts_no_posts_description',
+ 'type' => 'string',
+ 'default' => Lang::get('rainlab.blog::lang.settings.posts_no_posts_default'),
+ 'showExternalParam' => false,
+ ],
+ 'sortOrder' => [
+ 'title' => 'rainlab.blog::lang.settings.posts_order',
+ 'description' => 'rainlab.blog::lang.settings.posts_order_description',
+ 'type' => 'dropdown',
+ 'default' => 'published_at desc',
+ ],
+ 'categoryPage' => [
+ 'title' => 'rainlab.blog::lang.settings.posts_category',
+ 'description' => 'rainlab.blog::lang.settings.posts_category_description',
+ 'type' => 'dropdown',
+ 'default' => 'blog/category',
+ 'group' => 'rainlab.blog::lang.settings.group_links',
+ ],
+ 'postPage' => [
+ 'title' => 'rainlab.blog::lang.settings.posts_post',
+ 'description' => 'rainlab.blog::lang.settings.posts_post_description',
+ 'type' => 'dropdown',
+ 'default' => 'blog/post',
+ 'group' => 'rainlab.blog::lang.settings.group_links',
+ ],
+ 'exceptPost' => [
+ 'title' => 'rainlab.blog::lang.settings.posts_except_post',
+ 'description' => 'rainlab.blog::lang.settings.posts_except_post_description',
+ 'type' => 'string',
+ 'validationPattern' => '^[a-z0-9\-_,\s]+$',
+ 'validationMessage' => 'rainlab.blog::lang.settings.posts_except_post_validation',
+ 'default' => '',
+ 'group' => 'rainlab.blog::lang.settings.group_exceptions',
+ ],
+ 'exceptCategories' => [
+ 'title' => 'rainlab.blog::lang.settings.posts_except_categories',
+ 'description' => 'rainlab.blog::lang.settings.posts_except_categories_description',
+ 'type' => 'string',
+ 'validationPattern' => '^[a-z0-9\-_,\s]+$',
+ 'validationMessage' => 'rainlab.blog::lang.settings.posts_except_categories_validation',
+ 'default' => '',
+ 'group' => 'rainlab.blog::lang.settings.group_exceptions',
+ ],
+ ];
+ }
+
+ public function getCategoryPageOptions()
+ {
+ return Page::sortBy('baseFileName')->lists('baseFileName', 'baseFileName');
+ }
+
+ public function getPostPageOptions()
+ {
+ return Page::sortBy('baseFileName')->lists('baseFileName', 'baseFileName');
+ }
+
+ public function getSortOrderOptions()
+ {
+ $options = BlogPost::$allowedSortingOptions;
+
+ foreach ($options as $key => $value) {
+ $options[$key] = Lang::get($value);
+ }
+
+ return $options;
+ }
+
+ public function onRun()
+ {
+ $this->prepareVars();
+
+ $this->category = $this->page['category'] = $this->loadCategory();
+ $this->posts = $this->page['posts'] = $this->listPosts();
+
+ /*
+ * If the page number is not valid, redirect
+ */
+ if ($pageNumberParam = $this->paramName('pageNumber')) {
+ $currentPage = $this->property('pageNumber');
+
+ if ($currentPage > ($lastPage = $this->posts->lastPage()) && $currentPage > 1) {
+ return Redirect::to($this->currentPageUrl([$pageNumberParam => $lastPage]));
+ }
+ }
+ }
+
+ protected function prepareVars()
+ {
+ $this->pageParam = $this->page['pageParam'] = $this->paramName('pageNumber');
+ $this->noPostsMessage = $this->page['noPostsMessage'] = $this->property('noPostsMessage');
+
+ /*
+ * Page links
+ */
+ $this->postPage = $this->page['postPage'] = $this->property('postPage');
+ $this->categoryPage = $this->page['categoryPage'] = $this->property('categoryPage');
+ }
+
+ protected function listPosts()
+ {
+ $category = $this->category ? $this->category->id : null;
+ $categorySlug = $this->category ? $this->category->slug : null;
+
+ /*
+ * List all the posts, eager load their categories
+ */
+ $isPublished = !$this->checkEditor();
+
+ $posts = BlogPost::with(['categories', 'featured_images'])->listFrontEnd([
+ 'page' => $this->property('pageNumber'),
+ 'sort' => $this->property('sortOrder'),
+ 'perPage' => $this->property('postsPerPage'),
+ 'search' => trim(input('search')),
+ 'category' => $category,
+ 'published' => $isPublished,
+ 'exceptPost' => is_array($this->property('exceptPost'))
+ ? $this->property('exceptPost')
+ : preg_split('/,\s*/', $this->property('exceptPost'), -1, PREG_SPLIT_NO_EMPTY),
+ 'exceptCategories' => is_array($this->property('exceptCategories'))
+ ? $this->property('exceptCategories')
+ : preg_split('/,\s*/', $this->property('exceptCategories'), -1, PREG_SPLIT_NO_EMPTY),
+ ]);
+
+ /*
+ * Add a "url" helper attribute for linking to each post and category
+ */
+ $posts->each(function($post) use ($categorySlug) {
+ $post->setUrl($this->postPage, $this->controller, ['category' => $categorySlug]);
+
+ $post->categories->each(function($category) {
+ $category->setUrl($this->categoryPage, $this->controller);
+ });
+ });
+
+ return $posts;
+ }
+
+ protected function loadCategory()
+ {
+ if (!$slug = $this->property('categoryFilter')) {
+ return null;
+ }
+
+ $category = new BlogCategory;
+
+ $category = $category->isClassExtendedWith('RainLab.Translate.Behaviors.TranslatableModel')
+ ? $category->transWhere('slug', $slug)
+ : $category->where('slug', $slug);
+
+ $category = $category->first();
+
+ return $category ?: null;
+ }
+
+ protected function checkEditor()
+ {
+ $backendUser = BackendAuth::getUser();
+
+ return $backendUser &&
+ $backendUser->hasAccess('rainlab.blog.access_posts') &&
+ BlogSettings::get('show_all_posts', true);
+ }
+}
diff --git a/plugins/rainlab/blog/components/RssFeed.php b/plugins/rainlab/blog/components/RssFeed.php
new file mode 100644
index 00000000..295433b0
--- /dev/null
+++ b/plugins/rainlab/blog/components/RssFeed.php
@@ -0,0 +1,159 @@
+ 'rainlab.blog::lang.settings.rssfeed_title',
+ 'description' => 'rainlab.blog::lang.settings.rssfeed_description'
+ ];
+ }
+
+ public function defineProperties()
+ {
+ return [
+ 'categoryFilter' => [
+ 'title' => 'rainlab.blog::lang.settings.posts_filter',
+ 'description' => 'rainlab.blog::lang.settings.posts_filter_description',
+ 'type' => 'string',
+ 'default' => '',
+ ],
+ 'sortOrder' => [
+ 'title' => 'rainlab.blog::lang.settings.posts_order',
+ 'description' => 'rainlab.blog::lang.settings.posts_order_description',
+ 'type' => 'dropdown',
+ 'default' => 'created_at desc',
+ ],
+ 'postsPerPage' => [
+ 'title' => 'rainlab.blog::lang.settings.posts_per_page',
+ 'type' => 'string',
+ 'validationPattern' => '^[0-9]+$',
+ 'validationMessage' => 'rainlab.blog::lang.settings.posts_per_page_validation',
+ 'default' => '10',
+ ],
+ 'blogPage' => [
+ 'title' => 'rainlab.blog::lang.settings.rssfeed_blog',
+ 'description' => 'rainlab.blog::lang.settings.rssfeed_blog_description',
+ 'type' => 'dropdown',
+ 'default' => 'blog/post',
+ 'group' => 'rainlab.blog::lang.settings.group_links',
+ ],
+ 'postPage' => [
+ 'title' => 'rainlab.blog::lang.settings.posts_post',
+ 'description' => 'rainlab.blog::lang.settings.posts_post_description',
+ 'type' => 'dropdown',
+ 'default' => 'blog/post',
+ 'group' => 'rainlab.blog::lang.settings.group_links',
+ ],
+ ];
+ }
+
+ public function getBlogPageOptions()
+ {
+ return Page::sortBy('baseFileName')->lists('baseFileName', 'baseFileName');
+ }
+
+ public function getPostPageOptions()
+ {
+ return Page::sortBy('baseFileName')->lists('baseFileName', 'baseFileName');
+ }
+
+ public function getSortOrderOptions()
+ {
+ $options = BlogPost::$allowedSortingOptions;
+
+ foreach ($options as $key => $value) {
+ $options[$key] = Lang::get($value);
+ }
+
+ return $options;
+ }
+
+ public function onRun()
+ {
+ $this->prepareVars();
+
+ $xmlFeed = $this->renderPartial('@default');
+
+ return Response::make($xmlFeed, '200')->header('Content-Type', 'text/xml');
+ }
+
+ protected function prepareVars()
+ {
+ $this->blogPage = $this->page['blogPage'] = $this->property('blogPage');
+ $this->postPage = $this->page['postPage'] = $this->property('postPage');
+ $this->category = $this->page['category'] = $this->loadCategory();
+ $this->posts = $this->page['posts'] = $this->listPosts();
+
+ $this->page['link'] = $this->pageUrl($this->blogPage);
+ $this->page['rssLink'] = $this->currentPageUrl();
+ }
+
+ protected function listPosts()
+ {
+ $category = $this->category ? $this->category->id : null;
+
+ /*
+ * List all the posts, eager load their categories
+ */
+ $posts = BlogPost::with('categories')->listFrontEnd([
+ 'sort' => $this->property('sortOrder'),
+ 'perPage' => $this->property('postsPerPage'),
+ 'category' => $category
+ ]);
+
+ /*
+ * Add a "url" helper attribute for linking to each post and category
+ */
+ $posts->each(function($post) {
+ $post->setUrl($this->postPage, $this->controller);
+ });
+
+ return $posts;
+ }
+
+ protected function loadCategory()
+ {
+ if (!$categoryId = $this->property('categoryFilter')) {
+ return null;
+ }
+
+ if (!$category = BlogCategory::whereSlug($categoryId)->first()) {
+ return null;
+ }
+
+ return $category;
+ }
+}
diff --git a/plugins/rainlab/blog/components/categories/default.htm b/plugins/rainlab/blog/components/categories/default.htm
new file mode 100644
index 00000000..e9b02807
--- /dev/null
+++ b/plugins/rainlab/blog/components/categories/default.htm
@@ -0,0 +1,10 @@
+{% if __SELF__.categories|length > 0 %}
+
+ {% partial __SELF__ ~ "::items"
+ categories = __SELF__.categories
+ currentCategorySlug = __SELF__.currentCategorySlug
+ %}
+
+{% else %}
+ No categories were found.
+{% endif %}
diff --git a/plugins/rainlab/blog/components/categories/items.htm b/plugins/rainlab/blog/components/categories/items.htm
new file mode 100644
index 00000000..cb1b8e55
--- /dev/null
+++ b/plugins/rainlab/blog/components/categories/items.htm
@@ -0,0 +1,18 @@
+{% for category in categories %}
+ {% set postCount = category.post_count %}
+
+ {{ category.name }}
+ {% if postCount %}
+ {{ postCount }}
+ {% endif %}
+
+ {% if category.children|length > 0 %}
+
+ {% partial __SELF__ ~ "::items"
+ categories=category.children
+ currentCategorySlug=currentCategorySlug
+ %}
+
+ {% endif %}
+
+{% endfor %}
diff --git a/plugins/rainlab/blog/components/post/default.htm b/plugins/rainlab/blog/components/post/default.htm
new file mode 100644
index 00000000..291d4f32
--- /dev/null
+++ b/plugins/rainlab/blog/components/post/default.htm
@@ -0,0 +1,27 @@
+{% set post = __SELF__.post %}
+
+{{ post.content_html|raw }}
+
+{% if post.featured_images|length %}
+
+ {% for image in post.featured_images %}
+
+
+
+ {% endfor %}
+
+{% endif %}
+
+
+ Posted
+ {% if post.categories|length %} in
+ {% for category in post.categories %}
+ {{ category.name }} {% if not loop.last %}, {% endif %}
+ {% endfor %}
+ {% endif %}
+ on {{ post.published_at|date('M d, Y') }}
+
diff --git a/plugins/rainlab/blog/components/posts/default.htm b/plugins/rainlab/blog/components/posts/default.htm
new file mode 100644
index 00000000..0c0e2b27
--- /dev/null
+++ b/plugins/rainlab/blog/components/posts/default.htm
@@ -0,0 +1,40 @@
+{% set posts = __SELF__.posts %}
+
+
+ {% for post in posts %}
+
+
+
+
+ Posted
+ {% if post.categories|length %} in {% endif %}
+ {% for category in post.categories %}
+ {{ category.name }} {% if not loop.last %}, {% endif %}
+ {% endfor %}
+ on {{ post.published_at|date('M d, Y') }}
+
+
+ {{ post.summary|raw }}
+
+ {% else %}
+ {{ __SELF__.noPostsMessage }}
+ {% endfor %}
+
+
+{% if posts.lastPage > 1 %}
+
+{% endif %}
diff --git a/plugins/rainlab/blog/components/rssfeed/default.htm b/plugins/rainlab/blog/components/rssfeed/default.htm
new file mode 100644
index 00000000..b4527ec8
--- /dev/null
+++ b/plugins/rainlab/blog/components/rssfeed/default.htm
@@ -0,0 +1,18 @@
+
+
+
+ {{ this.page.meta_title ?: this.page.title }}
+ {{ link }}
+ {{ this.page.meta_description ?: this.page.description }}
+
+ {% for post in posts %}
+ -
+
{{ post.title }}
+ {{ post.url }}
+ {{ post.url }}
+ {{ post.published_at.toRfc2822String }}
+ {{ post.summary }}
+
+ {% endfor %}
+
+
diff --git a/plugins/rainlab/blog/composer.json b/plugins/rainlab/blog/composer.json
new file mode 100644
index 00000000..7f49a069
--- /dev/null
+++ b/plugins/rainlab/blog/composer.json
@@ -0,0 +1,25 @@
+{
+ "name": "rainlab/blog-plugin",
+ "type": "october-plugin",
+ "description": "Blog plugin for October CMS",
+ "homepage": "https://octobercms.com/plugin/rainlab-blog",
+ "keywords": ["october", "octobercms", "blog"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Alexey Bobkov",
+ "email": "aleksey.bobkov@gmail.com",
+ "role": "Co-founder"
+ },
+ {
+ "name": "Samuel Georges",
+ "email": "daftspunky@gmail.com",
+ "role": "Co-founder"
+ }
+ ],
+ "require": {
+ "php": ">=7.0",
+ "composer/installers": "~1.0"
+ },
+ "minimum-stability": "dev"
+}
diff --git a/plugins/rainlab/blog/config/config.php b/plugins/rainlab/blog/config/config.php
new file mode 100644
index 00000000..bdf5743b
--- /dev/null
+++ b/plugins/rainlab/blog/config/config.php
@@ -0,0 +1,18 @@
+ '',
+
+ 'summary_default_length' => 600
+
+];
diff --git a/plugins/rainlab/blog/controllers/Categories.php b/plugins/rainlab/blog/controllers/Categories.php
new file mode 100644
index 00000000..583cdbf2
--- /dev/null
+++ b/plugins/rainlab/blog/controllers/Categories.php
@@ -0,0 +1,47 @@
+delete();
+ }
+
+ Flash::success(Lang::get('rainlab.blog::lang.category.delete_success'));
+ }
+
+ return $this->listRefresh();
+ }
+}
diff --git a/plugins/rainlab/blog/controllers/Posts.php b/plugins/rainlab/blog/controllers/Posts.php
new file mode 100644
index 00000000..311087f2
--- /dev/null
+++ b/plugins/rainlab/blog/controllers/Posts.php
@@ -0,0 +1,153 @@
+vars['postsTotal'] = Post::count();
+ $this->vars['postsPublished'] = Post::isPublished()->count();
+ $this->vars['postsDrafts'] = $this->vars['postsTotal'] - $this->vars['postsPublished'];
+
+ $this->asExtension('ListController')->index();
+ }
+
+ public function create()
+ {
+ BackendMenu::setContextSideMenu('new_post');
+
+ $this->bodyClass = 'compact-container';
+ $this->addCss('/plugins/rainlab/blog/assets/css/rainlab.blog-preview.css');
+ $this->addJs('/plugins/rainlab/blog/assets/js/post-form.js');
+
+ return $this->asExtension('FormController')->create();
+ }
+
+ public function update($recordId = null)
+ {
+ $this->bodyClass = 'compact-container';
+ $this->addCss('/plugins/rainlab/blog/assets/css/rainlab.blog-preview.css');
+ $this->addJs('/plugins/rainlab/blog/assets/js/post-form.js');
+
+ return $this->asExtension('FormController')->update($recordId);
+ }
+
+ public function export()
+ {
+ $this->addCss('/plugins/rainlab/blog/assets/css/rainlab.blog-export.css');
+
+ return $this->asExtension('ImportExportController')->export();
+ }
+
+ public function listExtendQuery($query)
+ {
+ if (!$this->user->hasAnyAccess(['rainlab.blog.access_other_posts'])) {
+ $query->where('user_id', $this->user->id);
+ }
+ }
+
+ public function formExtendQuery($query)
+ {
+ if (!$this->user->hasAnyAccess(['rainlab.blog.access_other_posts'])) {
+ $query->where('user_id', $this->user->id);
+ }
+ }
+
+ public function formExtendFieldsBefore($widget)
+ {
+ if (!$model = $widget->model) {
+ return;
+ }
+
+ if ($model instanceof Post && $model->isClassExtendedWith('RainLab.Translate.Behaviors.TranslatableModel')) {
+ $widget->secondaryTabs['fields']['content']['type'] = 'RainLab\Blog\FormWidgets\MLBlogMarkdown';
+ }
+
+ if (BlogSettings::get('use_legacy_editor', false)) {
+ $widget->secondaryTabs['fields']['content']['legacyMode'] = true;
+ }
+ }
+
+ public function index_onDelete()
+ {
+ if (($checkedIds = post('checked')) && is_array($checkedIds) && count($checkedIds)) {
+
+ foreach ($checkedIds as $postId) {
+ if ((!$post = Post::find($postId)) || !$post->canEdit($this->user)) {
+ continue;
+ }
+
+ $post->delete();
+ }
+
+ Flash::success(Lang::get('rainlab.blog::lang.post.delete_success'));
+ }
+
+ return $this->listRefresh();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function listInjectRowClass($record, $definition = null)
+ {
+ if (!$record->published) {
+ return 'safe disabled';
+ }
+ }
+
+ public function formBeforeCreate($model)
+ {
+ $model->user_id = $this->user->id;
+ }
+
+ public function onRefreshPreview()
+ {
+ $data = post('Post');
+
+ $previewHtml = Post::formatHtml($data['content'], true);
+
+ return [
+ 'preview' => $previewHtml
+ ];
+ }
+}
diff --git a/plugins/rainlab/blog/controllers/categories/_list_toolbar.htm b/plugins/rainlab/blog/controllers/categories/_list_toolbar.htm
new file mode 100644
index 00000000..fe438117
--- /dev/null
+++ b/plugins/rainlab/blog/controllers/categories/_list_toolbar.htm
@@ -0,0 +1,25 @@
+
diff --git a/plugins/rainlab/blog/controllers/categories/_reorder_toolbar.htm b/plugins/rainlab/blog/controllers/categories/_reorder_toolbar.htm
new file mode 100644
index 00000000..de33eb7b
--- /dev/null
+++ b/plugins/rainlab/blog/controllers/categories/_reorder_toolbar.htm
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/plugins/rainlab/blog/controllers/categories/config_form.yaml b/plugins/rainlab/blog/controllers/categories/config_form.yaml
new file mode 100644
index 00000000..250c41aa
--- /dev/null
+++ b/plugins/rainlab/blog/controllers/categories/config_form.yaml
@@ -0,0 +1,16 @@
+# ===================================
+# Form Behavior Config
+# ===================================
+
+name: rainlab.blog::lang.blog.create_category
+form: $/rainlab/blog/models/category/fields.yaml
+modelClass: RainLab\Blog\Models\Category
+defaultRedirect: rainlab/blog/categories
+
+create:
+ redirect: rainlab/blog/categories/update/:id
+ redirectClose: rainlab/blog/categories
+
+update:
+ redirect: rainlab/blog/categories
+ redirectClose: rainlab/blog/categories
diff --git a/plugins/rainlab/blog/controllers/categories/config_list.yaml b/plugins/rainlab/blog/controllers/categories/config_list.yaml
new file mode 100644
index 00000000..c2b6b205
--- /dev/null
+++ b/plugins/rainlab/blog/controllers/categories/config_list.yaml
@@ -0,0 +1,43 @@
+# ===================================
+# List Behavior Config
+# ===================================
+
+# Model List Column configuration
+list: $/rainlab/blog/models/category/columns.yaml
+
+# Model Class name
+modelClass: RainLab\Blog\Models\Category
+
+# List Title
+title: rainlab.blog::lang.categories.list_title
+
+# Link URL for each record
+recordUrl: rainlab/blog/categories/update/:id
+
+# Message to display if the list is empty
+noRecordsMessage: backend::lang.list.no_records
+
+# Records to display per page
+recordsPerPage: 5
+
+# Display checkboxes next to each record
+showCheckboxes: true
+
+# Toolbar widget configuration
+toolbar:
+ # Partial for toolbar buttons
+ buttons: list_toolbar
+
+ # Search widget configuration
+ search:
+ prompt: backend::lang.list.search_prompt
+
+# Legacy (v1)
+showTree: true
+
+# Reordering
+structure:
+ showTree: true
+ showReorder: true
+ treeExpanded: true
+ maxDepth: 0
diff --git a/plugins/rainlab/blog/controllers/categories/config_reorder.yaml b/plugins/rainlab/blog/controllers/categories/config_reorder.yaml
new file mode 100644
index 00000000..392b0c51
--- /dev/null
+++ b/plugins/rainlab/blog/controllers/categories/config_reorder.yaml
@@ -0,0 +1,17 @@
+# ===================================
+# Reorder Behavior Config
+# ===================================
+
+# Reorder Title
+title: rainlab.blog::lang.category.reorder
+
+# Attribute name
+nameFrom: name
+
+# Model Class name
+modelClass: RainLab\Blog\Models\Category
+
+# Toolbar widget configuration
+toolbar:
+ # Partial for toolbar buttons
+ buttons: reorder_toolbar
\ No newline at end of file
diff --git a/plugins/rainlab/blog/controllers/categories/create.htm b/plugins/rainlab/blog/controllers/categories/create.htm
new file mode 100644
index 00000000..ea47c339
--- /dev/null
+++ b/plugins/rainlab/blog/controllers/categories/create.htm
@@ -0,0 +1,46 @@
+
+
+
+
+fatalError): ?>
+
+ = Form::open(['class' => 'layout']) ?>
+
+
+ = $this->formRender() ?>
+
+
+
+
+ = Form::close() ?>
+
+
+ = e(trans($this->fatalError)) ?>
+ = e(trans('rainlab.blog::lang.category.return_to_categories')) ?>
+
diff --git a/plugins/rainlab/blog/controllers/categories/index.htm b/plugins/rainlab/blog/controllers/categories/index.htm
new file mode 100644
index 00000000..766877d9
--- /dev/null
+++ b/plugins/rainlab/blog/controllers/categories/index.htm
@@ -0,0 +1,2 @@
+
+= $this->listRender() ?>
diff --git a/plugins/rainlab/blog/controllers/categories/reorder.htm b/plugins/rainlab/blog/controllers/categories/reorder.htm
new file mode 100644
index 00000000..407face1
--- /dev/null
+++ b/plugins/rainlab/blog/controllers/categories/reorder.htm
@@ -0,0 +1 @@
+= $this->reorderRender() ?>
\ No newline at end of file
diff --git a/plugins/rainlab/blog/controllers/categories/update.htm b/plugins/rainlab/blog/controllers/categories/update.htm
new file mode 100644
index 00000000..7949c329
--- /dev/null
+++ b/plugins/rainlab/blog/controllers/categories/update.htm
@@ -0,0 +1,54 @@
+
+
+
+
+fatalError): ?>
+
+ = Form::open(['class' => 'layout']) ?>
+
+
+ = $this->formRender() ?>
+
+
+
+ = Form::close() ?>
+
+
+ = e(trans($this->fatalError)) ?>
+ = e(trans('rainlab.blog::lang.category.return_to_categories')) ?>
+
diff --git a/plugins/rainlab/blog/controllers/posts/_list_toolbar.htm b/plugins/rainlab/blog/controllers/posts/_list_toolbar.htm
new file mode 100644
index 00000000..d0f93f67
--- /dev/null
+++ b/plugins/rainlab/blog/controllers/posts/_list_toolbar.htm
@@ -0,0 +1,37 @@
+
diff --git a/plugins/rainlab/blog/controllers/posts/_post_toolbar.htm b/plugins/rainlab/blog/controllers/posts/_post_toolbar.htm
new file mode 100644
index 00000000..96951e4c
--- /dev/null
+++ b/plugins/rainlab/blog/controllers/posts/_post_toolbar.htm
@@ -0,0 +1,56 @@
+formGetContext() == 'create';
+ $pageUrl = isset($pageUrl) ? $pageUrl : null;
+?>
+
diff --git a/plugins/rainlab/blog/controllers/posts/config_form.yaml b/plugins/rainlab/blog/controllers/posts/config_form.yaml
new file mode 100644
index 00000000..10c090f0
--- /dev/null
+++ b/plugins/rainlab/blog/controllers/posts/config_form.yaml
@@ -0,0 +1,16 @@
+# ===================================
+# Form Behavior Config
+# ===================================
+
+name: rainlab.blog::lang.blog.create_post
+form: $/rainlab/blog/models/post/fields.yaml
+modelClass: RainLab\Blog\Models\Post
+defaultRedirect: rainlab/blog/posts
+
+create:
+ redirect: rainlab/blog/posts/update/:id
+ redirectClose: rainlab/blog/posts
+
+update:
+ redirect: rainlab/blog/posts
+ redirectClose: rainlab/blog/posts
diff --git a/plugins/rainlab/blog/controllers/posts/config_import_export.yaml b/plugins/rainlab/blog/controllers/posts/config_import_export.yaml
new file mode 100644
index 00000000..e0cf8c5b
--- /dev/null
+++ b/plugins/rainlab/blog/controllers/posts/config_import_export.yaml
@@ -0,0 +1,41 @@
+# ===================================
+# Import/Export Behavior Config
+# ===================================
+
+import:
+ # Page title
+ title: rainlab.blog::lang.posts.import_post
+
+ # Import List Column configuration
+ list: $/rainlab/blog/models/postimport/columns.yaml
+
+ # Import Form Field configuration
+ form: $/rainlab/blog/models/postimport/fields.yaml
+
+ # Import Model class
+ modelClass: RainLab\Blog\Models\PostImport
+
+ # Redirect when finished
+ redirect: rainlab/blog/posts
+
+ # Required permissions
+ permissions: rainlab.blog.access_import_export
+
+export:
+ # Page title
+ title: rainlab.blog::lang.posts.export_post
+
+ # Output file name
+ fileName: posts.csv
+
+ # Export List Column configuration
+ list: $/rainlab/blog/models/postexport/columns.yaml
+
+ # Export Model class
+ modelClass: RainLab\Blog\Models\PostExport
+
+ # Redirect when finished
+ redirect: rainlab/blog/posts
+
+ # Required permissions
+ permissions: rainlab.blog.access_import_export
diff --git a/plugins/rainlab/blog/controllers/posts/config_list.yaml b/plugins/rainlab/blog/controllers/posts/config_list.yaml
new file mode 100644
index 00000000..62c7d237
--- /dev/null
+++ b/plugins/rainlab/blog/controllers/posts/config_list.yaml
@@ -0,0 +1,47 @@
+# ===================================
+# List Behavior Config
+# ===================================
+
+# Model List Column configuration
+list: $/rainlab/blog/models/post/columns.yaml
+
+# Filter widget configuration
+filter: $/rainlab/blog/models/post/scopes.yaml
+
+# Model Class name
+modelClass: RainLab\Blog\Models\Post
+
+# List Title
+title: rainlab.blog::lang.posts.list_title
+
+# Link URL for each record
+recordUrl: rainlab/blog/posts/update/:id
+
+# Message to display if the list is empty
+noRecordsMessage: backend::lang.list.no_records
+
+# Records to display per page
+recordsPerPage: 25
+
+# Displays the list column set up button
+showSetup: true
+
+# Displays the sorting link on each column
+showSorting: true
+
+# Default sorting column
+defaultSort:
+ column: published_at
+ direction: desc
+
+# Display checkboxes next to each record
+showCheckboxes: true
+
+# Toolbar widget configuration
+toolbar:
+ # Partial for toolbar buttons
+ buttons: list_toolbar
+
+ # Search widget configuration
+ search:
+ prompt: backend::lang.list.search_prompt
diff --git a/plugins/rainlab/blog/controllers/posts/create.htm b/plugins/rainlab/blog/controllers/posts/create.htm
new file mode 100644
index 00000000..e6cd6a55
--- /dev/null
+++ b/plugins/rainlab/blog/controllers/posts/create.htm
@@ -0,0 +1,23 @@
+fatalError): ?>
+
+
+ = Form::open([
+ 'class' => 'layout',
+ 'data-change-monitor' => 'true',
+ 'data-window-close-confirm' => e(trans('rainlab.blog::lang.post.close_confirm')),
+ 'id' => 'post-form'
+ ]) ?>
+ = $this->formRender() ?>
+
+ = Form::close() ?>
+
+
+
+
+ = Block::placeholder('breadcrumb') ?>
+
+
+
diff --git a/plugins/rainlab/blog/controllers/posts/export.htm b/plugins/rainlab/blog/controllers/posts/export.htm
new file mode 100644
index 00000000..1023caf0
--- /dev/null
+++ b/plugins/rainlab/blog/controllers/posts/export.htm
@@ -0,0 +1,27 @@
+
+
+
+
+= Form::open(['class' => 'layout']) ?>
+
+
+ = $this->exportRender() ?>
+
+
+
+
+= Form::close() ?>
diff --git a/plugins/rainlab/blog/controllers/posts/import.htm b/plugins/rainlab/blog/controllers/posts/import.htm
new file mode 100644
index 00000000..e33d6daa
--- /dev/null
+++ b/plugins/rainlab/blog/controllers/posts/import.htm
@@ -0,0 +1,25 @@
+
+
+
+
+= Form::open(['class' => 'layout']) ?>
+
+
+ = $this->importRender() ?>
+
+
+
+
+ = e(trans('rainlab.blog::lang.posts.import_post')) ?>
+
+
+
+= Form::close() ?>
diff --git a/plugins/rainlab/blog/controllers/posts/index.htm b/plugins/rainlab/blog/controllers/posts/index.htm
new file mode 100644
index 00000000..ea43a363
--- /dev/null
+++ b/plugins/rainlab/blog/controllers/posts/index.htm
@@ -0,0 +1 @@
+= $this->listRender() ?>
diff --git a/plugins/rainlab/blog/controllers/posts/update.htm b/plugins/rainlab/blog/controllers/posts/update.htm
new file mode 100644
index 00000000..3f82c198
--- /dev/null
+++ b/plugins/rainlab/blog/controllers/posts/update.htm
@@ -0,0 +1,25 @@
+fatalError): ?>
+
+
+ = Form::open([
+ 'class' => 'layout',
+ 'data-change-monitor' => 'true',
+ 'data-window-close-confirm' => e(trans('rainlab.blog::lang.post.close_confirm')),
+ 'id' => 'post-form'
+ ]) ?>
+ = $this->formRender() ?>
+
+ = Form::close() ?>
+
+
+
+
+
+ = Block::placeholder('breadcrumb') ?>
+
+
+
+
diff --git a/plugins/rainlab/blog/formwidgets/BlogMarkdown.php b/plugins/rainlab/blog/formwidgets/BlogMarkdown.php
new file mode 100644
index 00000000..caff7189
--- /dev/null
+++ b/plugins/rainlab/blog/formwidgets/BlogMarkdown.php
@@ -0,0 +1,133 @@
+viewPath = base_path().'/modules/backend/formwidgets/markdowneditor/partials';
+
+ $this->checkUploadPostback();
+
+ parent::init();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function loadAssets()
+ {
+ $this->assetPath = '/modules/backend/formwidgets/markdowneditor/assets';
+ parent::loadAssets();
+ }
+
+ /**
+ * Disable HTML cleaning on the widget level since the PostModel will handle it
+ *
+ * @return boolean
+ */
+ protected function shouldCleanHtml()
+ {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function onRefresh()
+ {
+ $content = post($this->formField->getName());
+
+ $previewHtml = PostModel::formatHtml($content, true);
+
+ return [
+ 'preview' => $previewHtml
+ ];
+ }
+
+ /**
+ * Handle images being uploaded to the blog post
+ *
+ * @return void
+ */
+ protected function checkUploadPostback()
+ {
+ if (!post('X_BLOG_IMAGE_UPLOAD')) {
+ return;
+ }
+
+ $uploadedFileName = null;
+
+ try {
+ $uploadedFile = Input::file('file');
+
+ if ($uploadedFile)
+ $uploadedFileName = $uploadedFile->getClientOriginalName();
+
+ $validationRules = ['max:'.File::getMaxFilesize()];
+ $validationRules[] = 'mimes:jpg,jpeg,bmp,png,gif';
+
+ $validation = Validator::make(
+ ['file_data' => $uploadedFile],
+ ['file_data' => $validationRules]
+ );
+
+ if ($validation->fails()) {
+ throw new ValidationException($validation);
+ }
+
+ if (!$uploadedFile->isValid()) {
+ throw new SystemException(Lang::get('cms::lang.asset.file_not_valid'));
+ }
+
+ $fileRelation = $this->model->content_images();
+
+ $file = new File();
+ $file->data = $uploadedFile;
+ $file->is_public = true;
+ $file->save();
+
+ $fileRelation->add($file, $this->sessionKey);
+ $result = [
+ 'file' => $uploadedFileName,
+ 'path' => $file->getPath()
+ ];
+
+ $response = Response::make()->setContent($result);
+ $this->controller->setResponse($response);
+
+ } catch (Exception $ex) {
+ $message = $uploadedFileName
+ ? Lang::get('cms::lang.asset.error_uploading_file', ['name' => $uploadedFileName, 'error' => $ex->getMessage()])
+ : $ex->getMessage();
+
+ $result = [
+ 'error' => $message,
+ 'file' => $uploadedFileName
+ ];
+
+ $response = Response::make()->setContent($result);
+ $this->controller->setResponse($response);
+ }
+ }
+}
diff --git a/plugins/rainlab/blog/formwidgets/MLBlogMarkdown.php b/plugins/rainlab/blog/formwidgets/MLBlogMarkdown.php
new file mode 100644
index 00000000..5da6fa90
--- /dev/null
+++ b/plugins/rainlab/blog/formwidgets/MLBlogMarkdown.php
@@ -0,0 +1,137 @@
+initLocale();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function render()
+ {
+ $this->actAsParent();
+ $parentContent = parent::render();
+ $this->actAsParent(false);
+
+ if (!$this->isAvailable) {
+ return $parentContent;
+ }
+
+ $this->vars['markdowneditor'] = $parentContent;
+
+ $this->actAsControl(true);
+
+ return $this->makePartial('mlmarkdowneditor');
+ }
+
+ public function prepareVars()
+ {
+ parent::prepareVars();
+ $this->prepareLocaleVars();
+ }
+
+ /**
+ * Returns an array of translated values for this field
+ * @param $value
+ * @return array
+ */
+ public function getSaveValue($value)
+ {
+ $localeData = $this->getLocaleSaveData();
+
+ /*
+ * Set the translated values to the model
+ */
+ if ($this->model->methodExists('setAttributeTranslated')) {
+ foreach ($localeData as $locale => $value) {
+ $this->model->setAttributeTranslated('content', $value, $locale);
+
+ $this->model->setAttributeTranslated(
+ 'content_html',
+ Post::formatHtml($value),
+ $locale
+ );
+ }
+ }
+
+ return array_get($localeData, $this->defaultLocale->code, $value);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function loadAssets()
+ {
+ $this->actAsParent();
+ parent::loadAssets();
+ $this->actAsParent(false);
+
+ if (Locale::isAvailable()) {
+ $this->loadLocaleAssets();
+
+ $this->actAsControl(true);
+ $this->addJs('js/mlmarkdowneditor.js');
+ $this->actAsControl(false);
+ }
+ }
+
+ protected function actAsParent($switch = true)
+ {
+ if ($switch) {
+ $this->originalAssetPath = $this->assetPath;
+ $this->originalViewPath = $this->viewPath;
+ $this->assetPath = '/modules/backend/formwidgets/markdowneditor/assets';
+ $this->viewPath = base_path('/modules/backend/formwidgets/markdowneditor/partials');
+ }
+ else {
+ $this->assetPath = $this->originalAssetPath;
+ $this->viewPath = $this->originalViewPath;
+ }
+ }
+
+ protected function actAsControl($switch = true)
+ {
+ if ($switch) {
+ $this->originalAssetPath = $this->assetPath;
+ $this->originalViewPath = $this->viewPath;
+ $this->assetPath = '/plugins/rainlab/translate/formwidgets/mlmarkdowneditor/assets';
+ $this->viewPath = base_path('/plugins/rainlab/translate/formwidgets/mlmarkdowneditor/partials');
+ }
+ else {
+ $this->assetPath = $this->originalAssetPath;
+ $this->viewPath = $this->originalViewPath;
+ }
+ }
+}
diff --git a/plugins/rainlab/blog/lang/bg/lang.php b/plugins/rainlab/blog/lang/bg/lang.php
new file mode 100644
index 00000000..6e5161df
--- /dev/null
+++ b/plugins/rainlab/blog/lang/bg/lang.php
@@ -0,0 +1,100 @@
+ [
+ 'name' => 'Блог',
+ 'description' => 'Стабилната блог платформа.'
+ ],
+ 'blog' => [
+ 'menu_label' => 'Блог',
+ 'menu_description' => 'управление на публикациите',
+ 'posts' => 'публикации',
+ 'create_post' => 'създай публикация',
+ 'categories' => 'категории',
+ 'create_category' => 'създай категория',
+ 'tab' => 'Блог',
+ 'access_posts' => 'управление на публикациите',
+ 'access_categories' => 'управление на категории',
+ 'access_other_posts' => 'управление на други потребители публикации в блога',
+ 'delete_confirm' => 'Сигурни ли сте?',
+ 'chart_published' => 'Публикувано',
+ 'chart_drafts' => 'Чернови',
+ 'chart_total' => 'Общо'
+ ],
+ 'posts' => [
+ 'list_title' => 'Управление публикациите в блога',
+ 'filter_category' => 'Категория',
+ 'filter_published' => 'Скрий публикуваните',
+ 'new_post' => 'Нова публикация'
+ ],
+ 'post' => [
+ 'title' => 'Заглавие',
+ 'title_placeholder' => 'Ново заглавие на публикацията',
+ 'slug' => 'Slug',
+ 'slug_placeholder' => 'нов slug на публикацията',
+ 'categories' => 'Категории',
+ 'created' => 'Създаден',
+ 'updated' => 'Обновен',
+ 'published' => 'Публикуван',
+ 'published_validation' => 'Моля, посочете дата на публикуване',
+ 'tab_edit' => 'Промяна',
+ 'tab_categories' => 'Категории',
+ 'categories_comment' => 'Изберете категории към който пренадлежи публикацията ',
+ 'categories_placeholder' => 'Няма категирии, Създайте първата?!',
+ 'tab_manage' => 'Управление',
+ 'published_on' => 'публикувано в',
+ 'excerpt' => 'Откъс',
+ 'featured_images' => 'Избрани снимки',
+ 'delete_confirm' => 'Наистина ли искате да изтриете тази публикация?',
+ 'close_confirm' => 'Публикацията не е запазена.',
+ 'return_to_posts' => 'Върни ме към всички публикации'
+ ],
+ 'categories' => [
+ 'list_title' => 'Управление категориите в блога',
+ 'new_category' => 'Нова категория',
+ 'uncategorized' => 'Без категория'
+ ],
+ 'category' => [
+ 'name' => 'Име',
+ 'name_placeholder' => 'Ново име на категорията',
+ 'slug' => 'Slug',
+ 'slug_placeholder' => 'нов slug на категотията',
+ 'posts' => 'публикации',
+ 'delete_confirm' => 'Наистина ли искате да изтриете тази категория?',
+ 'return_to_categories' => 'Върни ме към всички категории'
+ ],
+ 'settings' => [
+ 'category_title' => 'Списък с категории',
+ 'category_description' => 'Показва списък с категориите на блога.',
+ 'category_slug' => 'категория slug',
+ 'category_slug_description' => "Look up the blog category using the supplied slug value. This property is used by the default component partial for marking the currently active category.",
+ 'category_display_empty' => 'Показване на празни категории',
+ 'category_display_empty_description' => 'Показване на категории, които нямат никакви публикации.',
+ 'category_page' => 'Страница на категория',
+ 'category_page_description' => 'Име на страницата за категирия. Това се използва подразбиране от компонента.',
+ 'post_title' => 'Публикация',
+ 'post_description' => 'Показване на Публикациите в блога на страницата.',
+ 'post_slug' => 'Post slug',
+ 'post_slug_description' => "Търсене на публикации по зададен slug.",
+ 'post_category' => 'Страница за Категория',
+ 'post_category_description' => 'Име на страница за категория за генериране на линк.Това се използва подразбиране от компонента.',
+ 'posts_title' => 'Лист с Публикации',
+ 'posts_description' => 'Показване на лист с публикации на страницата.',
+ 'posts_pagination' => 'Номер на страницата',
+ 'posts_pagination_description' => 'Тази стойност се използва за определяне на коя страница е потребителя.',
+ 'posts_filter' => 'Филтер Категория',
+ 'posts_filter_description' => 'Въведи slug на категория или URL адрес за филтриране по. Оставете празно за да се покажат всички публикации.',
+ 'posts_per_page' => 'Публикации на страница',
+ 'posts_per_page_validation' => 'Невалиден формат за публикации на страница',
+ 'posts_no_posts' => 'Няма публикации',
+ 'posts_no_posts_description' => 'Съобщение което да се покаже, в случай ,че няма публикации за показване.Това се използва подразбиране от компонента.',
+ 'posts_order' => 'подреждане на публикации',
+ 'posts_order_description' => 'Атрибут по който да бъдат подредени публикациите',
+ 'posts_category' => 'страница на категориите',
+ 'posts_category_description' => 'Име на страницата за категории , за "публикувано в". Това се използва подразбиране от компонента.',
+ 'posts_post' => 'Post page',
+ 'posts_post_description' => 'Име на страницата за публикации "Прочетете повече". Това се използва подразбиране от компонента.',
+ 'posts_except_post' => 'Except post',
+ 'posts_except_post_description' => 'Enter ID/URL or variable with post ID/URL you want to except',
+ ]
+];
diff --git a/plugins/rainlab/blog/lang/cs/lang.php b/plugins/rainlab/blog/lang/cs/lang.php
new file mode 100644
index 00000000..553d1252
--- /dev/null
+++ b/plugins/rainlab/blog/lang/cs/lang.php
@@ -0,0 +1,154 @@
+ [
+ 'name' => 'Blog',
+ 'description' => 'Robustní blogová platforma.'
+ ],
+ 'blog' => [
+ 'menu_label' => 'Blog',
+ 'menu_description' => 'Správa blogových příspěvků',
+ 'posts' => 'Příspěvky',
+ 'create_post' => 'Příspěvek',
+ 'categories' => 'Kategorie',
+ 'create_category' => 'Kategorie příspěvků',
+ 'tab' => 'Blog',
+ 'access_posts' => 'Správa blogových příspěvků',
+ 'access_categories' => 'Správa blogových kategorií',
+ 'access_other_posts' => 'Správa příspěvků ostatních uživatelů',
+ 'access_import_export' => 'Možnost importu a exportu příspěvků',
+ 'access_publish' => 'Možnost publikovat příspěvky',
+ 'delete_confirm' => 'Jste si jistí?',
+ 'chart_published' => 'Publikované',
+ 'chart_drafts' => 'Návrhy',
+ 'chart_total' => 'Celkem',
+ ],
+ 'posts' => [
+ 'list_title' => 'Správa blogových příspěvků',
+ 'filter_category' => 'Kategorie',
+ 'filter_published' => 'Schovat publikované',
+ 'filter_date' => 'Datum',
+ 'new_post' => 'Nový příspěvek',
+ 'export_post' => 'Export příspěvků',
+ 'import_post' => 'Import příspěvků',
+ ],
+ 'post' => [
+ 'title' => 'Název',
+ 'title_placeholder' => 'Zadejte název',
+ 'content' => 'Obsah',
+ 'content_html' => 'HTML obsah',
+ 'slug' => 'URL příspěvku',
+ 'slug_placeholder' => 'zadejte-url-prispevku',
+ 'categories' => 'Kategorie',
+ 'author_email' => 'E-mail autora',
+ 'created' => 'Vytvořeno',
+ 'created_date' => 'Vytvořeno dne',
+ 'updated' => 'Upraveno',
+ 'updated_date' => 'Upraveno dne',
+ 'published' => 'Publikováno',
+ 'published_date' => 'Publikováno dne',
+ 'published_validation' => 'Zadejte prosím datum publikace příspěvku',
+ 'tab_edit' => 'Upravit',
+ 'tab_categories' => 'Kategorie',
+ 'categories_comment' => 'Vyberte kategorie do kterých příspěvek patří',
+ 'categories_placeholder' => 'Nejsou zde žádné kategorie, nejdříve musíte nějaké vytvořit!',
+ 'tab_manage' => 'Nastavení',
+ 'published_on' => 'Publikováno dne',
+ 'excerpt' => 'Perex příspěvku',
+ 'summary' => 'Shrnutí',
+ 'featured_images' => 'Obrázky',
+ 'delete_confirm' => 'Opravdu chcete smazat tento příspěvek?',
+ 'delete_success' => 'Vybrané příspěvky úspěšně odstraněny.',
+ 'close_confirm' => 'Příspěvek není uložený.',
+ 'return_to_posts' => 'Zpět na seznam příspěvků',
+ ],
+ 'categories' => [
+ 'list_title' => 'Správa blogových kategorií',
+ 'new_category' => 'Nová kategorie',
+ 'uncategorized' => 'Nezařazeno',
+ ],
+ 'category' => [
+ 'name' => 'Název',
+ 'name_placeholder' => 'Název nové kategorie',
+ 'description' => 'Popis',
+ 'slug' => 'URL kategorie',
+ 'slug_placeholder' => 'zadejte-url-kategorie',
+ 'posts' => 'Počet příspěvků',
+ 'delete_confirm' => 'Opravdu chcete smazat tuto kategorii?',
+ 'delete_success' => 'Vybrané kategorie úspěšně odstraněny.',
+ 'return_to_categories' => 'Zpět na seznam blogových kategorií',
+ 'reorder' => 'Změnit pořadí',
+ ],
+ 'menuitem' => [
+ 'blog_category' => 'Blogová kategorie',
+ 'all_blog_categories' => 'Všechny blogové kategorie',
+ 'blog_post' => 'Blogový příspěvek',
+ 'all_blog_posts' => 'Všechny blogové příspěvky',
+ 'category_blog_posts' => 'Blog category posts'
+ ],
+ 'settings' => [
+ 'category_title' => 'Seznam kategorií',
+ 'category_description' => 'Zobrazí na stránce seznam blogových kategorií.',
+ 'category_slug' => 'URL kategorie',
+ 'category_slug_description' => "Najde blogovou kategorii s tímto URL. Používá se pro zobrazení aktivní kategorie.",
+ 'category_display_empty' => 'Zobrazit prázdné kategorie',
+ 'category_display_empty_description' => 'Zobrazit kategorie bez blogových příspěvků.',
+ 'category_page' => 'Stránka kategorií',
+ 'category_page_description' => 'Vyberte stránku která slouží k zobrazení všech kategorií (nebo detailu kategorie).',
+ 'post_title' => 'Příspěvek',
+ 'post_description' => 'Zobrazí blogový příspěvek na stránce.',
+ 'post_slug' => 'URL příspěvku',
+ 'post_slug_description' => "Najde příspěvek dle zadané URL.",
+ 'post_category' => 'Stránka kategorie',
+ 'post_category_description' => 'Vyberte stránku která slouží k zobrazení všech kategorií (nebo detailu kategorie).',
+ 'posts_title' => 'Seznam příspěvků',
+ 'posts_description' => 'Zobrazí na stránce seznam posledních příspěvků na stránkách.',
+ 'posts_pagination' => 'Číslo stránky',
+ 'posts_pagination_description' => 'Číslo stránky určující na které stránce se uživatel nachází. Použito pro stránkování.',
+ 'posts_filter' => 'Filtr kategorií',
+ 'posts_filter_description' => 'Zadejte URL kategorie, nebo URL parametr pro filtrování příspěvků. Nechte prázdné pro zobrazení všech příspěvků.',
+ 'posts_per_page' => 'Příspěvků na stránku',
+ 'posts_per_page_validation' => 'Špatný formát počtu příspěvků na stránku, musí být zadáno jako číslo',
+ 'posts_no_posts' => 'Hláška prázdné stránky',
+ 'posts_no_posts_description' => 'Zpráva se zobrazí pokud se nepovede najít žádné články.',
+ 'posts_no_posts_default' => 'Nenalezeny žádné příspěvky',
+ 'posts_order' => 'Řazení článků',
+ 'posts_order_decription' => 'Nastaví řazení článků ve výpisu',
+ 'posts_category' => 'Stránka kategorií',
+ 'posts_category_description' => 'Vyberte stránku která slouží k zobrazení všech kategorií (nebo detailu kategorie).',
+ 'posts_post' => 'Stránka příspěvků',
+ 'posts_post_description' => 'Vyberte stránku která slouží k zobrazení článků (nebo detailu článku).',
+ 'posts_except_post' => 'Vyloučit příspěvěk',
+ 'posts_except_post_description' => 'Zadejte ID nebo URL příspěvku který chcete vyloučit',
+ 'posts_except_categories' => 'Vyloučené kategorie',
+ 'posts_except_categories_description' => 'Pro vyloučení kategorií zadejte čárkou oddělené URL příspěvků nebo proměnnou, která tento seznam obsahuje.',
+ 'rssfeed_blog' => 'Blogová stránka',
+ 'rssfeed_blog_description' => 'Name of the main blog page file for generating links. This property is used by the default component partial.',
+ 'rssfeed_title' => 'RSS Kanál',
+ 'rssfeed_description' => 'Vygeneruje RSS kanál který obsahuje blogové příspěvky.',
+ 'group_links' => 'Odkazy',
+ 'group_exceptions' => 'Výjimky'
+ ],
+ 'sorting' => [
+ 'title_asc' => 'Název (sestupně)',
+ 'title_desc' => 'Název (vzestupně)',
+ 'created_asc' => 'Vytvořeno (sestupně)',
+ 'created_desc' => 'Vytvořeno (vzestupně)',
+ 'updated_asc' => 'Upraveno (sestupně)',
+ 'updated_desc' => 'Upraveno (vzestupně)',
+ 'published_asc' => 'Publikováno (sestupně)',
+ 'published_desc' => 'Publikováno (vzestupně)',
+ 'random' => 'Náhodně'
+ ],
+ 'import' => [
+ 'update_existing_label' => 'Uprav existující příspěvky',
+ 'update_existing_comment' => 'Zvolte pokud chcete upravit příspěvky se stejným ID, názvem nebo URL.',
+ 'auto_create_categories_label' => 'VYtvořit kategorie ze souboru',
+ 'auto_create_categories_comment' => 'Chcete-li tuto funkci použít, měli byste se shodovat se sloupcem Kategorie, jinak vyberte výchozí kategorie, které chcete použít z níže uvedených položek.',
+ 'categories_label' => 'Kategorie',
+ 'categories_comment' => 'Vyberte kategorie ke kterým budou příspěvky přiřazeny (volitelné).',
+ 'default_author_label' => 'Výchozí autor příspěvků (volitelné)',
+ 'default_author_comment' => 'Import se pokusí použít existujícího autora, pokud odpovídá sloupci email, jinak se použije výše uvedený autor.',
+ 'default_author_placeholder' => '-- vyberte autora --'
+ ]
+];
diff --git a/plugins/rainlab/blog/lang/de/lang.php b/plugins/rainlab/blog/lang/de/lang.php
new file mode 100644
index 00000000..7e78057a
--- /dev/null
+++ b/plugins/rainlab/blog/lang/de/lang.php
@@ -0,0 +1,132 @@
+ [
+ 'name' => 'Blog',
+ 'description' => 'Eine robuste Blog Plattform.'
+ ],
+ 'blog' => [
+ 'menu_label' => 'Blog',
+ 'menu_description' => 'Blog Artikel bearbeiten',
+ 'posts' => 'Artikel',
+ 'create_post' => 'Blog Artikel',
+ 'categories' => 'Kategorien',
+ 'create_category' => 'Blog Kategorie',
+ 'tab' => 'Blog',
+ 'access_posts' => 'Blog Artikel verwalten',
+ 'access_categories' => 'Blog Kategorien verwalten',
+ 'access_other_posts' => 'Blog Artikel anderer Benutzer verwalten',
+ 'access_import_export' => 'Blog Artikel importieren oder exportieren',
+ 'access_publish' => 'Kann Artikel veröffentlichen',
+ 'delete_confirm' => 'Bist du sicher?',
+ 'chart_published' => 'Veröffentlicht',
+ 'chart_drafts' => 'Entwurf',
+ 'chart_total' => 'Gesamt'
+ ],
+ 'posts' => [
+ 'list_title' => 'Blog Artikel verwalten',
+ 'filter_category' => 'Kategorie',
+ 'filter_published' => 'Veröffentlichte ausblenden',
+ 'filter_date' => 'Date',
+ 'new_post' => 'Neuer Artikel',
+ 'export_post' => 'Exportiere Artikel',
+ 'import_post' => 'Importiere Artikel'
+ ],
+ 'post' => [
+ 'title' => 'Titel',
+ 'title_placeholder' => 'Neuer Titel',
+ 'content' => 'Inhalt',
+ 'content_html' => 'HTML-Inhalt',
+ 'slug' => 'Slug',
+ 'slug_placeholder' => 'neuer-artikel-slug',
+ 'categories' => 'Kategorien',
+ 'author_email' => 'Autor E-Mail',
+ 'created' => 'Erstellt',
+ 'created_date' => 'Erstellzeitpunkt',
+ 'updated' => 'Aktualisiert',
+ 'updated_date' => 'Aktualisierungszeitpunk',
+ 'published' => 'Veröffentlicht',
+ 'published_date' => 'Veröffentlichungszeitpunkt',
+ 'published_validation' => 'Bitte gebe das Datum der Veröffentlichung an',
+ 'tab_edit' => 'Bearbeiten',
+ 'tab_categories' => 'Kategorien',
+ 'categories_comment' => 'Wähle die zugehörigen Kategorien',
+ 'categories_placeholder' => 'Es existieren keine Kategorien. Bitte lege zuerst Kategorien an!',
+ 'tab_manage' => 'Verwalten',
+ 'published_on' => 'Veröffentlicht am',
+ 'excerpt' => 'Textauszug',
+ 'summary' => 'Zusammenfassung',
+ 'featured_images' => 'Zugehörige Bilder',
+ 'delete_confirm' => 'Möchtest du diesen Artikel wirklich löschen?',
+ 'close_confirm' => 'Der Artikel ist noch nicht gespeichert.',
+ 'return_to_posts' => 'Zurück zur Artikel-Übersicht',
+ 'posted_byline' => 'Veröffentlicht in :categories am :date',
+ 'posted_byline_no_categories' => 'Veröffentlicht am :date',
+ 'date_format' => 'd. F Y',
+ ],
+ 'categories' => [
+ 'list_title' => 'Blog Kategorien verwalten',
+ 'new_category' => 'Neue Kategorie',
+ 'uncategorized' => 'Allgemein'
+ ],
+ 'category' => [
+ 'name' => 'Name',
+ 'name_placeholder' => 'Neuer Kategorie Name',
+ 'description' => 'Beschreibung',
+ 'slug' => 'Slug',
+ 'slug_placeholder' => 'neuer-kategorie-slug',
+ 'posts' => 'Artikel',
+ 'delete_confirm' => 'Möchtest du die Kategorie wirklich löschen?',
+ 'return_to_categories' => 'Zurück zur Kategorie-Übersicht.',
+ 'reorder' => 'Kategorien sortieren'
+ ],
+ 'menuitem' => [
+ 'blog_category' => 'Blog Kategorie',
+ 'all_blog_categories' => 'Alle Blog Kategorien',
+ 'blog_post' => 'Blog Artikel',
+ 'all_blog_posts' => 'Alle Blog Artikel',
+ 'category_blog_posts' => 'Blog Kategorie Artikel'
+ ],
+ 'settings' => [
+ 'category_title' => 'Blog Kategorie-Übersicht',
+ 'category_description' => 'Zeigt eine Blog Kategorien-Übersicht.',
+ 'category_slug' => 'Slug Parametername',
+ 'category_slug_description' => 'Der URL-Routen-Parameter welcher verwendet wird um die aktuelle Kategorie zu bestimmen. Wird von der Standard-Komponente benötigt um die aktive Kategorie zu markieren.',
+ 'category_display_empty' => 'Leere Kategorien anzeigen',
+ 'category_display_empty_description' => 'Kategorien zeigen welche keine Artikel besitzen.',
+ 'category_page' => 'Kategorien Seite',
+ 'category_page_description' => 'Name der Kategorien-Seiten-Datei für die Kategorien Links. Wird von der Standard-Komponente benötigt.',
+ 'post_title' => 'Blog Artikel',
+ 'post_description' => 'Zeigt einen Blog Artikel auf der Seite.',
+ 'post_slug' => 'Slug Parametername',
+ 'post_slug_description' => 'Der URL-Routen-Parameter um den Post mittels "Slug" zu bestimmen.',
+ 'post_category' => 'Kategorien-Seite',
+ 'post_category_description' => 'Name der Kategorien-Seiten-Datei für Kategorie-Links.',
+ 'posts_title' => 'Blog Artikel-Übersicht',
+ 'posts_description' => 'Stellt eine Liste der neuesten Artikel auf der Seite dar.',
+ 'posts_pagination' => 'Blättern Parametername',
+ 'posts_pagination_description' => 'Der erwartete Parametername welcher für Seiten verwendet wird.',
+ 'posts_filter' => 'Kategorien-Filter',
+ 'posts_filter_description' => 'Bitte gebe ein Kategorien-Slug oder URL-Parameter an, mittels den die Artikel gefiltert werden. Wenn der Wert leer ist, werden alle Artikel angezeigt.',
+ 'posts_per_page' => 'Artikel pro Seite',
+ 'posts_per_page_validation' => 'Ungültiger "Artikel pro Seiten" Wert',
+ 'posts_no_posts' => 'Keine Artikel Nachricht',
+ 'posts_no_posts_description' => 'Nachricht welche dargestellt wird wenn keine Artikel vorhanden sind. Dieser Wert wird von der Standard-Komponente verwendet.',
+ 'posts_order' => 'Artikel Sortierung',
+ 'posts_order_description' => 'Attribute nach welchem Artikel sortiert werden.',
+ 'posts_category' => 'Kategorien-Seite',
+ 'posts_category_description' => 'Name der Kategorien-Seiten-Datei für "Veröffentlicht in" Kategorien-Links. Dieser Wert von der Standard-Komponente verwendet.',
+ 'posts_post' => 'Artikel Seite',
+ 'posts_post_description' => 'Name der Artikel-Seiten-Datei für die "Erfahre mehr" Links. Dieser Wert für von der Standard-Komponente verwendet.',
+ 'posts_except_post' => 'Artikel ausschließen',
+ 'posts_except_post_description' => 'Gebe direkt die ID/URL oder eine Variable mit der Artikel-ID/URL an um diesen Artikel auszuschließen. Dieser Wert für von der Standard-Komponente verwendet.',
+ 'posts_except_categories' => 'Kategorien ausschließen',
+ 'posts_except_categories_description' => 'Gebe eine kommagetrennte Liste von Kategorie-Slugs oder eine Variable mit einer solchen Liste an um deren Artikel auszuschließen. Die Dieser Wert für von der Standard-Komponente verwendet.',
+ 'rssfeed_blog' => 'Blog Seite',
+ 'rssfeed_blog_description' => 'Name der Artikel-Seiten-Datei für die Links. Dieser Wert für von der Standard-Komponente verwendet.',
+ 'rssfeed_title' => 'RSS-Feed',
+ 'rssfeed_description' => 'Erstellt einen RSS-Feed mit Artikeln aus dem Blog.',
+ 'group_links' => 'Links',
+ 'group_exceptions' => 'Ausnahmen'
+ ]
+];
diff --git a/plugins/rainlab/blog/lang/en/lang.php b/plugins/rainlab/blog/lang/en/lang.php
new file mode 100644
index 00000000..06e7e471
--- /dev/null
+++ b/plugins/rainlab/blog/lang/en/lang.php
@@ -0,0 +1,169 @@
+ [
+ 'name' => 'Blog',
+ 'description' => 'A robust blogging platform.'
+ ],
+ 'blog' => [
+ 'menu_label' => 'Blog',
+ 'menu_description' => 'Manage Blog Posts',
+ 'posts' => 'Posts',
+ 'create_post' => 'Blog post',
+ 'categories' => 'Categories',
+ 'create_category' => 'Blog category',
+ 'tab' => 'Blog',
+ 'access_posts' => 'Manage the blog posts',
+ 'access_categories' => 'Manage the blog categories',
+ 'access_other_posts' => 'Manage other users blog posts',
+ 'access_import_export' => 'Allowed to import and export posts',
+ 'access_publish' => 'Allowed to publish posts',
+ 'manage_settings' => 'Manage blog settings',
+ 'delete_confirm' => 'Are you sure?',
+ 'chart_published' => 'Published',
+ 'chart_drafts' => 'Drafts',
+ 'chart_total' => 'Total',
+ 'settings_description' => 'Manage blog settings',
+ 'show_all_posts_label' => 'Show All Posts to Backend Users',
+ 'show_all_posts_comment' => 'Display both published and unpublished posts on the frontend to backend users',
+ 'use_legacy_editor_label' => 'Use the Legacy Markdown Editor',
+ 'use_legacy_editor_comment' => 'Enable the older version of the markdown editor when using October CMS v2 and above',
+ 'tab_general' => 'General',
+ 'preview' => 'Preview'
+ ],
+ 'posts' => [
+ 'list_title' => 'Manage the blog posts',
+ 'filter_category' => 'Category',
+ 'filter_published' => 'Published',
+ 'filter_date' => 'Date',
+ 'new_post' => 'New Post',
+ 'export_post' => 'Export Posts',
+ 'import_post' => 'Import Posts'
+ ],
+ 'post' => [
+ 'title' => 'Title',
+ 'title_placeholder' => 'New post title',
+ 'content' => 'Content',
+ 'content_html' => 'HTML Content',
+ 'slug' => 'Slug',
+ 'slug_placeholder' => 'new-post-slug',
+ 'categories' => 'Categories',
+ 'author_email' => 'Author Email',
+ 'created' => 'Created',
+ 'created_date' => 'Created date',
+ 'updated' => 'Updated',
+ 'updated_date' => 'Updated date',
+ 'published' => 'Published',
+ 'published_by' => 'Published by',
+ 'current_user' => 'Current user',
+ 'published_date' => 'Published date',
+ 'published_validation' => 'Please specify the published date',
+ 'tab_edit' => 'Edit',
+ 'tab_categories' => 'Categories',
+ 'categories_comment' => 'Select categories the blog post belongs to',
+ 'categories_placeholder' => 'There are no categories, you should create one first!',
+ 'tab_manage' => 'Manage',
+ 'published_on' => 'Published on',
+ 'excerpt' => 'Excerpt',
+ 'summary' => 'Summary',
+ 'featured_images' => 'Featured Images',
+ 'delete_confirm' => 'Delete this post?',
+ 'delete_success' => 'Successfully deleted those posts.',
+ 'close_confirm' => 'The post is not saved.',
+ 'return_to_posts' => 'Return to posts list',
+ 'posted_byline' => 'Posted in :categories on :date.',
+ 'posted_byline_no_categories' => 'Posted on :date.',
+ 'date_format' => 'M d, Y',
+ ],
+ 'categories' => [
+ 'list_title' => 'Manage the blog categories',
+ 'new_category' => 'New Category',
+ 'uncategorized' => 'Uncategorized'
+ ],
+ 'category' => [
+ 'name' => 'Name',
+ 'name_placeholder' => 'New category name',
+ 'description' => 'Description',
+ 'slug' => 'Slug',
+ 'slug_placeholder' => 'new-category-slug',
+ 'posts' => 'Posts',
+ 'delete_confirm' => 'Delete this category?',
+ 'delete_success' => 'Successfully deleted those categories.',
+ 'return_to_categories' => 'Return to the blog category list',
+ 'reorder' => 'Reorder Categories'
+ ],
+ 'menuitem' => [
+ 'blog_category' => 'Blog category',
+ 'all_blog_categories' => 'All blog categories',
+ 'blog_post' => 'Blog post',
+ 'all_blog_posts' => 'All blog posts',
+ 'category_blog_posts' => 'Blog category posts'
+ ],
+ 'settings' => [
+ 'category_title' => 'Category List',
+ 'category_description' => 'Displays a list of blog categories on the page.',
+ 'category_slug' => 'Category slug',
+ 'category_slug_description' => "Look up the blog category using the supplied slug value. This property is used by the default component partial for marking the currently active category.",
+ 'category_display_empty' => 'Display empty categories',
+ 'category_display_empty_description' => 'Show categories that do not have any posts.',
+ 'category_page' => 'Category page',
+ 'category_page_description' => 'Name of the category page file for the category links. This property is used by the default component partial.',
+ 'post_title' => 'Post',
+ 'post_description' => 'Displays a blog post on the page.',
+ 'post_slug' => 'Post slug',
+ 'post_slug_description' => "Look up the blog post using the supplied slug value.",
+ 'post_category' => 'Category page',
+ 'post_category_description' => 'Name of the category page file for the category links. This property is used by the default component partial.',
+ 'posts_title' => 'Post List',
+ 'posts_description' => 'Displays a list of latest blog posts on the page.',
+ 'posts_pagination' => 'Page number',
+ 'posts_pagination_description' => 'This value is used to determine what page the user is on.',
+ 'posts_filter' => 'Category filter',
+ 'posts_filter_description' => 'Enter a category slug or URL parameter to filter the posts by. Leave empty to show all posts.',
+ 'posts_per_page' => 'Posts per page',
+ 'posts_per_page_validation' => 'Invalid format of the posts per page value',
+ 'posts_no_posts' => 'No posts message',
+ 'posts_no_posts_description' => 'Message to display in the blog post list in case if there are no posts. This property is used by the default component partial.',
+ 'posts_no_posts_default' => 'No posts found',
+ 'posts_order' => 'Post order',
+ 'posts_order_description' => 'Attribute on which the posts should be ordered',
+ 'posts_category' => 'Category page',
+ 'posts_category_description' => 'Name of the category page file for the "Posted into" category links. This property is used by the default component partial.',
+ 'posts_post' => 'Post page',
+ 'posts_post_description' => 'Name of the blog post page file for the "Learn more" links. This property is used by the default component partial.',
+ 'posts_except_post' => 'Except post',
+ 'posts_except_post_description' => 'Enter ID/URL or variable with post ID/URL you want to exclude. You may use a comma-separated list to specify multiple posts.',
+ 'posts_except_post_validation' => 'Post exceptions must be a single slug or ID, or a comma-separated list of slugs and IDs',
+ 'posts_except_categories' => 'Except categories',
+ 'posts_except_categories_description' => 'Enter a comma-separated list of category slugs or variable with such a list of categories you want to exclude',
+ 'posts_except_categories_validation' => 'Category exceptions must be a single category slug, or a comma-separated list of slugs',
+ 'rssfeed_blog' => 'Blog page',
+ 'rssfeed_blog_description' => 'Name of the main blog page file for generating links. This property is used by the default component partial.',
+ 'rssfeed_title' => 'RSS Feed',
+ 'rssfeed_description' => 'Generates an RSS feed containing posts from the blog.',
+ 'group_links' => 'Links',
+ 'group_exceptions' => 'Exceptions'
+ ],
+ 'sorting' => [
+ 'title_asc' => 'Title (ascending)',
+ 'title_desc' => 'Title (descending)',
+ 'created_asc' => 'Created (ascending)',
+ 'created_desc' => 'Created (descending)',
+ 'updated_asc' => 'Updated (ascending)',
+ 'updated_desc' => 'Updated (descending)',
+ 'published_asc' => 'Published (ascending)',
+ 'published_desc' => 'Published (descending)',
+ 'random' => 'Random'
+ ],
+ 'import' => [
+ 'update_existing_label' => 'Update existing posts',
+ 'update_existing_comment' => 'Check this box to update posts that have exactly the same ID, title or slug.',
+ 'auto_create_categories_label' => 'Create categories specified in the import file',
+ 'auto_create_categories_comment' => 'You should match the Categories column to use this feature, otherwise select the default categories to use from the items below.',
+ 'categories_label' => 'Categories',
+ 'categories_comment' => 'Select the categories that imported posts will belong to (optional).',
+ 'default_author_label' => 'Default post author (optional)',
+ 'default_author_comment' => 'The import will try to use an existing author if you match the Author Email column, otherwise the author specified above is used.',
+ 'default_author_placeholder' => '-- select author --'
+ ]
+];
diff --git a/plugins/rainlab/blog/lang/es/lang.php b/plugins/rainlab/blog/lang/es/lang.php
new file mode 100644
index 00000000..d34e8e4c
--- /dev/null
+++ b/plugins/rainlab/blog/lang/es/lang.php
@@ -0,0 +1,166 @@
+ [
+ 'name' => 'Blog',
+ 'description' => 'Una plataforma robusta de blogging.'
+ ],
+ 'blog' => [
+ 'menu_label' => 'Blog',
+ 'menu_description' => 'Administrar Publicaciones',
+ 'posts' => 'Publicaciones',
+ 'create_post' => 'Crear publicación',
+ 'categories' => 'Categorías',
+ 'create_category' => 'Categoría',
+ 'tab' => 'Blog',
+ 'access_posts' => 'Administrar las publicaciones',
+ 'access_categories' => 'Administrar las categorías',
+ 'access_other_posts' => 'Administrar publicaciones de otros usuarios',
+ 'access_import_export' => 'Autorizado para importar y exportar publicaciones',
+ 'access_publish' => 'Autorizado para publicar publicaciones',
+ 'manage_settings' => 'Administrar configuración del blog',
+ 'delete_confirm' => '¿Está seguro?',
+ 'chart_published' => 'Publicado',
+ 'chart_drafts' => 'Borradores',
+ 'chart_total' => 'Total',
+ 'settings_description' => 'Administrar configuración del blog',
+ 'show_all_posts_label' => 'Mostrar todas las publicaciones a los usuarios de backend',
+ 'show_all_posts_comment' => 'Mostrar las publicaciones publicados y los borradores a los usuarios de backend',
+ 'tab_general' => 'General'
+ ],
+ 'posts' => [
+ 'list_title' => 'Administrar publicaciones',
+ 'filter_category' => 'Categoría',
+ 'filter_published' => 'Publicado',
+ 'filter_date' => 'Fecha',
+ 'new_post' => 'Nueva publicación',
+ 'export_post' => 'Exportar publicaciones',
+ 'import_post' => 'Importar publicaciones'
+ ],
+ 'post' => [
+ 'title' => 'Título',
+ 'title_placeholder' => 'Título de la publicación',
+ 'content' => 'Contenido',
+ 'content_html' => 'Contenido HTML',
+ 'slug' => 'Identificador',
+ 'slug_placeholder' => 'nueva-publicacion',
+ 'categories' => 'Categorías',
+ 'author_email' => 'Email del Autor',
+ 'created' => 'Creado',
+ 'created_date' => 'Fecha de Creación',
+ 'updated' => 'Actualizado',
+ 'updated_date' => 'Fecha de Actualización',
+ 'published' => 'Publicado',
+ 'published_by' => 'Publicado por',
+ 'current_user' => 'Usuario actual',
+ 'published_date' => 'Fecha de publicación',
+ 'published_validation' => 'Por favor, especifique la fecha de publicación',
+ 'tab_edit' => 'Editar',
+ 'tab_categories' => 'Categorías',
+ 'categories_comment' => 'Seleccione las categorías para la publicación',
+ 'categories_placeholder' => 'No hay categorías, ¡crea una primero!',
+ 'tab_manage' => 'Administrar',
+ 'published_on' => 'Publicado el',
+ 'excerpt' => 'Resumen',
+ 'summary' => 'Resumen',
+ 'featured_images' => 'Imágenes Destacadas',
+ 'delete_confirm' => '¿Borrar la publicación?',
+ 'delete_success' => 'Publicación borrada correctamente',
+ 'close_confirm' => 'La publicación no está guardada.',
+ 'return_to_posts' => 'Volver a la lista de publicaciones',
+ 'posted_byline' => 'Publicado en :categories el :date.',
+ 'posted_byline_no_categories' => 'Publicado el :date.',
+ 'date_format' => 'd de M de Y',
+ ],
+ 'categories' => [
+ 'list_title' => 'Administrar las categorías',
+ 'new_category' => 'Nueva categoría',
+ 'uncategorized' => 'Sin Categoría'
+ ],
+ 'category' => [
+ 'name' => 'Nombre',
+ 'name_placeholder' => 'Nombre de la categoría',
+ 'description' => 'Descripción',
+ 'slug' => 'Identificador',
+ 'slug_placeholder' => 'nueva-categoría',
+ 'posts' => 'Publicaciones',
+ 'delete_confirm' => '¿Borrar esta categoría?',
+ 'delete_success' => 'Categorías borradas correctamente.',
+ 'return_to_categories' => 'Volver a la lista de categorías',
+ 'reorder' => 'Re-ordenar Categorías'
+ ],
+ 'menuitem' => [
+ 'blog_category' => 'Categoría del blog',
+ 'all_blog_categories' => 'Todas las categorías del blog',
+ 'blog_post' => 'Publicación del blog',
+ 'all_blog_posts' => 'Todas las publicaciones del blog',
+ 'category_blog_posts' => 'Publicaciones del blog por categorías'
+ ],
+ 'settings' => [
+ 'category_title' => 'Lista de Categorías',
+ 'category_description' => 'Muestra en la página una lista de las categorías.',
+ 'category_slug' => 'Identificador de la categoría',
+ 'category_slug_description' => "Localiza una categoría utilizando el identificador proporcionado. Esta propiedad es utilizada dentro del parcial que viene por defecto en el componente para marcar la categoría activa.",
+ 'category_display_empty' => 'Mostrar categorías vacías',
+ 'category_display_empty_description' => 'Mostrar categorías que no tienen ninguna publicación.',
+ 'category_page' => 'Página de categorías',
+ 'category_page_description' => 'Nombre del archivo de página utilizado para los enlaces de categorías. Esta propiedad es utilizada dentro del parcial que viene por defecto en el componente.',
+ 'post_title' => 'Publicación',
+ 'post_description' => 'Muestra una publicación en la página.',
+ 'post_slug' => 'Identificador de la publicación',
+ 'post_slug_description' => "Se buscará la publicación utilizando el valor del identificador proporcionado.",
+ 'post_category' => 'Página de categoría',
+ 'post_category_description' => 'Nombre del archivo de página utilizado para los enlaces de categorías. Esta propiedad es utilizada dentro del parcial que viene por defecto en el componente.',
+ 'posts_title' => 'Lista de publicaciones',
+ 'posts_description' => 'Muestra una lista de las últimas publicaciones en la página.',
+ 'posts_pagination' => 'Número de página',
+ 'posts_pagination_description' => 'Este valor se utiliza para determinar en que página se encuentra el usuario.',
+ 'posts_filter' => 'Filtro de categoría',
+ 'posts_filter_description' => 'Ingrese un identificador de categoría o parámetro URL. Se utilizará para filtrar las publicaciones. Deje el campo vacío para mostrar todas las publicaciones.',
+ 'posts_per_page' => 'Publicaciones por página',
+ 'posts_per_page_validation' => 'Formato inválido para el valor de publicaciones por página',
+ 'posts_no_posts' => 'Mensaje cuando no hay publicaciones',
+ 'posts_no_posts_description' => 'Mensaje que se mostrará en la lista de publicaciones del blog cuando no haya ningúno. Esta propiedad es utilizada dentro del parcial que viene por defecto en el componente.',
+ 'posts_no_posts_default' => 'No se encontraron publicaciones.',
+ 'posts_order' => 'Ordenar publicaciones por',
+ 'posts_order_description' => 'Atributo mediante el cual se deberán ordenar las publicaciones',
+ 'posts_category' => 'Página de Categoría',
+ 'posts_category_description' => 'Nombre del archivo de página utilizado para los enlaces de categoría "Publicado en". Esta propiedad es utilizada dentro del parcial que viene por defecto en el componente.',
+ 'posts_post' => 'Página de las publicaciones',
+ 'posts_post_description' => 'Nombre del archivo de página utilizado para los enlaces "Saber más". Esta propiedad es utilizada dentro del parcial que viene por defecto en el componente.',
+ 'posts_except_post' => 'Exceptuar publicación',
+ 'posts_except_post_description' => 'Ingrese una ID/URL o variable que contenga una ID/URL de la publicación que se quiera excluir',
+ 'posts_except_post_validation' => 'La publicación a excluir debe ser una ID/URL, o una lista separada por comas de IDs/URLs',
+ 'posts_except_categories' => 'Excluir categorías',
+ 'posts_except_categories_description' => 'Introduce una lista separada por comas de IDs/URLs de categorías con las categorías a excluir.',
+ 'posts_except_categories_validation' => 'Las categorías excluidas deben ser una URL de categoría o una lista separada por comas',
+ 'rssfeed_blog' => 'Página del blog',
+ 'rssfeed_blog_description' => 'Nombre del archivo de página principal para generación de enlaces. Esta propiedad es utilizada dentro del parcial que viene por defecto en el componente.',
+ 'rssfeed_title' => 'RSS Feed',
+ 'rssfeed_description' => 'Genera un feed de RSS con las publicaciones del blog.',
+ 'group_links' => 'Enlaces',
+ 'group_exceptions' => 'Excepciones'
+ ],
+ 'sorting' => [
+ 'title_asc' => 'Título (ascendiente)',
+ 'title_desc' => 'Título (descendiente)',
+ 'created_asc' => 'Creado (ascendiente)',
+ 'created_desc' => 'Creado (descendiente)',
+ 'updated_asc' => 'Editado (ascendiente)',
+ 'updated_desc' => 'Editado (descendiente)',
+ 'published_asc' => 'Publicado (ascendiente)',
+ 'published_desc' => 'Publicado (descendiente)',
+ 'random' => 'Aleatorio'
+ ],
+ 'import' => [
+ 'update_existing_label' => 'Editar publicaciones existentes',
+ 'update_existing_comment' => 'Selecciona este check para actualizar las publicaciones con exactamente la misma ID, título o URL.',
+ 'auto_create_categories_label' => 'Crear categorías especificadas en el archivo a importar',
+ 'auto_create_categories_comment' => 'Debes hacer coincidir la columna Categoría para usar esta funcionalidad, sino selecciona la categoría por defecto para para usar para los elementos de abajo.',
+ 'categories_label' => 'Categorías',
+ 'categories_comment' => 'Selecciona las categorías a las que pertenecerán las publicaciones importadas (opcional).',
+ 'default_author_label' => 'Autor de publicación por defecto (opcional)',
+ 'default_author_comment' => 'La importación intentará usar un autor existente si coicide con la columna "Author Email", sino se usará el autor especificado arriba.',
+ 'default_author_placeholder' => '-- Selecciona Autor/a --'
+ ]
+];
diff --git a/plugins/rainlab/blog/lang/fa/lang.php b/plugins/rainlab/blog/lang/fa/lang.php
new file mode 100644
index 00000000..04134519
--- /dev/null
+++ b/plugins/rainlab/blog/lang/fa/lang.php
@@ -0,0 +1,109 @@
+ [
+ 'name' => 'وبلاگ',
+ 'description' => 'پلتفرم قوی برای وبلاگ نویسی'
+ ],
+ 'blog' => [
+ 'menu_label' => 'وبلاگ',
+ 'menu_description' => 'مدیریت پست های ارسالی',
+ 'posts' => 'پست ها',
+ 'create_post' => 'ایجاد پست جدید',
+ 'categories' => 'دسته بندی ها',
+ 'create_category' => 'ایجاد دسته بندی جدید',
+ 'tab' => 'وبلاگ',
+ 'access_posts' => 'مدیریت پست های ارسالی',
+ 'access_categories' => 'مدیریت دسته بندی های وبلاگ',
+ 'access_other_posts' => 'مدیریت پست های ارسالی سایر کاربران',
+ 'access_import_export' => 'توانایی واردکردن و خارج کردن پستها',
+ 'delete_confirm' => 'آیا اطمینان دارید؟',
+ 'chart_published' => 'منتشر شده',
+ 'chart_drafts' => 'پیش نویس',
+ 'chart_total' => 'مجموع'
+ ],
+ 'posts' => [
+ 'list_title' => 'مدیریت پست های ارسالی',
+ 'filter_category' => 'دسته بندی',
+ 'filter_published' => 'مخفی کردن منتشر شده ها',
+ 'new_post' => 'پست جدید'
+ ],
+ 'post' => [
+ 'title' => 'عنوان',
+ 'title_placeholder' => 'عنوان پست جدید',
+ 'content' => 'محتوی',
+ 'content_html' => 'محتوی HTML',
+ 'slug' => 'آدرس',
+ 'slug_placeholder' => 'آدرس-پست-جدید',
+ 'categories' => 'دسته بندی ها',
+ 'author_email' => 'پست الکترونیکی نویسنده',
+ 'created' => 'ایجاد شده در',
+ 'created_date' => 'تاریخ ایجاد',
+ 'updated' => 'به روزرسانی شده در',
+ 'updated_date' => 'تاریخ به روزرسانی',
+ 'published' => 'منتشر شده',
+ 'published_date' => 'تاریخ انتشار',
+ 'published_validation' => 'لطفا تاریخ انتشار را وارد نمایید',
+ 'tab_edit' => 'ویرایش',
+ 'tab_categories' => 'دسته بندی ها',
+ 'categories_comment' => 'دسته بندی هایی را که پست به آنها تعلق دارد را انتخاب نمایید',
+ 'categories_placeholder' => 'دسته بندی ای وجود ندارد. ابتدا یک دسته بندی ایجاد نمایید!',
+ 'tab_manage' => 'مدیریت',
+ 'published_on' => 'منتشر شده در',
+ 'excerpt' => 'خلاصه',
+ 'summary' => 'چکیده',
+ 'featured_images' => 'تصاویر شاخص',
+ 'delete_confirm' => 'آیا از حذف این پست اطمینان دارید؟',
+ 'close_confirm' => 'پست ذخیره نشده است',
+ 'return_to_posts' => 'بازگشت به لیست پست ها'
+ ],
+ 'categories' => [
+ 'list_title' => 'مدیریت دسته بندی های وبلاگ',
+ 'new_category' => 'دسته بندی جدید',
+ 'uncategorized' => 'بدون دسته بندی'
+ ],
+ 'category' => [
+ 'name' => 'نام',
+ 'name_placeholder' => 'نام دسته بندی جدید',
+ 'slug' => 'آدرس',
+ 'slug_placeholder' => 'آدرس-جدید-دسته-بندی',
+ 'posts' => 'پست ها',
+ 'delete_confirm' => 'آیا از حذف این دسته بندی اطمینان دارید؟',
+ 'return_to_categories' => 'بازگشت به لیست دسته بندی های وبلاگ',
+ 'reorder' => 'مرتب سازی دسته بندی ها'
+ ],
+ 'settings' => [
+ 'category_title' => 'لیست دسته بندی',
+ 'category_description' => 'نمایش لیست دسته بندی های وبلاگ در صفحه',
+ 'category_slug' => 'آدرس دسته بندی',
+ 'category_slug_description' => "دسته بندی وبلاگ توسط آدرس وارد شده جستجو می شود. این عمل توسط ابزار دسته بندی برای برجسته ساختن دسته بندی در حال نمایش استفاده می شود.",
+ 'category_display_empty' => 'نمایش دسته بندی های خالی',
+ 'category_display_empty_description' => 'نمایش دسته بندی هایی که هیچ ارسالی در آنها وجود ندارد.',
+ 'category_page' => 'صفحه دسته بندی',
+ 'category_page_description' => 'نام صفحه ای که لیست دسته بندی ها در آن نمایش داده می شوند. این گزینه به طور پیشفرض توسط ابزار مورد استفاده قرار میگیرد.',
+ 'post_title' => 'پست',
+ 'post_description' => 'نمایش پست در صفحه',
+ 'post_slug' => 'آدرس پست',
+ 'post_slug_description' => "پست توسط آدرس وارد شده جستجو میشود.",
+ 'post_category' => 'صفحه دسته بندی',
+ 'post_category_description' => 'نام صفحه ای که لیست دسته بندی ها در آن نمایش داده می شوند. این گزینه به طور پیشفرض توسط ابزار مورد استفاده قرار میگیرد.',
+ 'posts_title' => 'لیست پست ها',
+ 'posts_description' => 'نمایش لیستی از پستهایی که اخیرا ارسال شده اند در صفحه.',
+ 'posts_pagination' => 'شماره صفحه',
+ 'posts_pagination_description' => 'این مقدار جهت تشخیص صفحه ای که کاربر در آن قرار دارد مورد استفاده قرار میگیرد.',
+ 'posts_filter' => 'فیلتر دسته بندی',
+ 'posts_filter_description' => 'آدرس دسته بندی ای را که میخواهید پست های آن نمایش داده شوند را وارد نمایید. اگر میخواهید همه پست ها نمایش داده شوند این مقدار را خالی رها کنید.',
+ 'posts_per_page' => 'تعداد پست ها در هر صفحه',
+ 'posts_per_page_validation' => 'مقدار ورودی تعداد پست ها در هر صفحه نامعتبر است.',
+ 'posts_no_posts' => 'پیغام پستی وجود ندارد',
+ 'posts_no_posts_description' => 'این پیغام در صورتی که پستی جهت نمایش وجود نداشته باشد، نمایش داده می شود.',
+ 'posts_order' => 'ترتیب پست ها',
+ 'posts_order_decription' => 'مشخصه ترتیب نمایش پست ها در صفحه',
+ 'posts_category' => 'صفحه دسته بندی',
+ 'posts_category_description' => 'نام صفحه دسته بندی برای نمایش پستهای مربوط به آن.',
+ 'posts_post' => 'صفحه پست',
+ 'posts_post_description' => 'نام صفحه مربوط به نمایش کامل پست ها جهت لینک ادامه مطلب',
+ 'posts_except_post' => 'Except post',
+ 'posts_except_post_description' => 'Enter ID/URL or variable with post ID/URL you want to except',
+ ]
+];
diff --git a/plugins/rainlab/blog/lang/fi/lang.php b/plugins/rainlab/blog/lang/fi/lang.php
new file mode 100644
index 00000000..284c9211
--- /dev/null
+++ b/plugins/rainlab/blog/lang/fi/lang.php
@@ -0,0 +1,163 @@
+ [
+ 'name' => 'Blogi',
+ 'description' => 'Vankka bloggausalusta.'
+ ],
+ 'blog' => [
+ 'menu_label' => 'Blogi',
+ 'menu_description' => 'Hallitse blogipostauksia',
+ 'posts' => 'Postaukset',
+ 'create_post' => 'Blogipostaus',
+ 'categories' => 'Categories',
+ 'create_category' => 'Blogikategoria',
+ 'tab' => 'Blogi',
+ 'access_posts' => 'Hallitse postauksia',
+ 'access_categories' => 'Hallitse kategorioita',
+ 'access_other_posts' => 'Hallitse muiden käyttäjien postauksia',
+ 'access_import_export' => 'Saa tuoda ja viedä postauksia',
+ 'access_publish' => 'Saa julkaista postauksia',
+ 'manage_settings' => 'Manage blog settings',
+ 'delete_confirm' => 'Olteko varma?',
+ 'chart_published' => 'Julkaistu',
+ 'chart_drafts' => 'Luonnokset',
+ 'chart_total' => 'Yhteensä',
+ 'settings_description' => 'Hallinnoi blogin asetuksia',
+ 'show_all_posts_label' => 'Näytä kaikki postaukset ylläpitäjille',
+ 'show_all_posts_comment' => 'Näytä molemmat sekä julkaistut että julkaisemattomat postaukset ylläpitäjille',
+ 'tab_general' => 'Yleiset'
+ ],
+ 'posts' => [
+ 'list_title' => 'Hallitse blogipostauksia',
+ 'filter_category' => 'Kategoria',
+ 'filter_published' => 'Julkaistu',
+ 'filter_date' => 'Päivämäärä',
+ 'new_post' => 'Uusi postaus',
+ 'export_post' => 'Vie postaukset',
+ 'import_post' => 'Tuo postauksia'
+ ],
+ 'post' => [
+ 'title' => 'Otsikko',
+ 'title_placeholder' => 'Uuden postauksen otsikko',
+ 'content' => 'Sisältö',
+ 'content_html' => 'HTML Sisältö',
+ 'slug' => 'Slugi',
+ 'slug_placeholder' => 'uuden-postaukse-slugi',
+ 'categories' => 'Kategoriat',
+ 'author_email' => 'Tekijän sähköposti',
+ 'created' => 'Luotu',
+ 'created_date' => 'Luomispäivämäärä',
+ 'updated' => 'Muokattu',
+ 'updated_date' => 'Muokkauspäivämäärä',
+ 'published' => 'Julkaistu',
+ 'published_by' => 'Published by',
+ 'current_user' => 'Current user',
+ 'published_date' => 'Julkaisupäivämäärä',
+ 'published_validation' => 'Määrittele julkaisupäivämäärä',
+ 'tab_edit' => 'Muokkaa',
+ 'tab_categories' => 'Kategoriat',
+ 'categories_comment' => 'Valitse kategoriat joihin postaus kuuluu',
+ 'categories_placeholder' => 'Kategorioita ei ole, sinun pitäisi luoda ensimmäinen ensin!',
+ 'tab_manage' => 'Hallitse',
+ 'published_on' => 'Julkaistu',
+ 'excerpt' => 'Poiminto',
+ 'summary' => 'Yhteenveto',
+ 'featured_images' => 'Esittelykuvat',
+ 'delete_confirm' => 'Poista tämä postaus?',
+ 'delete_success' => 'Postaukset poistettu onnistuneesti.',
+ 'close_confirm' => 'Tämä postaus ei ole tallennettu.',
+ 'return_to_posts' => 'Palaa postauslistaan'
+ ],
+ 'categories' => [
+ 'list_title' => 'Hallitse blogikategorioita',
+ 'new_category' => 'Uusi kategoria',
+ 'uncategorized' => 'Luokittelematon'
+ ],
+ 'category' => [
+ 'name' => 'Nimi',
+ 'name_placeholder' => 'Uuden kategorian nimi',
+ 'description' => 'Kuvaus',
+ 'slug' => 'Slugi',
+ 'slug_placeholder' => 'uuden-kategorian-slugi',
+ 'posts' => 'Julkaisuja',
+ 'delete_confirm' => 'Poista tämä kategoria?',
+ 'delete_success' => 'Kategoriat poistettu onnistuneesti.',
+ 'return_to_categories' => 'Palaa blogikategorialistaan',
+ 'reorder' => 'Järjestä kategoriat uudelleen'
+ ],
+ 'menuitem' => [
+ 'blog_category' => 'Blogikategoria',
+ 'all_blog_categories' => 'Kaikki blogikategoriat',
+ 'blog_post' => 'Blogipostaukset',
+ 'all_blog_posts' => 'Kaikki blogipostaukset',
+ 'category_blog_posts' => 'Blogin kategorian postaukset'
+ ],
+ 'settings' => [
+ 'category_title' => 'Kategorialista',
+ 'category_description' => 'Näyttää listan blogikategorioista sivulla.',
+ 'category_slug' => 'Kategorian slugi',
+ 'category_slug_description' => 'Etsii blogikategorian käyttämällä annettua slugi-arvoa. Komponentti käyttää tätä merkitsemään aktiivisen kategorian.',
+ 'category_display_empty' => 'Näytä tyhjät kategoriat',
+ 'category_display_empty_description' => 'Näytä kategoriat joilla ei ole yhtään postauksia.',
+ 'category_page' => 'Kategoriasivu',
+ 'category_page_description' => 'Kategorialistaussivun tiedostonimi. Oletuskomponenttiosa käyttää tätä ominaisuutta.',
+ 'post_title' => 'Postaus',
+ 'post_description' => 'Näyttää blogipostauksen sivulla.',
+ 'post_slug' => 'Postauksen slugi',
+ 'post_slug_description' => 'Etsii blogipostauksen käyttämällä annettua slugi-arvoa.',
+ 'post_category' => 'Kategoriasivu',
+ 'post_category_description' => 'Kategorialistaussivun tiedostonimi. Oletuskomponenttiosa käyttää tätä ominaisuutta.',
+ 'posts_title' => 'Lista postauksista',
+ 'posts_description' => 'Näyttää listan uusimmista blogipostauksista sivulla.',
+ 'posts_pagination' => 'Sivunumero',
+ 'posts_pagination_description' => 'Tätä arvoa käytetään määrittämään millä sivulla käyttäjä on.',
+ 'posts_filter' => 'Kategoriasuodatin',
+ 'posts_filter_description' => 'Lisää kategorian slugi tai URL parametri, jolla suodattaa postauksia. Jätä tyhjäksi näyttääksesi kaikki postaukset.',
+ 'posts_per_page' => 'Postauksia per sivu',
+ 'posts_per_page_validation' => 'Postauksia per sivu -kohta sisältää kelvottoman arvon',
+ 'posts_no_posts' => 'Ei julkaisuja -viesti',
+ 'posts_no_posts_description' => 'Viesti, joka näytetään silloin kun postauksia ei ole. Oletuskomponenttiosa käyttää tätä ominaisuutta.',
+ 'posts_no_posts_default' => 'Ei postauksia',
+ 'posts_order' => 'Postauksien järjestys',
+ 'posts_order_description' => 'Attribuutti, jonka mukaan postaukset tulisi järjestää',
+ 'posts_category' => 'Kategoriasivu',
+ 'posts_category_description' => 'Kategoriasivun tiedosto "Julkaistu kohteeseen" kategorialinkkejä varten. Oletuskomponenttiosa käyttää tätä ominaisuutta.',
+ 'posts_post' => 'Postaussivu',
+ 'posts_post_description' => 'Blogisivun tiedostonimi "Lue lisää" linkkejä varten. Oletuskomponenttiosa käyttää tätä ominaisuutta.',
+ 'posts_except_post' => 'Poissulje postauksia',
+ 'posts_except_post_description' => 'Lisää postauksen ID/URL tai muuttuja, jonka haluat poissulkea',
+ 'posts_except_post_validation' => 'Postaukset poikkeukset täytyy olla yksittäinen slugi tai ID, pilkulla erotettu slugi-lista ja ID:t',
+ 'posts_except_categories' => 'Poikkeavat kategoriat',
+ 'posts_except_categories_description' => 'Lisää pilkulla erotettu listaus kategoria slugeista tai listaus kategorioista jotka haluat jättää ulkopuolelle',
+ 'posts_except_categories_validation' => 'Poikkeavat kategoriat ovat oltava yksittäinen kategoria slugi tai pilkulla erotettu listaus slugeista',
+ 'rssfeed_blog' => 'Blogisivu',
+ 'rssfeed_blog_description' => 'Blogisivun tiedostonimi linkkien generointia varten. Oletuskomponenttiosa käyttää tätä ominaisuutta.',
+ 'rssfeed_title' => 'RSS syöte',
+ 'rssfeed_description' => 'Generoi RSS syötteen sisältäen postaukset blogista.',
+ 'group_links' => 'Linkit',
+ 'group_exceptions' => 'Poikkeukset'
+ ],
+ 'sorting' => [
+ 'title_asc' => 'Otsikko (ascending)',
+ 'title_desc' => 'Otsikko (descending)',
+ 'created_asc' => 'Luotu (ascending)',
+ 'created_desc' => 'Luotu (descending)',
+ 'updated_asc' => 'Päivitetty (ascending)',
+ 'updated_desc' => 'Päivitetty (descending)',
+ 'published_asc' => 'Julkaistu (ascending)',
+ 'published_desc' => 'Julkaistu (descending)',
+ 'random' => 'Satunnainen'
+ ],
+ 'import' => [
+ 'update_existing_label' => 'Päivitä olemassa olevat postaukset',
+ 'update_existing_comment' => 'Valitse tämä laatikko päivittääksesi postaukset, joissa on täsmälleen sama ID, otsikko tai slugi.',
+ 'auto_create_categories_label' => 'Luo tuotavassa tiedostossa määritellyt kategoriat.',
+ 'auto_create_categories_comment' => 'Sinun tulisi yhdistää Kategoriat-sarake käyttääksesi tätä toiminnallisuutta. Muussa tapauksessa valitse oletuskategoria alapuolelta.',
+ 'categories_label' => 'Kategoriat',
+ 'categories_comment' => 'Valitse kategoriat, joihin tuotavat postaukset liitetään (option).',
+ 'default_author_label' => 'Oletuskirjoittaja (optio)',
+ 'default_author_comment' => 'Tuonti yrittää käyttää Kirjoittaja tiedon sähköpostia yhdistäessään kirjoittajaa. Muussa tapauksessa käytetään ylempänä määriteltyä.',
+ 'default_author_placeholder' => '-- valitse kirjoittaja --'
+ ]
+];
diff --git a/plugins/rainlab/blog/lang/fr/lang.php b/plugins/rainlab/blog/lang/fr/lang.php
new file mode 100644
index 00000000..c2b86009
--- /dev/null
+++ b/plugins/rainlab/blog/lang/fr/lang.php
@@ -0,0 +1,166 @@
+ [
+ 'name' => 'Blog',
+ 'description' => 'Une plateforme de blog robuste.'
+ ],
+ 'blog' => [
+ 'menu_label' => 'Blog',
+ 'menu_description' => 'Gestion d’articles de blog',
+ 'posts' => 'Articles',
+ 'create_post' => 'article de blog',
+ 'categories' => 'Catégories',
+ 'create_category' => 'catégorie d’articles',
+ 'tab' => 'Blog',
+ 'access_posts' => 'Gérer les articles',
+ 'access_categories' => 'Gérer les catégories',
+ 'access_other_posts' => 'Gérer les articles d’autres utilisateurs',
+ 'access_import_export' => 'Autorisé à importer et exporter des articles',
+ 'access_publish' => 'Autorisé à publier des articles',
+ 'manage_settings' => 'Gérer les paramètres du blog',
+ 'delete_confirm' => 'Confirmez-vous la suppression des articles sélectionnés ?',
+ 'chart_published' => 'Publié',
+ 'chart_drafts' => 'Brouillons',
+ 'chart_total' => 'Total',
+ 'settings_description' => 'Gérer les paramètres du blog',
+ 'show_all_posts_label' => "Afficher tous les messages aux utilisateurs du panneaux d'administration",
+ 'show_all_posts_comment' => 'Afficher autant les publications publiées et non publiées sur le site web pour les utilisateurs principaux',
+ 'tab_general' => 'Général'
+ ],
+ 'posts' => [
+ 'list_title' => 'Gérer les articles du blog',
+ 'filter_category' => 'Catégorie',
+ 'filter_published' => 'Masquer la publication',
+ 'filter_date' => 'Date',
+ 'new_post' => 'Nouvel article',
+ 'export_post' => 'Exporter les articles',
+ 'import_post' => 'Importer des articles'
+ ],
+ 'post' => [
+ 'title' => 'Titre',
+ 'title_placeholder' => 'Titre du nouvel article',
+ 'content' => 'Contenu',
+ 'content_html' => 'Contenu HTML',
+ 'slug' => 'Adresse',
+ 'slug_placeholder' => 'adresse-du-nouvel-article',
+ 'categories' => 'Catégories',
+ 'author_email' => 'Email de l’auteur',
+ 'created' => 'Créé',
+ 'created_date' => 'Date de création',
+ 'updated' => 'Mis a jour',
+ 'updated_date' => 'Date de mise à jour',
+ 'published' => 'Publié',
+ 'published_by' => 'Publié par',
+ 'current_user' => 'Utilisateur actuel',
+ 'published_date' => 'Date de publication',
+ 'published_validation' => 'Veuillez préciser la date de publication',
+ 'tab_edit' => 'Rédaction',
+ 'tab_categories' => 'Catégories',
+ 'categories_comment' => 'Sélectionnez les catégories auxquelles l’article est lié',
+ 'categories_placeholder' => 'Il n’existe pas encore de catégorie, mais vous pouvez en créer une !',
+ 'tab_manage' => 'Configuration',
+ 'published_on' => 'Publié le',
+ 'excerpt' => 'Extrait',
+ 'summary' => 'Résumé',
+ 'featured_images' => 'Image de promotion',
+ 'delete_confirm' => 'Confirmez-vous la suppression de cet article ?',
+ 'delete_success' => 'Ces articles ont été supprimés avec succès.',
+ 'close_confirm' => 'L’article n’est pas enregistré.',
+ 'return_to_posts' => 'Retour à la liste des articles',
+ 'posted_byline' => 'Posté dans :categories le :date.',
+ 'posted_byline_no_categories' => 'Posté le :date.',
+ 'date_format' => 'd M Y'
+ ],
+ 'categories' => [
+ 'list_title' => 'Gérer les catégories',
+ 'new_category' => 'Nouvelle catégorie',
+ 'uncategorized' => 'Non catégorisé'
+ ],
+ 'category' => [
+ 'name' => 'Nom',
+ 'name_placeholder' => 'Nom de la nouvelle catégorie',
+ 'description' => 'Description',
+ 'slug' => 'Adresse URL',
+ 'slug_placeholder' => 'adresse-de-la-nouvelle-catégorie',
+ 'posts' => 'Articles',
+ 'delete_confirm' => 'Confirmez-vous la suppression de cette catégorie ?',
+ 'delete_success' => 'Ces catégories ont été supprimés avec succès.',
+ 'return_to_categories' => 'Retour à la liste des catégories',
+ 'reorder' => 'Réorganiser les catégories'
+ ],
+ 'menuitem' => [
+ 'blog_category' => 'Catégories du blog',
+ 'all_blog_categories' => 'Toutes les catégories du blog',
+ 'blog_post' => 'Articles du blog',
+ 'all_blog_posts' => 'Tous les articles du blog',
+ 'category_blog_posts' => "Articles d'une catégorie du blog"
+ ],
+ 'settings' => [
+ 'category_title' => 'Liste des catégories',
+ 'category_description' => 'Afficher une liste des catégories sur la page.',
+ 'category_slug' => 'Adresse URL de la catégorie',
+ 'category_slug_description' => 'Adresse URL d’accès à la catégorie. Cette propriété est utilisée par le partial par défaut du composant pour marquer la catégorie courante comme active.',
+ 'category_display_empty' => 'Afficher les catégories vides.',
+ 'category_display_empty_description' => 'Afficher les catégories qui ne sont liés à aucun article.',
+ 'category_page' => 'Page des catégories',
+ 'category_page_description' => 'Nom de la page des catégories pour les liens de catégories. Cette propriété est utilisée par le partial par défaut du composant.',
+ 'post_title' => 'Article',
+ 'post_description' => 'Affiche un article de blog sur la page.',
+ 'post_slug' => 'Adresse URL de l’article',
+ 'post_slug_description' => 'Adresse URL d’accès à l’article.',
+ 'post_category' => 'Page des catégories',
+ 'post_category_description' => 'Nom de la page des catégories pour les liens de catégories. Cette propriété est utilisée par le partial par défaut du composant.',
+ 'posts_title' => 'Liste d’articles',
+ 'posts_description' => 'Affiche une liste des derniers articles de blog sur la page.',
+ 'posts_pagination' => 'Numéro de page',
+ 'posts_pagination_description' => 'Cette valeur est utilisée pour déterminer à quelle page l’utilisateur se trouve.',
+ 'posts_filter' => 'Filtre des catégories',
+ 'posts_filter_description' => 'Entrez une adresse de catégorie ou un paramètre d’URL pour filter les articles. Laissez vide pour afficher tous les articles.',
+ 'posts_per_page' => 'Articles par page',
+ 'posts_per_page_validation' => 'Format du nombre d’articles par page incorrect',
+ 'posts_no_posts' => 'Message en l’absence d’articles',
+ 'posts_no_posts_description' => 'Message à afficher dans la liste d’articles lorsqu’il n’y a aucun article. Cette propriété est utilisée par le partial par défaut du composant.',
+ 'posts_no_posts_default' => 'Aucun article trouvé',
+ 'posts_order' => 'Ordre des articles',
+ 'posts_order_description' => 'Attribut selon lequel les articles seront ordonnés',
+ 'posts_category' => 'Page de catégorie',
+ 'posts_category_description' => 'Nom du fichier de la page de catégorie pour les liens de catégorie "Publié dans". Cette propriété est utilisée par le composant par défaut du modèle partiel.',
+ 'posts_post' => "Page de l'article",
+ 'posts_post_description' => 'Nom du fichier de la page de l\'article du blog pour les liens "En savoir plus". Cette propriété est utilisée par le composant par défaut du modèle partiel.',
+ 'posts_except_post' => 'Article exempté',
+ 'posts_except_post_description' => 'Enter ID/URL or variable with post ID/URL you want to except',
+ 'posts_category' => 'Page des catégories',
+ 'posts_category_description' => 'Nom de la page des catégories pour les liens de catégories "Publié dans". Cette propriété est utilisée par le partial par défaut du composant.',
+ 'posts_post' => 'Page d’article',
+ 'posts_post_description' => 'Nom de la page d’articles pour les liens "En savoir plus". Cette propriété est utilisée par le partial par défaut du composant.',
+ 'rssfeed_blog' => 'Page du blog',
+ 'rssfeed_blog_description' => 'Nom de la page principale du blog pour générer les liens. Cette propriété est utilisé par le composant dans le partial.',
+ 'rssfeed_title' => 'Flux RSS',
+ 'rssfeed_description' => 'Génère un Flux RSS contenant les articles du blog.',
+ 'group_links' => 'Liens',
+ 'group_exceptions' => 'Exceptions'
+ ],
+ 'sorting' => [
+ 'title_asc' => 'Titre (ascendant)',
+ 'title_desc' => 'Titre (descendant)',
+ 'created_asc' => 'Création le (ascendant)',
+ 'created_desc' => 'Création (descendant)',
+ 'updated_asc' => 'Mise à jour (ascendant)',
+ 'updated_desc' => 'Mise à jour (descendant)',
+ 'published_asc' => 'Publication (ascendant)',
+ 'published_desc' => 'Publication (descendant)',
+ 'random' => 'Aléatoire'
+ ],
+ 'import' => [
+ 'update_existing_label' => 'Mettre à jour les articles existants',
+ 'update_existing_comment' => 'Cochez cette case pour mettre à jour les articles qui ont exactement le même identifiant, titre ou slug.',
+ 'auto_create_categories_label' => "Créer les catégories spécifiées dans le fichier d'importation",
+ 'auto_create_categories_comment' => 'Vous devez faire correspondre la colonne Catégories pour utiliser cette fonctionnalité. Sinon, sélectionnez les catégories par défaut à utiliser parmi les éléments ci-dessous.',
+ 'categories_label' => 'Catégories',
+ 'categories_comment' => 'Sélectionnez les catégories auxquelles appartiendront les articles importées (facultatif).',
+ 'default_author_label' => 'Auteur par défaut (facultatif)',
+ 'default_author_comment' => "L'importation tentera d'utiliser un auteur existant si vous correspondez la colonne Email à l'auteur, sinon l'auteur spécifié ci-dessus sera utilisé.",
+ 'default_author_placeholder' => "-- sélectionnez l'auteur --"
+ ]
+];
diff --git a/plugins/rainlab/blog/lang/hu/lang.php b/plugins/rainlab/blog/lang/hu/lang.php
new file mode 100644
index 00000000..e996f609
--- /dev/null
+++ b/plugins/rainlab/blog/lang/hu/lang.php
@@ -0,0 +1,166 @@
+ [
+ 'name' => 'Blog',
+ 'description' => 'Teljeskörű blog alkalmazás.'
+ ],
+ 'blog' => [
+ 'menu_label' => 'Blog',
+ 'menu_description' => 'Blog bejegyzések kezelése',
+ 'posts' => 'Bejegyzések',
+ 'create_post' => 'blog bejegyzés',
+ 'categories' => 'Kategóriák',
+ 'create_category' => 'blog kategória',
+ 'tab' => 'Blog',
+ 'access_posts' => 'Blog bejegyzések kezelése',
+ 'access_categories' => 'Blog kategóriák kezelése',
+ 'access_other_posts' => 'Más felhasználók bejegyzéseinek kezelése',
+ 'access_import_export' => 'Bejegyzések importálása és exportálása',
+ 'access_publish' => 'Blog bejegyzések közzététele',
+ 'manage_settings' => 'Blog beállítások kezelése',
+ 'delete_confirm' => 'Törölni akarja a kijelölt bejegyzéseket?',
+ 'chart_published' => 'Közzétéve',
+ 'chart_drafts' => 'Piszkozatok',
+ 'chart_total' => 'Összesen',
+ 'settings_description' => 'Beállítási lehetőségek.',
+ 'show_all_posts_label' => 'Az összes bejegyzés mutatása az adminisztrátorok számára',
+ 'show_all_posts_comment' => 'A közzétett és a még nem publikált bejegyzések is egyaránt meg fognak jelenni az oldal szerkesztőinek.',
+ 'tab_general' => 'Általános'
+ ],
+ 'posts' => [
+ 'list_title' => 'Blog bejegyzések',
+ 'filter_category' => 'Kategória',
+ 'filter_published' => 'Közzétéve',
+ 'filter_date' => 'Létrehozva',
+ 'new_post' => 'Új bejegyzés',
+ 'export_post' => 'Exportálás',
+ 'import_post' => 'Importálás'
+ ],
+ 'post' => [
+ 'title' => 'Cím',
+ 'title_placeholder' => 'Új bejegyzés címe',
+ 'content' => 'Szöveges tartalom',
+ 'content_html' => 'HTML tartalom',
+ 'slug' => 'Keresőbarát cím',
+ 'slug_placeholder' => 'uj-bejegyzes-cime',
+ 'categories' => 'Kategóriák',
+ 'author_email' => 'Szerző e-mail címe',
+ 'created' => 'Létrehozva',
+ 'created_date' => 'Létrehozás dátuma',
+ 'updated' => 'Módosítva',
+ 'updated_date' => 'Módosítás dátuma',
+ 'published' => 'Közzétéve',
+ 'published_by' => 'Szerző:',
+ 'current_user' => 'Felhasználó',
+ 'published_date' => 'Közzététel dátuma',
+ 'published_validation' => 'Adja meg a közzététel dátumát',
+ 'tab_edit' => 'Szerkesztés',
+ 'tab_categories' => 'Kategóriák',
+ 'categories_comment' => 'Jelölje be azokat a kategóriákat, melyekbe be akarja sorolni a bejegyzést',
+ 'categories_placeholder' => 'Nincsenek kategóriák, előbb létre kell hoznia egyet!',
+ 'tab_manage' => 'Kezelés',
+ 'published_on' => 'Közzététel dátuma',
+ 'excerpt' => 'Kivonat',
+ 'summary' => 'Összegzés',
+ 'featured_images' => 'Kiemelt képek',
+ 'delete_confirm' => 'Valóban törölni akarja ezt a bejegyzést?',
+ 'delete_success' => 'Sikeresen törölve lettek a bejegyzések.',
+ 'close_confirm' => 'A bejegyzés nem került mentésre.',
+ 'return_to_posts' => 'Vissza a bejegyzésekhez',
+ 'posted_byline' => 'Publikálva: :date, itt: :categories',
+ 'posted_byline_no_categories' => 'Publikálva: :date.',
+ 'date_format' => 'Y.M.d.',
+ ],
+ 'categories' => [
+ 'list_title' => 'Blog kategóriák',
+ 'new_category' => 'Új kategória',
+ 'uncategorized' => 'Nincs kategorizálva'
+ ],
+ 'category' => [
+ 'name' => 'Név',
+ 'name_placeholder' => 'Új kategória neve',
+ 'description' => 'Leírás',
+ 'slug' => 'Keresőbarát cím',
+ 'slug_placeholder' => 'uj-kategoria-neve',
+ 'posts' => 'Bejegyzések',
+ 'delete_confirm' => 'Valóban törölni akarja ezt a kategóriát?',
+ 'delete_success' => 'Sikeresen törölve lettek a kategóriák.',
+ 'return_to_categories' => 'Vissza a kategóriákhoz',
+ 'reorder' => 'Kategóriák sorrendje'
+ ],
+ 'menuitem' => [
+ 'blog_category' => 'Blog kategória',
+ 'all_blog_categories' => 'Összes blog kategória',
+ 'blog_post' => 'Blog bejegyzés',
+ 'all_blog_posts' => 'Összes blog bejegyzés',
+ 'category_blog_posts' => 'Blog kategória bejegyzések'
+ ],
+ 'settings' => [
+ 'category_title' => 'Blog kategória lista',
+ 'category_description' => 'A blog kategóriákat listázza ki a lapon.',
+ 'category_slug' => 'Cím paraméter neve',
+ 'category_slug_description' => 'A webcím útvonal paramétere a jelenlegi kategória keresőbarát címe alapján való kereséséhez. Az alapértelmezett komponensrész ezt a tulajdonságot használja a jelenleg aktív kategória megjelöléséhez.',
+ 'category_display_empty' => 'Üres kategóriák kijelzése',
+ 'category_display_empty_description' => 'Azon kategóriák megjelenítése, melyekben nincs egy bejegyzés sem.',
+ 'category_page' => 'Kategória lap',
+ 'category_page_description' => 'A kategória hivatkozások kategória lap fájljának neve. Az alapértelmezett komponensrész használja ezt a tulajdonságot.',
+ 'post_title' => 'Blog bejegyzés',
+ 'post_description' => 'Egy blog bejegyzést jelez ki a lapon.',
+ 'post_slug' => 'Cím paraméter neve',
+ 'post_slug_description' => 'A webcím útvonal paramétere a bejegyzés keresőbarát címe alapján való kereséséhez.',
+ 'post_category' => 'Kategória lap',
+ 'post_category_description' => 'A kategória hivatkozások kategória lap fájljának neve. Az alapértelmezett komponensrész használja ezt a tulajdonságot.',
+ 'posts_title' => 'Blog bejegyzések',
+ 'posts_description' => 'A közzétett blog bejegyzések listázása a honlapon.',
+ 'posts_pagination' => 'Lapozósáv paraméter neve',
+ 'posts_pagination_description' => 'A lapozósáv lapjai által használt, várt paraméter neve.',
+ 'posts_filter' => 'Kategória szűrő',
+ 'posts_filter_description' => 'Adja meg egy kategória keresőbarát címét vagy webcím paraméterét a bejegyzések szűréséhez. Hagyja üresen az összes bejegyzés megjelenítéséhez.',
+ 'posts_per_page' => 'Bejegyzések laponként',
+ 'posts_per_page_validation' => 'A laponkénti bejegyzések értéke érvénytelen formátumú',
+ 'posts_no_posts' => 'Üzenet ha nincs bejegyzés',
+ 'posts_no_posts_description' => 'A blog bejegyzés listában kijelezendő üzenet abban az esetben, ha nincsenek bejegyzések. Az alapértelmezett komponensrész használja ezt a tulajdonságot.',
+ 'posts_no_posts_default' => 'Nem található bejegyzés',
+ 'posts_order' => 'Bejegyzések sorrendje',
+ 'posts_order_description' => 'Jellemző, ami alapján rendezni kell a bejegyzéseket',
+ 'posts_category' => 'Kategória lap',
+ 'posts_category_description' => 'A "Kategória" kategória hivatkozások kategória lap fájljának neve. Az alapértelmezett komponensrész használja ezt a tulajdonságot.',
+ 'posts_post' => 'Bejegyzéslap',
+ 'posts_post_description' => 'A "Tovább olvasom" hivatkozások blog bejegyzéslap fájljának neve. Az alapértelmezett komponensrész használja ezt a tulajdonságot.',
+ 'posts_except_post' => 'Bejegyzés kizárása',
+ 'posts_except_post_description' => 'Adja meg annak a bejegyzésnek az azonosítóját vagy webcímét, amit nem akar megjeleníteni a listázáskor.',
+ 'posts_except_post_validation' => 'A kivételnek webcímnek, illetve azonosítónak, vagy pedig ezeknek a vesszővel elválasztott felsorolásának kell lennie.',
+ 'posts_except_categories' => 'Kategória kizárása',
+ 'posts_except_categories_description' => 'Adja meg azoknak a kategóriáknak a webcímét vesszővel elválasztva, amiket nem akar megjeleníteni a listázáskor.',
+ 'posts_except_categories_validation' => 'A kivételnek webcímnek, vagy pedig ezeknek a vesszővel elválasztott felsorolásának kell lennie.',
+ 'rssfeed_blog' => 'Blog oldal',
+ 'rssfeed_blog_description' => 'Annak a lapnak a neve, ahol listázódnak a blog bejegyzések. Ezt a beállítást használja alapértelmezetten a blog komponens is.',
+ 'rssfeed_title' => 'RSS hírfolyam',
+ 'rssfeed_description' => 'A bloghoz tartozó RSS hírfolyam generálása.',
+ 'group_links' => 'Hivatkozások',
+ 'group_exceptions' => 'Kivételek'
+ ],
+ 'sorting' => [
+ 'title_asc' => 'Név (növekvő)',
+ 'title_desc' => 'Név (csökkenő)',
+ 'created_asc' => 'Létrehozva (növekvő)',
+ 'created_desc' => 'Létrehozva (csökkenő)',
+ 'updated_asc' => 'Frissítve (növekvő)',
+ 'updated_desc' => 'Frissítve (csökkenő)',
+ 'published_asc' => 'Publikálva (növekvő)',
+ 'published_desc' => 'Publikálva (csökkenő)',
+ 'random' => 'Véletlenszerű'
+ ],
+ 'import' => [
+ 'update_existing_label' => 'Meglévő bejegyzések frissítése',
+ 'update_existing_comment' => 'Két bejegyzés akkor számít ugyanannak, ha megegyezik az azonosító számuk, a címük vagy a webcímük.',
+ 'auto_create_categories_label' => 'Az import fájlban megadott kategóriák létrehozása',
+ 'auto_create_categories_comment' => 'A funkció használatához meg kell felelnie a Kategóriák oszlopnak, különben az alábbi elemekből válassza ki az alapértelmezett kategóriákat.',
+ 'categories_label' => 'Kategóriák',
+ 'categories_comment' => 'Válassza ki azokat a kategóriákat, amelyekhez az importált bejegyzések tartoznak (nem kötelező).',
+ 'default_author_label' => 'Alapértelmezett szerző (nem kötelező)',
+ 'default_author_comment' => 'A rendszer megpróbál egy meglévő felhasználót társítani a bejegyzéshez az Email oszlop alapján. Amennyiben ez nem sikerül, az itt megadott szerzőt fogja alapul venni.',
+ 'default_author_placeholder' => '-- válasszon felhasználót --'
+ ]
+];
diff --git a/plugins/rainlab/blog/lang/it/lang.php b/plugins/rainlab/blog/lang/it/lang.php
new file mode 100644
index 00000000..0eade81a
--- /dev/null
+++ b/plugins/rainlab/blog/lang/it/lang.php
@@ -0,0 +1,107 @@
+ [
+ 'name' => 'Blog',
+ 'description' => 'Una solida piattaforma di blogging.'
+ ],
+ 'blog' => [
+ 'menu_label' => 'Blog',
+ 'menu_description' => 'Gestisci i post',
+ 'posts' => 'Post',
+ 'create_post' => 'post del blog',
+ 'categories' => 'Categorie',
+ 'create_category' => 'categorie del blog',
+ 'tab' => 'Blog',
+ 'access_posts' => 'Gestisci i post',
+ 'access_categories' => 'Gestisci le categorie',
+ 'access_other_posts' => 'Gestisci i post di altri utenti',
+ 'access_import_export' => 'Permesso ad importare ed esportare i post',
+ 'delete_confirm' => 'Sei sicuro?',
+ 'chart_published' => 'Pubblicato',
+ 'chart_drafts' => 'Bozze',
+ 'chart_total' => 'Totale'
+ ],
+ 'posts' => [
+ 'list_title' => 'Gestisci i post',
+ 'category' => 'Categoria',
+ 'hide_published' => 'Nascondi pubblicati',
+ 'new_post' => 'Nuovo post'
+ ],
+ 'post' => [
+ 'title' => 'Titolo',
+ 'title_placeholder' => 'Titolo del nuovo post',
+ 'content' => 'Contenuto',
+ 'content_html' => 'Contenuto HTML',
+ 'slug' => 'Slug',
+ 'slug_placeholder' => 'slug-del-nuovo-post',
+ 'categories' => 'Categorie',
+ 'author_email' => 'Email dell\'autore',
+ 'created' => 'Creato',
+ 'created_date' => 'Data di creazione',
+ 'updated' => 'Aggiornato',
+ 'updated_date' => 'Data di aggiornamento',
+ 'published' => 'Pubblicato',
+ 'published_date' => 'Data di pubblicazione',
+ 'published_validation' => 'Per favore fornisci la data di pubblicazione',
+ 'tab_edit' => 'Modifica',
+ 'tab_categories' => 'Categorie',
+ 'categories_comment' => 'Seleziona le categorie a cui appartiene il post',
+ 'categories_placeholder' => 'Non ci sono categorie, per iniziare dovresti crearne una!',
+ 'tab_manage' => 'Gestisci',
+ 'published_on' => 'Pubblicato il',
+ 'excerpt' => 'Estratto',
+ 'summary' => 'Riassunto',
+ 'featured_images' => 'Immagini in evidenza',
+ 'delete_confirm' => 'Vuoi veramente cancellare questo post?',
+ 'close_confirm' => 'Questo post non è salvato.',
+ 'return_to_posts' => 'Ritorna all\'elenco dei post'
+ ],
+ 'categories' => [
+ 'list_title' => 'Gestisci le categorie del blog',
+ 'new_category' => 'Nuova categoria',
+ 'uncategorized' => 'Non categorizzato'
+ ],
+ 'category' => [
+ 'name' => 'Nome',
+ 'name_placeholder' => 'Nome della nuova categoria',
+ 'slug' => 'Slug',
+ 'slug_placeholder' => 'slug-nuova-categoria',
+ 'posts' => 'Post',
+ 'delete_confirm' => 'Vuoi veramente cancellare questa categoria?',
+ 'return_to_categories' => 'Ritorna all\'elenco delle categorie del blog',
+ 'reorder' => 'Riordino Categorie'
+ ],
+ 'settings' => [
+ 'category_title' => 'Elenco Categorie',
+ 'category_description' => 'Mostra un\'elenco delle categorie del blog sulla pagina.',
+ 'category_slug' => 'Slug categoria',
+ 'category_slug_description' => "Cerca la categoria del blog usando lo slug fornito. Questa proprietà è usata dal componente parziale di default per segnare la categoria attualmente usata.",
+ 'category_display_empty' => 'Mostra categorie vuote',
+ 'category_display_empty_description' => 'Mostra categorie che non hanno alcun post.',
+ 'category_page' => 'Pagina delle categorie',
+ 'category_page_description' => 'Nome del file della pagina delle categorie contenente i link delle categorie. Questa proprietà è usata dal componente parziale di default.',
+ 'post_title' => 'Post',
+ 'post_description' => 'Mostra un post sulla pagina.',
+ 'post_slug' => 'Slug del post',
+ 'post_slug_description' => "Cerca il post con lo slug fornito.",
+ 'post_category' => 'Pagina delle categorie',
+ 'post_category_description' => 'Nome del file della pagina delle categorie contenente i link delle categorie. Questa proprietà è usata dal componente parziale di default.',
+ 'posts_title' => 'Elenco dei post',
+ 'posts_description' => 'Mostra un\'elenco degli ultimi post sulla pagina.',
+ 'posts_pagination' => 'Numero di pagina',
+ 'posts_pagination_description' => 'Questo valore è usato per determinare su quale pagina è l\'utente.',
+ 'posts_filter' => 'Filtro delle categorie',
+ 'posts_filter_description' => 'Inserisci lo slug di una categoria o un parametro dell\'URL con il quale filtrare i post. Lascia vuoto per mostrare tutti i post.',
+ 'posts_per_page' => 'Post per pagina',
+ 'posts_per_page_validation' => 'Il valore di post per pagina ha un formato non valido ',
+ 'posts_no_posts' => 'Messaggio per l\'assenza di post',
+ 'posts_no_posts_description' => 'Messaggio da mostrare nell\'elenco dei post in caso non ce ne siano. Questa proprietà è usata dal componente parziale di default.',
+ 'posts_order' => 'Ordine dei post',
+ 'posts_order_description' => 'Attributo sul quale i post dovrebbero esser ordinati',
+ 'posts_category' => 'Pagina delle categorie',
+ 'posts_category_description' => 'Nome del file per la pagina delle categorie per i link "Postato in" alle categorie. Questa proprietà è usata dal componente parziale di default.',
+ 'posts_post' => 'Pagina del post',
+ 'posts_post_description' => 'Nome del file per la pagina del post per i link "Scopri di più". Questa proprietà è usata dal componente parziale di default.'
+ ]
+];
diff --git a/plugins/rainlab/blog/lang/ja/lang.php b/plugins/rainlab/blog/lang/ja/lang.php
new file mode 100644
index 00000000..be6d0b48
--- /dev/null
+++ b/plugins/rainlab/blog/lang/ja/lang.php
@@ -0,0 +1,100 @@
+ [
+ 'name' => 'ブログ',
+ 'description' => 'ロバストなブログプラットフォームです。'
+ ],
+ 'blog' => [
+ 'menu_label' => 'ブログ',
+ 'menu_description' => 'ブログの投稿管理',
+ 'posts' => '投稿',
+ 'create_post' => '投稿の追加',
+ 'categories' => 'カテゴリ',
+ 'create_category' => 'カテゴリの追加',
+ 'tab' => 'ブログ',
+ 'access_posts' => '投稿の管理',
+ 'access_categories' => 'カテゴリの管理',
+ 'access_other_posts' => '他ユーザーの投稿の管理',
+ 'delete_confirm' => '削除していいですか?',
+ 'chart_published' => '公開済み',
+ 'chart_drafts' => '下書き',
+ 'chart_total' => '合計'
+ ],
+ 'posts' => [
+ 'list_title' => '投稿の管理',
+ 'filter_category' => 'カテゴリ',
+ 'filter_published' => '下書きのみ',
+ 'new_post' => '投稿を追加'
+ ],
+ 'post' => [
+ 'title' => 'タイトル',
+ 'title_placeholder' => 'タイトルを入力してください',
+ 'slug' => 'スラッグ',
+ 'slug_placeholder' => 'new-post-slug−123',
+ 'categories' => 'カテゴリ',
+ 'created' => '作成日',
+ 'updated' => '更新日',
+ 'published' => '公開する',
+ 'published_validation' => '投稿の公開日を指定してください。',
+ 'tab_edit' => '編集',
+ 'tab_categories' => 'カテゴリ',
+ 'categories_comment' => '投稿を関連付けるカテゴリを選択してください。(複数選択可)',
+ 'categories_placeholder' => 'まだカテゴリがありません。先に作成してください。',
+ 'tab_manage' => '管理',
+ 'published_on' => '公開日',
+ 'excerpt' => '投稿の抜粋',
+ 'featured_images' => 'アイキャッチ画像',
+ 'delete_confirm' => '削除していいですか?',
+ 'close_confirm' => '投稿は保存されていません。',
+ 'return_to_posts' => '投稿一覧に戻る'
+ ],
+ 'categories' => [
+ 'list_title' => 'カテゴリ管理',
+ 'new_category' => 'カテゴリの追加',
+ 'uncategorized' => '未分類'
+ ],
+ 'category' => [
+ 'name' => '名前',
+ 'name_placeholder' => 'カテゴリ名をつけてください',
+ 'slug' => 'スラッグ',
+ 'slug_placeholder' => 'new-category-slug-123',
+ 'posts' => '投稿数',
+ 'delete_confirm' => '削除していいですか?',
+ 'return_to_categories' => 'カテゴリ一覧に戻る'
+ ],
+ 'settings' => [
+ 'category_title' => 'カテゴリリスト',
+ 'category_description' => 'ページ内にカテゴリリストを表示します。',
+ 'category_slug' => 'カテゴリスラッグ',
+ 'category_slug_description' => "表示するカテゴリのスラッグを指定します。この項目はコンポーネントのデフォルトパーシャルで使用されます。",
+ 'category_display_empty' => '空のカテゴリの表示',
+ 'category_display_empty_description' => 'この項目がチェックされている場合、投稿が0件のカテゴリもリストに表示します。',
+ 'category_page' => 'カテゴリページ',
+ 'category_page_description' => 'カテゴリページへのリンクを生成するために、カテゴリページのファイル名を指定します。この項目はコンポーネントのデフォルトパーシャルで使用されます。',
+ 'post_title' => '投稿',
+ 'post_description' => 'ページ内に投稿を表示します。',
+ 'post_slug' => '投稿スラッグ',
+ 'post_slug_description' => "表示する投稿のスラッグを指定します。特定の投稿のスラッグか、URLパラメータ(:slug)を指定できます。",
+ 'post_category' => 'カテゴリページ',
+ 'post_category_description' => 'カテゴリリンクを生成するために、カテゴリページのファイル名を指定します。拡張子(.htm)は省いてください。この項目はコンポーネントのデフォルトパーシャルで使用されます。',
+ 'posts_title' => '投稿リスト',
+ 'posts_description' => 'ページ内に新しい投稿のリストを表示します。',
+ 'posts_pagination' => 'ページ番号',
+ 'posts_pagination_description' => 'ページ番号を指定します。URLパラメータ(:page)を指定できます。',
+ 'posts_filter' => 'カテゴリフィルタ',
+ 'posts_filter_description' => '投稿リストのフィルタを指定します。カテゴリのスラッグかURLパラメータ(:slug)を指定できます。空の場合、すべての投稿が表示されます。',
+ 'posts_per_page' => '1ページに表示する投稿数を指定します。',
+ 'posts_per_page_validation' => '1ページに表示する投稿数の形式が正しくありません。',
+ 'posts_no_posts' => '0件時メッセージ',
+ 'posts_no_posts_description' => 'この投稿リストが0件の場合に表示するメッセージを指定します。この項目はコンポーネントのデフォルトパーシャルで使用されます。',
+ 'posts_order' => '並び順',
+ 'posts_order_description' => '投稿リスト内の並び順を指定します。',
+ 'posts_category' => 'カテゴリページ',
+ 'posts_category_description' => 'カテゴリリンクを生成するために、カテゴリページのファイル名を指定します。この項目はコンポーネントのデフォルトパーシャルで使用されます。',
+ 'posts_post' => '投稿ページ',
+ 'posts_post_description' => '"Learn more"リンクを生成するため、投稿ページのファイル名を指定します。拡張子(.htm)は省いてください。この項目はコンポーネントのデフォルトパーシャルで使用されます。',
+ 'posts_except_post' => 'Except post',
+ 'posts_except_post_description' => 'Enter ID/URL or variable with post ID/URL you want to except',
+ ]
+];
diff --git a/plugins/rainlab/blog/lang/nb-no/lang.php b/plugins/rainlab/blog/lang/nb-no/lang.php
new file mode 100644
index 00000000..d0336326
--- /dev/null
+++ b/plugins/rainlab/blog/lang/nb-no/lang.php
@@ -0,0 +1,99 @@
+ [
+ 'name' => 'Blogg',
+ 'description' => 'En robust bloggeplattform.',
+ ],
+ 'blog' => [
+ 'menu_label' => 'Blogg',
+ 'menu_description' => 'Administrer blogginnlegg',
+ 'posts' => 'Innlegg',
+ 'create_post' => 'innlegg',
+ 'categories' => 'Kategorier',
+ 'create_category' => 'kategori',
+ 'access_posts' => 'Administrer blogginnleggene',
+ 'access_categories' => 'Administrer bloggkategorier',
+ 'access_other_posts' => 'Administrere andre brukere sine blogginnlegg',
+ 'delete_confirm' => 'Er du sikker?',
+ 'chart_published' => 'Publisert',
+ 'chart_drafts' => 'Utkast',
+ 'chart_total' => 'Totalt',
+ ],
+ 'posts' => [
+ 'list_title' => 'Administrer blogginnlegg',
+ 'filter_category' => 'Kategori',
+ 'filter_published' => 'Skjul publiserte',
+ 'new_post' => 'Nytt innlegg',
+ ],
+ 'post' => [
+ 'title' => 'Tittel',
+ 'title_placeholder' => 'Innleggets tittel',
+ 'slug' => 'Slug',
+ 'slug_placeholder' => 'innleggets-tittel',
+ 'categories' => 'Kategorier',
+ 'created' => 'Opprettet',
+ 'updated' => 'Oppdatert',
+ 'published' => 'Publisert',
+ 'published_validation' => 'Velg en dato når innlegget skal publiseres',
+ 'tab_edit' => 'Endre',
+ 'tab_categories' => 'Kategorier',
+ 'categories_comment' => 'Velg hvilke kategorier innlegget tilhører',
+ 'categories_placeholder' => 'Det finnes ingen kategorier! Vennligst opprett en først.',
+ 'tab_manage' => 'Egenskaper',
+ 'published_on' => 'Publiseringsdato',
+ 'excerpt' => 'Utdrag',
+ 'featured_images' => 'Utvalgte bilder',
+ 'delete_confirm' => 'Vil du virkelig slette dette innlegget?',
+ 'close_confirm' => 'Innlegget er ikke lagret.',
+ 'return_to_posts' => 'Tilbake til innleggsliste',
+ ],
+ 'categories' => [
+ 'list_title' => 'Administrer bloggkategorier',
+ 'new_category' => 'Ny kategori',
+ 'uncategorized' => 'Uten kategori',
+ ],
+ 'category' => [
+ 'name' => 'Navn',
+ 'name_placeholder' => 'Kategoriens navn',
+ 'slug' => 'Slug',
+ 'slug_placeholder' => 'kategoriens-navn',
+ 'posts' => 'Innlegg',
+ 'delete_confirm' => 'Vil du virkelig slette denne kategorien?',
+ 'return_to_categories' => 'Tilbake til kategorilisten',
+ ],
+ 'settings' => [
+ 'category_title' => 'Category List',
+ 'category_description' => 'Displays a list of blog categories on the page.',
+ 'category_slug' => 'Category slug',
+ 'category_slug_description' => "Look up the blog category using the supplied slug value. This property is used by the default component partial for marking the currently active category.",
+ 'category_display_empty' => 'Display empty categories',
+ 'category_display_empty_description' => 'Show categories that do not have any posts.',
+ 'category_page' => 'Category page',
+ 'category_page_description' => 'Name of the category page file for the category links. This property is used by the default component partial.',
+ 'post_title' => 'Post',
+ 'post_description' => 'Displays a blog post on the page.',
+ 'post_slug' => 'Post slug',
+ 'post_slug_description' => "Look up the blog post using the supplied slug value.",
+ 'post_category' => 'Category page',
+ 'post_category_description' => 'Name of the category page file for the category links. This property is used by the default component partial.',
+ 'posts_title' => 'Post List',
+ 'posts_description' => 'Displays a list of latest blog posts on the page.',
+ 'posts_pagination' => 'Page number',
+ 'posts_pagination_description' => 'This value is used to determine what page the user is on.',
+ 'posts_filter' => 'Category filter',
+ 'posts_filter_description' => 'Enter a category slug or URL parameter to filter the posts by. Leave empty to show all posts.',
+ 'posts_per_page' => 'Posts per page',
+ 'posts_per_page_validation' => 'Invalid format of the posts per page value',
+ 'posts_no_posts' => 'No posts message',
+ 'posts_no_posts_description' => 'Message to display in the blog post list in case if there are no posts. This property is used by the default component partial.',
+ 'posts_order' => 'Post order',
+ 'posts_order_description' => 'Attribute on which the posts should be ordered',
+ 'posts_category' => 'Category page',
+ 'posts_category_description' => 'Name of the category page file for the "Posted into" category links. This property is used by the default component partial.',
+ 'posts_post' => 'Post page',
+ 'posts_post_description' => 'Name of the blog post page file for the "Learn more" links. This property is used by the default component partial.',
+ 'posts_except_post' => 'Except post',
+ 'posts_except_post_description' => 'Enter ID/URL or variable with post ID/URL you want to except',
+ ],
+];
diff --git a/plugins/rainlab/blog/lang/nl/lang.php b/plugins/rainlab/blog/lang/nl/lang.php
new file mode 100644
index 00000000..1d5b7828
--- /dev/null
+++ b/plugins/rainlab/blog/lang/nl/lang.php
@@ -0,0 +1,113 @@
+ [
+ 'name' => 'Blog',
+ 'description' => 'A robust blogging platform.'
+ ],
+ 'blog' => [
+ 'menu_label' => 'Blog',
+ 'menu_description' => 'Beheer blog artikelen',
+ 'posts' => 'Artikelen',
+ 'create_post' => 'Artikel',
+ 'categories' => 'Categorieën',
+ 'create_category' => 'blog categorie',
+ 'tab' => 'Blog',
+ 'access_posts' => 'Blog artikelen beheren',
+ 'access_categories' => 'Blog categorieën beheren',
+ 'access_other_posts' => 'Beheren van blog artikelen van gebruikers',
+ 'access_import_export' => 'Toegang tot importeren en exporteren van artikelen',
+ 'delete_confirm' => 'Weet je het zeker?',
+ 'chart_published' => 'Gepubliceerd',
+ 'chart_drafts' => 'Concepten',
+ 'chart_total' => 'Totaal'
+ ],
+ 'posts' => [
+ 'list_title' => 'Beheren van blog artikelen',
+ 'filter_category' => 'Categorie',
+ 'filter_published' => 'Verberg gepubliceerd',
+ 'new_post' => 'Nieuw artikel'
+ ],
+ 'post' => [
+ 'title' => 'Titel',
+ 'title_placeholder' => 'Titel van artikel',
+ 'content' => 'Inhoud',
+ 'content_html' => 'HTML Inhoud',
+ 'slug' => 'Slug',
+ 'slug_placeholder' => 'nieuw-artikel-slug',
+ 'categories' => 'Categorieën',
+ 'author_email' => 'E-mail auteur',
+ 'created' => 'Aangemaakt',
+ 'created_date' => 'Aangemaakt op',
+ 'updated' => 'Bijgewerkt',
+ 'updated_date' => 'Bijgewerkt op',
+ 'published' => 'Gepubliceerd',
+ 'published_by' => 'Gepubliceerd door',
+ 'current_user' => 'Huidige gebruiker',
+ 'published_date' => 'Gepubliceerd op',
+ 'published_validation' => 'Graag een publicatie datum opgeven',
+ 'tab_edit' => 'Bewerken',
+ 'tab_categories' => 'Categorieën',
+ 'categories_comment' => 'Selecteer een categorie waarbij het artikel hoort',
+ 'categories_placeholder' => 'Er zijn geen categorieën, maak eerst een categorie aan!',
+ 'tab_manage' => 'Beheer',
+ 'published_on' => 'Gepubliceerd op',
+ 'excerpt' => 'Samenvatting',
+ 'summary' => 'Samenvatting',
+ 'featured_images' => 'Uitgelichte afbeelding',
+ 'delete_confirm' => 'Weet je zeker dat je dit artikel wilt verwijderen?',
+ 'close_confirm' => 'Artikel is nog niet opgeslagen.',
+ 'return_to_posts' => 'Terug naar artikel overzicht',
+ 'posted_byline' => 'Gepubliceerd in :categories op :date.',
+ 'posted_byline_no_categories' => 'Gepubliceerd op :date.',
+ 'date_format' => 'd, M, Y',
+ ],
+ 'categories' => [
+ 'list_title' => 'Beheer blog categorieën',
+ 'new_category' => 'Nieuwe categorie',
+ 'uncategorized' => 'Ongecategoriseerd'
+ ],
+ 'category' => [
+ 'name' => 'Naam',
+ 'name_placeholder' => 'Naam categorie',
+ 'slug' => 'Slug',
+ 'slug_placeholder' => 'nieuw-categorie-slug',
+ 'posts' => 'Artikelen',
+ 'delete_confirm' => 'Weet je zeker dat je deze categorie wilt verwijderen?',
+ 'return_to_categories' => 'Terug naar categorie overzicht'
+ ],
+ 'settings' => [
+ 'category_title' => 'Categorie overzicht',
+ 'category_description' => 'Geeft een lijst weer van alle categorieën op de pagina.',
+ 'category_slug' => 'Categorie slug',
+ 'category_slug_description' => 'Haal blog categorie op a.h.v. de opgegeven slug. Deze waarde wordt standaard gebruikt voor de partial om de actieve categorie te markeren.',
+ 'category_display_empty' => 'Geef lege categorieën weer',
+ 'category_display_empty_description' => 'Geef categorieën weer die geen artikelen hebben.',
+ 'category_page' => 'Categorie pagina',
+ 'category_page_description' => 'Naam van categorie pagina bestand voor de categorie links. Deze waarde wordt standaard gebruikt door de partial.',
+ 'post_title' => 'Artikel',
+ 'post_description' => 'Geef een artikel weer op de pagina.',
+ 'post_slug' => 'Artikel slug',
+ 'post_slug_description' => 'Haal een artikel op a.h.v. de opgegeven slug.',
+ 'post_category' => 'Categorie pagina',
+ 'post_category_description' => 'Naam van categorie pagina bestand voor de categorie links. Deze waarde wordt standaard gebruikt door de partial.',
+ 'posts_title' => 'Artikel overzicht',
+ 'posts_description' => 'Geeft een lijst van de nieuwste artikelen weer op de pagina.',
+ 'posts_pagination' => 'Pagina nummer',
+ 'posts_pagination_description' => 'Deze waarde wordt gebruikt om te kijken op welke pagina de gebruiker is.',
+ 'posts_filter' => 'Categorie filter',
+ 'posts_filter_description' => 'Geef een categorie slug of URL param om de artikelen hier op te kunnen filteren. Leeg laten als alle artikelen getoond moeten worden.',
+ 'posts_per_page' => 'Artikelen per pagina',
+ 'posts_per_page_validation' => 'Ongeldig formaat voor het aantal artikelen per pagina',
+ 'posts_no_posts' => 'Geen artikelen melding',
+ 'posts_no_posts_description' => 'Deze tekst wordt getoond als er geen artikelen zijn. Deze waarde wordt standaard gebruikt door de partial.',
+ 'posts_order' => 'Volgorde artikelen',
+ 'posts_order_description' => 'Kolom waar de artikelen op gesorteerd moeten worden',
+ 'posts_category' => 'Categorie pagina',
+ 'posts_category_description' => 'Naam van categorie pagina bestand voor gekoppeld artikel overzichts pagina. Deze waarde wordt standaard gebruikt door de partial.',
+ 'posts_post' => 'Artikel pagina',
+ 'posts_post_description' => 'Naam van blog pagina bestand voor de "Lees meer" links. Deze waarde wordt standaard gebruikt door de partial.',
+ 'posts_except_post' => 'Except post',
+ 'posts_except_post_description' => 'Enter ID/URL or variable with post ID/URL you want to except',
+ ]
+];
diff --git a/plugins/rainlab/blog/lang/pl/lang.php b/plugins/rainlab/blog/lang/pl/lang.php
new file mode 100644
index 00000000..69548989
--- /dev/null
+++ b/plugins/rainlab/blog/lang/pl/lang.php
@@ -0,0 +1,159 @@
+ [
+ 'name' => 'Blog',
+ 'description' => 'Solidna platforma blogera',
+ ],
+ 'blog' => [
+ 'menu_label' => 'Blog',
+ 'menu_description' => 'Zarządzaj postami na blogu',
+ 'posts' => 'Posty',
+ 'create_post' => 'Utwórz post',
+ 'categories' => 'Kategorie',
+ 'create_category' => 'Utwórz kategorię',
+ 'tab' => 'Blog',
+ 'access_posts' => 'Zarządzaj postami',
+ 'access_categories' => 'Zarządzaj kategoriami na blogu',
+ 'access_other_posts' => 'Zarządzaj postami innych użytkowników',
+ 'access_import_export' => 'Zarządzaj importowaniem i eksportowaniem postów',
+ 'access_publish' => 'Publikuj posty',
+ 'manage_settings' => 'Zarządzaj ustawieniami bloga',
+ 'delete_confirm' => 'Czy jesteś pewien?',
+ 'chart_published' => 'Opublikowane',
+ 'chart_drafts' => 'Szkice',
+ 'chart_total' => 'Łącznie',
+ 'settings_description' => 'Zarządzaj ustawieniami bloga',
+ 'show_all_posts_label' => 'Pokaż wszystkie posty użytkownikom backendu',
+ 'show_all_posts_comment' => 'Wyświetl opublikowane i nieopublikowany posty na stronie dla użytkowników backendu',
+ 'tab_general' => 'Ogólne',
+ ],
+ 'posts' => [
+ 'list_title' => 'Zarządzaj postami',
+ 'filter_category' => 'Kategoria',
+ 'filter_published' => 'Ukryj opublikowane',
+ 'filter_date' => 'Date',
+ 'new_post' => 'Nowy post',
+ 'export_post' => 'Eksportuj posty',
+ 'import_post' => 'Importuj posty',
+ ],
+ 'post' => [
+ 'title' => 'Tytuł',
+ 'title_placeholder' => 'Tytuł nowego posta',
+ 'content' => 'Zawartość',
+ 'content_html' => 'Zawartość HTML',
+ 'slug' => 'Alias',
+ 'slug_placeholder' => 'alias-nowego-postu',
+ 'categories' => 'Kategorie',
+ 'author_email' => 'Email autora',
+ 'created' => 'Utworzony',
+ 'created_date' => 'Data utworzenia',
+ 'updated' => 'Zaktualizowany',
+ 'updated_date' => 'Data aktualizacji',
+ 'published' => 'Opublikowany',
+ 'published_date' => 'Data publikacji',
+ 'published_validation' => 'Proszę określić datę publikacji',
+ 'tab_edit' => 'Edytuj',
+ 'tab_categories' => 'Kategorie',
+ 'categories_comment' => 'Wybierz kategorie do których post należy',
+ 'categories_placeholder' => 'Nie ma żadnej kategorii, powinieneś utworzyć przynajmniej jedną.',
+ 'tab_manage' => 'Zarządzaj',
+ 'published_on' => 'Opublikowane',
+ 'excerpt' => 'Zalążek',
+ 'summary' => 'Summary',
+ 'featured_images' => 'Załączone grafiki',
+ 'delete_confirm' => 'Czy naprawdę chcesz usunąć ten post?',
+ 'delete_success' => 'Posty zostały pomyślnie usunięte.',
+ 'close_confirm' => 'Ten post nie jest zapisany.',
+ 'return_to_posts' => 'Wróć do listy postów',
+ ],
+ 'categories' => [
+ 'list_title' => 'Zarządzaj kategoriami postów',
+ 'new_category' => 'Nowa kategoria',
+ 'uncategorized' => 'Bez kategorii',
+ ],
+ 'category' => [
+ 'name' => 'Nazwa',
+ 'name_placeholder' => 'Nazwa nowej kategorii',
+ 'description' => 'Opis',
+ 'slug' => 'Alias',
+ 'slug_placeholder' => 'alias-nowej-kategorii',
+ 'posts' => 'Posty',
+ 'delete_confirm' => 'Czy naprawdę chcesz usunąć tę kategorię?',
+ 'delete_success' => 'Kategorie zostały pomyślnie usunięte.',
+ 'return_to_categories' => 'Wróć do listy kategorii',
+ 'reorder' => 'Zmień kolejnośc kategorii',
+ ],
+ 'menuitem' => [
+ 'blog_category' => 'Kategorie',
+ 'all_blog_categories' => 'Wszystkie kategorie',
+ 'blog_post' => 'Post na bloga',
+ 'all_blog_posts' => 'Wszystkie posty',
+ 'category_blog_posts' => 'Posty w kategorii',
+ ],
+ 'settings' => [
+ 'category_title' => 'Lista kategorii',
+ 'category_description' => 'Wyświetla listę blogowych kategorii na stronie.',
+ 'category_slug' => 'Alias kategorii',
+ 'category_slug_description' => 'Look up the blog category using the supplied slug value. This property is used by the default component partial for marking the currently active category.',
+ 'category_display_empty' => 'Pokaż puste kategorie',
+ 'category_display_empty_description' => 'Pokazuje kategorie, które nie posiadają postów',
+ 'category_page' => 'Strona kategorii',
+ 'category_page_description' => 'Nazwa strony kategorii gdzie są pokazywane linki. Ten parametr jest domyślnie używany przez komponent.',
+ 'post_title' => 'Post',
+ 'post_description' => 'Wyświetla pojedynczy post na stronie.',
+ 'post_slug' => 'Alias postu',
+ 'post_slug_description' => 'Szuka post po nazwie aliasu.',
+ 'post_category' => 'Strona kategorii',
+ 'post_category_description' => 'Nazwa strony kategorii gdzie są pokazywane linki. Ten parametr jest domyślnie używany przez komponent.',
+ 'posts_title' => 'Lista postów',
+ 'posts_description' => 'Wyświetla kilka ostatnich postów.',
+ 'posts_pagination' => 'Numer strony',
+ 'posts_pagination_description' => 'Ta wartość odpowiada za odczytanie numeru strony.',
+ 'posts_filter' => 'Filtr kategorii',
+ 'posts_filter_description' => 'Wprowadź alias kategorii lub adres URL aby filtrować posty. Pozostaw puste aby pokazać wszystkie.',
+ 'posts_per_page' => 'Ilość postów na strone',
+ 'posts_per_page_validation' => 'Nieprawidłowa wartość ilości postów na strone',
+ 'posts_no_posts' => 'Komunikat o braku postów',
+ 'posts_no_posts_description' => 'Wiadomość, która ukaże się kiedy komponent nie odnajdzie postów. Ten parametr jest domyślnie używany przez komponent.',
+ 'posts_no_posts_default' => 'Nie znaleziono postów',
+ 'posts_order' => 'Kolejność postów',
+ 'posts_order_description' => 'Parametr przez który mają być sortowane posty',
+ 'posts_category' => 'Strona kategorii',
+ 'posts_category_description' => 'Nazwa strony kategorii w wyświetlaniu linków "Posted into" [Opublikowano w]. Ten parametr jest domyślnie używany przez komponent.',
+ 'posts_post' => 'Strona postu',
+ 'posts_post_description' => 'Nazwa strony postu dla linków "Learn more" [Czytaj więcej]. Ten parametr jest domyślnie używany przez komponent.',
+ 'posts_except_post' => 'Wyklucz posty',
+ 'posts_except_post_description' => 'Wprowadź ID/URL lub zmienną z ID/URL postu, który chcesz wykluczyć',
+ 'posts_except_post_validation' => 'Wartość pola wykluczenia postów musi być pojedynczym ID/aliasem lub listą ID/aliasów rozdzieloną przecinkami',
+ 'posts_except_categories' => 'Wyklucz kategorie',
+ 'posts_except_categories_description' => 'Wprowadź listę aliasów kategorii rozdzieloną przecinkami lub zmienną zawierającą taką listę',
+ 'posts_except_categories_validation' => 'Wartośc pola wykluczenia kategorii musi być pojedynczym aliasem lub listą aliasów rozdzielonych przecinkami',
+ 'rssfeed_blog' => 'Strona bloga',
+ 'rssfeed_blog_description' => 'Nazwa strony głównej bloga do generowania linków. Używane przez domyślny fragment komponentu.',
+ 'rssfeed_title' => 'Kanał RSS',
+ 'rssfeed_description' => 'Generuje kanał RSS zawierający posty z bloga.',
+ 'group_links' => 'Linki',
+ 'group_exceptions' => 'Wyjątki',
+ ],
+ 'sorting' => [
+ 'title_asc' => 'Tytuł (rosnąco)',
+ 'title_desc' => 'Tytuł (malejąco)',
+ 'created_asc' => 'Data utworzenia (rosnąco)',
+ 'created_desc' => 'Data utworzenia (malejąco)',
+ 'updated_asc' => 'Data aktualizacji (rosnąco)',
+ 'updated_desc' => 'Data aktualizacji (malejąco)',
+ 'published_asc' => 'Data publikacji (rosnąco)',
+ 'published_desc' => 'Data publikacji (malejąco)',
+ 'random' => 'Losowo',
+ ],
+ 'import' => [
+ 'update_existing_label' => 'Aktualizuj istniejące wpisy',
+ 'update_existing_comment' => 'Zaznacz to pole, aby zaktualizować posty, które mają taki sam identyfikator (ID), tytuł lub alias.',
+ 'auto_create_categories_label' => 'Utwórz kategorie podane w pliku',
+ 'auto_create_categories_comment' => 'Aby skorzystać z tej funkcji powinieneś dopasować kolumnę Kategorii. W przeciwnym wypadku wybierz domyślną kategorię do użycia poniżej.',
+ 'categories_label' => 'Kategorie',
+ 'categories_comment' => 'Wybierz kategorię, do której będą należeć zaimportowane posty (opcjonalne).',
+ 'default_author_label' => 'Domyślny autor postów (opcjonalne)',
+ 'default_author_comment' => 'Import spróbuje dopasować istniejącego autora na podstawie kolumny email. W przypadku niepowodzenia zostanie użyty autor wybrany powyżej.',
+ 'default_author_placeholder' => '-- wybierz autora --',
+ ],
+];
diff --git a/plugins/rainlab/blog/lang/pt-br/lang.php b/plugins/rainlab/blog/lang/pt-br/lang.php
new file mode 100644
index 00000000..98037ce2
--- /dev/null
+++ b/plugins/rainlab/blog/lang/pt-br/lang.php
@@ -0,0 +1,124 @@
+ [
+ 'name' => 'Blog',
+ 'description' => 'A plataforma de blogs robusta.'
+ ],
+ 'blog' => [
+ 'menu_label' => 'Blog',
+ 'menu_description' => 'Gerencie os posts do blog',
+ 'posts' => 'Posts',
+ 'create_post' => 'Blog post',
+ 'categories' => 'Categorias',
+ 'create_category' => 'Blog categoria',
+ 'tab' => 'Blog',
+ 'access_posts' => 'Gerencie os posts do blog',
+ 'access_categories' => 'Gerenciar as categorias de blog',
+ 'access_other_posts' => 'Gerencie outros posts de usuários do blog',
+ 'access_import_export' => 'Permissão para importação e exportação de mensagens',
+ 'access_publish' => 'Permitido publicar posts',
+ 'delete_confirm' => 'Você tem certeza?',
+ 'chart_published' => 'Publicados',
+ 'chart_drafts' => 'Rascunhos',
+ 'chart_total' => 'Total'
+ ],
+ 'posts' => [
+ 'list_title' => 'Gerencie os posts do blog',
+ 'filter_category' => 'Categoria',
+ 'filter_published' => 'Esconder publicados',
+ 'filter_date' => 'Data',
+ 'new_post' => 'Novo post',
+ 'export_post' => 'Exportar posts',
+ 'import_post' => 'Importar posts'
+ ],
+ 'post' => [
+ 'title' => 'Título',
+ 'title_placeholder' => 'Novo título do post',
+ 'content' => 'Conteúdo',
+ 'content_html' => 'HTML Conteúdo',
+ 'slug' => 'Slug',
+ 'slug_placeholder' => 'slug-do-post',
+ 'categories' => 'Categorias',
+ 'author_email' => 'Autor Email',
+ 'created' => 'Criado',
+ 'created_date' => 'Data de criação',
+ 'updated' => 'Atualizado',
+ 'updated_date' => 'Data de atualização',
+ 'published' => 'Publicado',
+ 'published_date' => 'Data de publicação',
+ 'published_validation' => 'Por favor, especifique a data de publicação',
+ 'tab_edit' => 'Editar',
+ 'tab_categories' => 'Categorias',
+ 'categories_comment' => 'Selecione as categorias do blog que o post pertence.',
+ 'categories_placeholder' => 'Não há categorias, você deve criar um primeiro!',
+ 'tab_manage' => 'Gerenciar',
+ 'published_on' => 'Publicado em',
+ 'excerpt' => 'Resumo',
+ 'summary' => 'Resumo',
+ 'featured_images' => 'Imagens destacadas',
+ 'delete_confirm' => 'Você realmente deseja excluir este post?',
+ 'close_confirm' => 'O post não foi salvo.',
+ 'return_to_posts' => 'Voltar à lista de posts'
+ ],
+ 'categories' => [
+ 'list_title' => 'Gerenciar as categorias do blog',
+ 'new_category' => 'Nova categoria',
+ 'uncategorized' => 'Sem categoria'
+ ],
+ 'category' => [
+ 'name' => 'Nome',
+ 'name_placeholder' => 'Novo nome para a categoria',
+ 'description' => 'Descrição',
+ 'slug' => 'Slug',
+ 'slug_placeholder' => 'novo-slug-da-categoria',
+ 'posts' => 'Posts',
+ 'delete_confirm' => 'Você realmente quer apagar esta categoria?',
+ 'return_to_categories' => 'Voltar para a lista de categorias do blog',
+ 'reorder' => 'Reordenar Categorias'
+ ],
+ 'menuitem' => [
+ 'blog_category' => 'Blog categoria',
+ 'all_blog_categories' => 'Todas as categorias de blog',
+ 'blog_post' => 'Blog post',
+ 'all_blog_posts' => 'Todas as postagens do blog'
+ ],
+ 'settings' => [
+ 'category_title' => 'Lista de categoria',
+ 'category_description' => 'Exibe uma lista de categorias de blog na página.',
+ 'category_slug' => 'Slug da categoria',
+ 'category_slug_description' => "Olhe para cima, a categoria do blog já está usando o valor fornecido! Esta propriedade é usada pelo componente default parcial para a marcação da categoria atualmente ativa.",
+ 'category_display_empty' => 'xibir categorias vazias',
+ 'category_display_empty_description' => 'Mostrar categorias que não tem nenhum post.',
+ 'category_page' => 'Página da categoria',
+ 'category_page_description' => 'Nome do arquivo de página da categoria para os links de categoria. Esta propriedade é usada pelo componente default parcial.',
+ 'post_title' => 'Post',
+ 'post_description' => 'Exibe um post na página.',
+ 'post_slug' => 'Post slug',
+ 'post_slug_description' => "Procure o post do blog usando o valor do slug fornecido.",
+ 'post_category' => 'Página da categoria',
+ 'post_category_description' => 'Nome do arquivo de página da categoria para os links de categoria. Esta propriedade é usada pelo componente default parcial.',
+ 'posts_title' => 'Lista de posts',
+ 'posts_description' => 'Exibe uma lista de últimas postagens na página.',
+ 'posts_pagination' => 'Número da pagina',
+ 'posts_pagination_description' => 'Esse valor é usado para determinar qual página o usuário está.',
+ 'posts_filter' => 'Filtro de categoria',
+ 'posts_filter_description' => 'Digite um slug de categoria ou parâmetro de URL para filtrar as mensagens. Deixe em branco para mostrar todas as mensagens.',
+ 'posts_per_page' => 'Posts por página',
+ 'posts_per_page_validation' => 'Formato inválido das mensagens por valor de página',
+ 'posts_no_posts' => 'Nenhuma mensagem de posts',
+ 'posts_no_posts_description' => 'Mensagem para exibir na lista post no caso, se não há mensagens. Esta propriedade é usada pelo componente default parcial.',
+ 'posts_order' => 'Orde posts',
+ 'posts_order_decription' => 'Atributo em que as mensagens devem ser ordenados',
+ 'posts_category' => 'Página de Categoria',
+ 'posts_category_description' => 'Nome do arquivo de página da categoria para os links de categoria. Esta propriedade é usada pelo componente default parcial.',
+ 'posts_post' => 'Página de posts',
+ 'posts_post_description' => 'Nome do arquivo post página para os "Saiba mais" links. Esta propriedade é usada pelo componente default parcial.',
+ 'posts_except_post' => 'Except post',
+ 'posts_except_post_description' => 'Enter ID/URL or variable with post ID/URL you want to except',
+ 'rssfeed_blog' => 'Página do Blog',
+ 'rssfeed_blog_description' => 'Nome do arquivo principal da página do blog para geração de links. Essa propriedade é usada pelo componente padrão parcial.',
+ 'rssfeed_title' => 'RSS Feed',
+ 'rssfeed_description' => 'Gera um feed RSS que contém posts do blog.'
+ ]
+];
diff --git a/plugins/rainlab/blog/lang/ru/lang.php b/plugins/rainlab/blog/lang/ru/lang.php
new file mode 100644
index 00000000..4a8be1b7
--- /dev/null
+++ b/plugins/rainlab/blog/lang/ru/lang.php
@@ -0,0 +1,159 @@
+ [
+ 'name' => 'Блог',
+ 'description' => 'Надежная блоговая-платформа.'
+ ],
+ 'blog' => [
+ 'menu_label' => 'Блог',
+ 'menu_description' => 'Управление Блогом',
+ 'posts' => 'Записи',
+ 'create_post' => 'записи',
+ 'categories' => 'Категории',
+ 'create_category' => 'категории',
+ 'tab' => 'Блог',
+ 'access_posts' => 'Управление записями блога',
+ 'access_categories' => 'Управление категориями блога',
+ 'access_other_posts' => 'Управление записями других пользователей',
+ 'access_import_export' => 'Разрешено импортировать и экспортировать записи',
+ 'access_publish' => 'Разрешено публиковать записи',
+ 'manage_settings' => 'Управление настройками блога',
+ 'delete_confirm' => 'Вы уверены, что хотите сделать это?',
+ 'chart_published' => 'Опубликовано',
+ 'chart_drafts' => 'Черновики',
+ 'chart_total' => 'Всего',
+ 'settings_description' => 'Управление настройками блога',
+ 'show_all_posts_label' => 'Показывать все записи для внутренних (бэкенд) пользователей',
+ 'show_all_posts_comment' => 'Показывать опубликованные и неопубликованные записи на фронтенде для внутренних (бэкенд) пользователей',
+ 'tab_general' => 'Основные'
+ ],
+ 'posts' => [
+ 'list_title' => 'Управление записями блога',
+ 'filter_category' => 'Категория',
+ 'filter_published' => 'Скрыть опубликованные',
+ 'filter_date' => 'Дата',
+ 'new_post' => 'Новая запись',
+ 'export_post' => 'Экспорт записей',
+ 'import_post' => 'Импорт записей'
+ ],
+ 'post' => [
+ 'title' => 'Заголовок',
+ 'title_placeholder' => 'Новый заголовок записи',
+ 'content' => 'Контент',
+ 'content_html' => 'HTML Контент',
+ 'slug' => 'URL записи',
+ 'slug_placeholder' => 'new-post-slug',
+ 'categories' => 'Категории',
+ 'author_email' => 'Email автора',
+ 'created' => 'Создано',
+ 'created_date' => 'Дата создания',
+ 'updated' => 'Обновлено',
+ 'updated_date' => 'Дата обновления',
+ 'published' => 'Опубликовано',
+ 'published_date' => 'Дата публикации',
+ 'published_validation' => 'Пожалуйста, укажите дату публикации.',
+ 'tab_edit' => 'Редактор',
+ 'tab_categories' => 'Категории',
+ 'categories_comment' => 'Выберите категории, к которым относится эта запись',
+ 'categories_placeholder' => 'Не найдено ни одной категории, создайте хотя бы одну!',
+ 'tab_manage' => 'Управление',
+ 'published_on' => 'Опубликовано',
+ 'excerpt' => 'Отрывок',
+ 'summary' => 'Резюме',
+ 'featured_images' => 'Тематические изображения',
+ 'delete_confirm' => 'Вы действительно хотите удалить эту запись?',
+ 'delete_success' => 'Эти записи успешно удалены.',
+ 'close_confirm' => 'Запись не была сохранена.',
+ 'return_to_posts' => 'Вернуться к списку записей'
+ ],
+ 'categories' => [
+ 'list_title' => 'Управление категориями блога',
+ 'new_category' => 'Новая категория',
+ 'uncategorized' => 'Без категории'
+ ],
+ 'category' => [
+ 'name' => 'Название',
+ 'name_placeholder' => 'Новое имя категории',
+ 'description' => 'Описание',
+ 'slug' => 'URL адрес',
+ 'slug_placeholder' => 'new-category-slug',
+ 'posts' => 'Записи',
+ 'delete_confirm' => 'Вы действительно хотите удалить эту категорию?',
+ 'delete_success' => 'Эти категории успешно удалены.',
+ 'return_to_categories' => 'Вернуться к списку категорий',
+ 'reorder' => 'Порядок категорий'
+ ],
+ 'menuitem' => [
+ 'blog_category' => 'Категория блога',
+ 'all_blog_categories' => 'Все категории блога',
+ 'blog_post' => 'Запись блога',
+ 'all_blog_posts' => 'Все записи блога',
+ 'category_blog_posts' => 'Записи категории блога'
+ ],
+ 'settings' => [
+ 'category_title' => 'Список категорий блога',
+ 'category_description' => 'Отображает список категорий на странице.',
+ 'category_slug' => 'Параметр URL',
+ 'category_slug_description' => 'Параметр маршрута, используемый для поиска в текущей категории по URL. Это свойство используется по умолчанию компонентом Фрагменты для маркировки активной категории.',
+ 'category_display_empty' => 'Пустые категории',
+ 'category_display_empty_description' => 'Отображать категории, которые не имеют записей.',
+ 'category_page' => 'Страница категорий',
+ 'category_page_description' => 'Название страницы категорий. Это свойство используется по умолчанию компонентом Фрагменты.',
+ 'post_title' => 'Запись блога',
+ 'post_description' => 'Отображение записи блога',
+ 'post_slug' => 'Параметр URL',
+ 'post_slug_description' => 'Параметр маршрута, необходимый для выбора конкретной записи.',
+ 'post_category' => 'Страница категорий',
+ 'post_category_description' => 'Название страницы категорий. Это свойство используется по умолчанию компонентом Фрагменты.',
+ 'posts_title' => 'Список записей блога',
+ 'posts_description' => 'Отображает список последних записей блога на странице.',
+ 'posts_pagination' => 'Параметр постраничной навигации',
+ 'posts_pagination_description' => 'Параметр, необходимый для постраничной навигации.',
+ 'posts_filter' => 'Фильтр категорий',
+ 'posts_filter_description' => 'Введите URL категории или параметр URL-адреса для фильтрации записей. Оставьте пустым, чтобы посмотреть все записи.',
+ 'posts_per_page' => 'Записей на странице',
+ 'posts_per_page_validation' => 'Недопустимый Формат. Ожидаемый тип данных - действительное число.',
+ 'posts_no_posts' => 'Отсутствие записей',
+ 'posts_no_posts_description' => 'Сообщение, отображаемое в блоге, если отсутствуют записи. Это свойство используется по умолчанию компонентом Фрагменты.',
+ 'posts_no_posts_default' => 'Записей не найдено',
+ 'posts_order' => 'Сортировка',
+ 'posts_order_description' => 'Атрибут, по которому будут сортироваться записи.',
+ 'posts_category' => 'Страница категорий',
+ 'posts_category_description' => 'Название категории на странице записи "размещена в категории". Это свойство используется по умолчанию компонентом Фрагменты.',
+ 'posts_post' => 'Страница записи',
+ 'posts_post_description' => 'Название страницы для ссылки "подробнее". Это свойство используется по умолчанию компонентом Фрагменты.',
+ 'posts_except_post' => 'Кроме записи',
+ 'posts_except_post_description' => 'Введите ID/URL или переменную с ID/URL записи, которую вы хотите исключить',
+ 'posts_except_categories' => 'Кроме категорий',
+ 'posts_except_categories_description' => 'Введите разделенный запятыми список URL категорий или переменную со списком категорий, которые вы хотите исключить',
+ 'rssfeed_blog' => 'Страница блога',
+ 'rssfeed_blog_description' => 'Имя основного файла страницы блога для генерации ссылок. Это свойство используется по умолчанию компонентом Фрагменты.',
+ 'rssfeed_title' => 'RSS Feed',
+ 'rssfeed_description' => 'Создает RSS-канал, содержащий записи из блога.',
+ 'group_links' => 'Ссылки',
+ 'group_exceptions' => 'Исключения'
+ ],
+ 'sorting' => [
+ 'title_asc' => 'Заголовок (по возрастанию)',
+ 'title_desc' => 'Заголовок (по убыванию)',
+ 'created_asc' => 'Создано (по возрастанию)',
+ 'created_desc' => 'Создано (по убыванию)',
+ 'updated_asc' => 'Обновлено (по возрастанию)',
+ 'updated_desc' => 'Обновлено (по убыванию)',
+ 'published_asc' => 'Опубликовано (по возрастанию)',
+ 'published_desc' => 'Опубликовано (по убыванию)',
+ 'random' => 'Случайно'
+ ],
+ 'import' => [
+ 'update_existing_label' => 'Обновить существующие записи',
+ 'update_existing_comment' => 'Установите этот флажок, чтобы обновлять записи имеющие одинаковый ID, title или URL.',
+ 'auto_create_categories_label' => 'Создать категории указанные в импортируемом файле',
+ 'auto_create_categories_comment' => 'Вы должны сопоставить столбец Категории, чтобы использовать эту функцию. В противном случае выберите для назначения категорию по умолчанию из пунктов ниже.',
+ 'categories_label' => 'Категории',
+ 'categories_comment' => 'Выберите категории, к которым будут принадлежать импортированные записи (необязательно).',
+ 'default_author_label' => 'Автор записи по умолчанию (необязательно)',
+ 'default_author_comment' => 'Импорт попытается использовать существующего автора, если он соответствуете столбцу Email автора, в противном случае используется указанный выше автор.',
+ 'default_author_placeholder' => '-- выберите автора --'
+ ]
+];
diff --git a/plugins/rainlab/blog/lang/sk/lang.php b/plugins/rainlab/blog/lang/sk/lang.php
new file mode 100644
index 00000000..57567787
--- /dev/null
+++ b/plugins/rainlab/blog/lang/sk/lang.php
@@ -0,0 +1,154 @@
+ [
+ 'name' => 'Blog',
+ 'description' => 'Robustná blogová platforma.'
+ ],
+ 'blog' => [
+ 'menu_label' => 'Blog',
+ 'menu_description' => 'Správa blogových príspevkov',
+ 'posts' => 'Príspevky',
+ 'create_post' => 'Príspevok',
+ 'categories' => 'Kategórie',
+ 'create_category' => 'Kategórie príspevkov',
+ 'tab' => 'Blog',
+ 'access_posts' => 'Správa blogových príspevkov',
+ 'access_categories' => 'Správa blogových kategórií',
+ 'access_other_posts' => 'Správa blogových príspevkov ostatných užívateľov',
+ 'access_import_export' => 'Možnosť importu a exportu príspevkov',
+ 'access_publish' => 'Možnosť publikovať príspevky',
+ 'delete_confirm' => 'Ste si istý?',
+ 'chart_published' => 'PublikovanéPublished',
+ 'chart_drafts' => 'Koncepty',
+ 'chart_total' => 'Celkom'
+ ],
+ 'posts' => [
+ 'list_title' => 'Správa blogových príspevkov',
+ 'filter_category' => 'Kategória',
+ 'filter_published' => 'Publikované',
+ 'filter_date' => 'Dátum',
+ 'new_post' => 'Nový príspevok',
+ 'export_post' => 'Exportovať príspevky',
+ 'import_post' => 'Importovať príspevky'
+ ],
+ 'post' => [
+ 'title' => 'Názov',
+ 'title_placeholder' => 'Názov nového príspevku',
+ 'content' => 'Obsah',
+ 'content_html' => 'HTML Obsah',
+ 'slug' => 'URL príspevku',
+ 'slug_placeholder' => 'url-nového-príspevku',
+ 'categories' => 'Kategórie',
+ 'author_email' => 'Email autora',
+ 'created' => 'Vytvorené',
+ 'created_date' => 'Dátum vytvorenia',
+ 'updated' => 'Upravené',
+ 'updated_date' => 'Dátum upravenia',
+ 'published' => 'Publikované',
+ 'published_date' => 'Dátum publikovania',
+ 'published_validation' => 'Prosím zvoľte dátum publikovania príspevku',
+ 'tab_edit' => 'Upraviť',
+ 'tab_categories' => 'Kategórie',
+ 'categories_comment' => 'Vyberte kategórie do ktorých tento príspevok patrí',
+ 'categories_placeholder' => 'Neexistujú žiadne kategórie, najprv nejakú vytvorte!',
+ 'tab_manage' => 'Nastavenia',
+ 'published_on' => 'Dátum publikovania',
+ 'excerpt' => 'Výňatok príspevku',
+ 'summary' => 'Zhrnutie',
+ 'featured_images' => 'Obrázky',
+ 'delete_confirm' => 'Zmazať tento príspevok?',
+ 'delete_success' => 'Vybrané príspevky boli úspešne odstránené.',
+ 'close_confirm' => 'Príspevok nie je uložený.',
+ 'return_to_posts' => 'Späť na zoznam príspevkov'
+ ],
+ 'categories' => [
+ 'list_title' => 'Správa blogových kategórií',
+ 'new_category' => 'Nová kategória',
+ 'uncategorized' => 'Nezaradené'
+ ],
+ 'category' => [
+ 'name' => 'Názov',
+ 'name_placeholder' => 'Názov novej kategórie',
+ 'description' => 'Popis',
+ 'slug' => 'URL kategórie',
+ 'slug_placeholder' => 'url-novej-kategórie',
+ 'posts' => 'Počet príspevkov',
+ 'delete_confirm' => 'Zmazať túto kategóriu?',
+ 'delete_success' => 'Vybrané kategórie boli úspešne odstránené.',
+ 'return_to_categories' => 'Späť na zoznam kategórií',
+ 'reorder' => 'Zmeniť poradie kategórií'
+ ],
+ 'menuitem' => [
+ 'blog_category' => 'Blogová kategória',
+ 'all_blog_categories' => 'Všetky blogové kategórie',
+ 'blog_post' => 'Blogové príspevky',
+ 'all_blog_posts' => 'Všetky blogové príspevky',
+ 'category_blog_posts' => 'Blogové príspevky v kategórií'
+ ],
+ 'settings' => [
+ 'category_title' => 'Zoznam kategórií',
+ 'category_description' => 'Zobrazí zoznam blogových kategórií na stránke.',
+ 'category_slug' => 'URL kategórie',
+ 'category_slug_description' => "Nájde blogovú kategóriu s týmto URL. Používa sa pre zobrazenie aktívnej kategórie.",
+ 'category_display_empty' => 'Zobraziť prázdne kategórie',
+ 'category_display_empty_description' => 'Zobrazí kategórie, ktoré nemajú žiadne príspevky.',
+ 'category_page' => 'Stránka kategórie',
+ 'category_page_description' => 'Názov stránky kategórie kam budú smerovať odkazy na kategóriu. Táto hodnota je použitá v predvolenej čiastočnej stránke komponentu.',
+ 'post_title' => 'Príspevok',
+ 'post_description' => 'Zobrazí blogový príspevok na stránke.',
+ 'post_slug' => 'URL príspevku',
+ 'post_slug_description' => "Nájde blogový príspevok s týmto URL.",
+ 'post_category' => 'Stránka kategórie',
+ 'post_category_description' => 'Názov stránky kategórie kam budú smerovať odkazy na kategóriu. Táto hodnota je použitá v predvolenej čiastočnej stránke komponentu.',
+ 'posts_title' => 'Zoznam príspevkov',
+ 'posts_description' => 'Zobrazí zoznam blogových príspevkov na stránke.',
+ 'posts_pagination' => 'číslo stránky',
+ 'posts_pagination_description' => 'Táto hodnota je použitá na určenie na akej stránke sa užívateľ nachádza.',
+ 'posts_filter' => 'Filter kategórií',
+ 'posts_filter_description' => 'Zadajte URL kategórie alebo URL parameter na filtrovanie príspevkov. Nechajte prázdne pre zobrazenie všetkých príspevkov.',
+ 'posts_per_page' => 'Príspevkov na stránku',
+ 'posts_per_page_validation' => 'Neplatný formát hodnoty počtu príspevkov na stránku',
+ 'posts_no_posts' => 'Správa prázdnej stránky',
+ 'posts_no_posts_description' => 'Správa, ktorá bude zobrazená v zozname príspevkov v prípade, že nie sú žiadne na zobrazenie. Táto hodnota je použitá v predvolenej čiastočnej stránke komponentu.',
+ 'posts_no_posts_default' => 'Nenašli sa žiadne príspevky',
+ 'posts_order' => 'Zoradenie príspevkov',
+ 'posts_order_description' => 'Atribút podľa ktorého budú príspevky zoradené',
+ 'posts_category' => 'Stránka kategórie',
+ 'posts_category_description' => 'Názov stránky kategórie kam budú smerovať odkazy "Vložené do". Táto hodnota je použitá v predvolenej čiastočnej stránke komponentu.',
+ 'posts_post' => 'Stránka príspevku',
+ 'posts_post_description' => 'Názov stránky príspevku kam budú smerovať odkazy "Zistiť viac". Táto hodnota je použitá v predvolenej čiastočnej stránke komponentu.',
+ 'posts_except_post' => 'Okrem príspevku',
+ 'posts_except_post_description' => 'Zadajte ID/URL alebo premennú s ID/URL príspevku, ktorý chcete vylúčiť',
+ 'posts_except_categories' => 'Okrem kategórií',
+ 'posts_except_categories_description' => 'Zadajte zoznam kategórií oddelený čiarkami alebo premennú s týmto zoznamom, ktoré chcete vylúčiť',
+ 'rssfeed_blog' => 'Stránka blogu',
+ 'rssfeed_blog_description' => 'Názov hlavnej stránky blogu na generovanie odkazov. Táto hodnota je použitá v predvolenej čiastočnej stránke komponentu.',
+ 'rssfeed_title' => 'RSS Kanál',
+ 'rssfeed_description' => 'Vygeneruje RSS kanál, ktorý obsahuje blogové príspevky.',
+ 'group_links' => 'Odkazy',
+ 'group_exceptions' => 'Výnimky'
+ ],
+ 'sorting' => [
+ 'title_asc' => 'Názov (vzostupne)',
+ 'title_desc' => 'Názov (zostupne)',
+ 'created_asc' => 'Vytvorené (vzostupne)',
+ 'created_desc' => 'Vytvorené (zostupne)',
+ 'updated_asc' => 'Upravené (vzostupne)',
+ 'updated_desc' => 'Upravené (zostupne)',
+ 'published_asc' => 'Publikované (vzostupne)',
+ 'published_desc' => 'Publikované (zostupne)',
+ 'random' => 'Náhodne'
+ ],
+ 'import' => [
+ 'update_existing_label' => 'Aktualizovať existujúce príspevky',
+ 'update_existing_comment' => 'Začiarknutím tohto políčka aktualizujte príspevky, ktoré majú presne to isté ID, titul alebo URL príspevku.',
+ 'auto_create_categories_label' => 'Vytvoriť kategórie zadané v importovanom súbore',
+ 'auto_create_categories_comment' => 'Ak chcete túto funkciu použiť, mali by sa zhodovať so stĺpcom Kategórie, inak vyberte predvolené kategórie, ktoré chcete použiť z nižšie uvedených položiek.',
+ 'categories_label' => 'Kategórie',
+ 'categories_comment' => 'Vyberte kategórie, do ktorých budú patriť importované príspevky (voliteľné).',
+ 'default_author_label' => 'Predvolený autor príspevku (voliteľné)',
+ 'default_author_comment' => 'Import sa pokúsi použiť existujúceho autora, ak sa zhoduje so stĺpcom e-mail, inak sa použije vyššie uvedený autor.',
+ 'default_author_placeholder' => '-- vyberte autora --'
+ ]
+];
diff --git a/plugins/rainlab/blog/lang/sl/lang.php b/plugins/rainlab/blog/lang/sl/lang.php
new file mode 100644
index 00000000..d0c3c12d
--- /dev/null
+++ b/plugins/rainlab/blog/lang/sl/lang.php
@@ -0,0 +1,163 @@
+ [
+ 'name' => 'Blog',
+ 'description' => 'Robustna platforma za bloganje.',
+ ],
+ 'blog' => [
+ 'menu_label' => 'Blog',
+ 'menu_description' => 'Upravljanje bloga',
+ 'posts' => 'Objave',
+ 'create_post' => 'Blog objava',
+ 'categories' => 'Kategorije',
+ 'create_category' => 'Blog kategorija',
+ 'tab' => 'Blog',
+ 'access_posts' => 'Upravljanje blog objav',
+ 'access_categories' => 'Upravljanje blog kategorij',
+ 'access_other_posts' => 'Upravljanje objav drugih uporabnikov',
+ 'access_import_export' => 'Dovoljenje za uvoz in izvoz objav',
+ 'access_publish' => 'Dovoljenje za objavljanje objav',
+ 'manage_settings' => 'Urejanje nastavitev bloga',
+ 'delete_confirm' => 'Ali ste prepričani?',
+ 'chart_published' => 'Objavljeno',
+ 'chart_drafts' => 'Osnutki',
+ 'chart_total' => 'Skupaj',
+ 'settings_description' => 'Urejanje nastavitev bloga',
+ 'show_all_posts_label' => 'Administratorjem prikaži vse objave',
+ 'show_all_posts_comment' => 'Na spletni strani prikaži administratorjem vse objavljene in neobjavljene objave.',
+ 'tab_general' => 'Splošno',
+ ],
+ 'posts' => [
+ 'list_title' => 'Urejanje blog objav',
+ 'filter_category' => 'Kategorija',
+ 'filter_published' => 'Objavljeno',
+ 'filter_date' => 'Datum',
+ 'new_post' => 'Nova objava',
+ 'export_post' => 'Izvoz objav',
+ 'import_post' => 'Uvoz objav',
+ ],
+ 'post' => [
+ 'title' => 'Naslov',
+ 'title_placeholder' => 'Naslov nove objave',
+ 'content' => 'Vsebina',
+ 'content_html' => 'HTML vsebina',
+ 'slug' => 'Povezava',
+ 'slug_placeholder' => 'povezava-nove-objave',
+ 'categories' => 'Kategorije',
+ 'author_email' => 'E-pošta avtorja',
+ 'created' => 'Ustvarjeno',
+ 'created_date' => 'Ustvarjeno dne',
+ 'updated' => 'Posodobljeno',
+ 'updated_date' => 'Posodobljeno dne',
+ 'published' => 'Objavljeno',
+ 'published_by' => 'Objavil',
+ 'current_user' => 'Trenutni uporabnik',
+ 'published_date' => 'Datum objave',
+ 'published_validation' => 'Prosimo, podajte datum objave',
+ 'tab_edit' => 'Upravljanje',
+ 'tab_categories' => 'Kategorije',
+ 'categories_comment' => 'Izberite kategorije, v katere spada objava',
+ 'categories_placeholder' => 'Ni najdenih kategorij, ustvarite vsaj eno kategorijo!',
+ 'tab_manage' => 'Urejanje',
+ 'published_on' => 'Datum objave',
+ 'excerpt' => 'Izvleček',
+ 'summary' => 'Povzetek',
+ 'featured_images' => 'Uporabljene slike',
+ 'delete_confirm' => 'Želite izbrisati to objavo?',
+ 'delete_success' => 'Te objave so bile uspešno izbrisane.',
+ 'close_confirm' => 'Ta objava ni shranjena.',
+ 'return_to_posts' => 'Vrnite se na seznam objav',
+ ],
+ 'categories' => [
+ 'list_title' => 'Upravljanje blog kategorij',
+ 'new_category' => 'Nova kategorija',
+ 'uncategorized' => 'Nekategorizirano',
+ ],
+ 'category' => [
+ 'name' => 'Naslov',
+ 'name_placeholder' => 'Naslov nove kategorije',
+ 'description' => 'Opis',
+ 'slug' => 'Povezava',
+ 'slug_placeholder' => 'povezava-nove-kategorije',
+ 'posts' => 'Objave',
+ 'delete_confirm' => 'Želite izbrisati to kategorijo?',
+ 'delete_success' => 'Te kategorije so bile uspešno izbrisane.',
+ 'return_to_categories' => 'Vrnite se na seznam blog kategorij',
+ 'reorder' => 'Spremenite vrstni red kategorij',
+ ],
+ 'menuitem' => [
+ 'blog_category' => 'Blog kategorija',
+ 'all_blog_categories' => 'Vse blog kategorije',
+ 'blog_post' => 'Blog objava',
+ 'all_blog_posts' => 'Vse blog objave',
+ 'category_blog_posts' => 'Objave v kategoriji',
+ ],
+ 'settings' => [
+ 'category_title' => 'Seznam kategorij',
+ 'category_description' => 'Prikaži seznam blog kategorij na strani.',
+ 'category_slug' => 'Povezava kategorije',
+ 'category_slug_description' => 'Omogoča pregled blog kategorije na podani povezavi. Ta lastnost je uporabljena v privzeti predlogi komponente za označevanje trenutno aktivne kategorije.',
+ 'category_display_empty' => 'Prikaži prazne kategorije',
+ 'category_display_empty_description' => 'Prikaže kategorije brez objav.',
+ 'category_page' => 'Stran s kategorijo',
+ 'category_page_description' => 'Naslov strani blog kategorij za ustvarjanje povezav. Ta lastnost je uporabljena v privzeti predlogi komponente.',
+ 'post_title' => 'Objava',
+ 'post_description' => 'Prikaži blog objavo na strani.',
+ 'post_slug' => 'Povezava objave',
+ 'post_slug_description' => "Omogoča pregled blog objave na podani povezavi.",
+ 'post_category' => 'Stran s kategorijo',
+ 'post_category_description' => 'Naslov strani blog kategorije za ustvarjanje povezav. Ta lastnost je uporabljena v privzeti predlogi komponente.',
+ 'posts_title' => 'Seznam objav',
+ 'posts_description' => 'Prikaži seznam najnovejših blog objav na strani.',
+ 'posts_pagination' => 'Številka strani',
+ 'posts_pagination_description' => 'Ta vrednost se uporablja za določitev, na kateri strani se nahaja uporabnik.',
+ 'posts_filter' => 'Filter kategorij',
+ 'posts_filter_description' => 'Vnesite povezavo kategorije ali URL parameter za filtriranje objav. Pustite prazno za prikaz vseh objav.',
+ 'posts_per_page' => 'Število objav na strani',
+ 'posts_per_page_validation' => 'Neveljaven format števila objav na strani',
+ 'posts_no_posts' => 'Sporočilo brez objav',
+ 'posts_no_posts_description' => 'Sporočilo, ki se prikaže na seznamu blog objav, če ni nobenih objav. Ta lastnost je uporabljena v privzeti predlogi komponente.',
+ 'posts_no_posts_default' => 'Ni najdenih objav',
+ 'posts_order' => 'Vrstni red objav',
+ 'posts_order_description' => 'Lastnost, glede na katero naj bodo razvrščene objave',
+ 'posts_category' => 'Stran s kategorijo',
+ 'posts_category_description' => 'Naslov strani blog kategorije za povezave "Objavljeno v". Ta lastnost je uporabljena v privzeti predlogi komponente.',
+ 'posts_post' => 'Stran z objavo',
+ 'posts_post_description' => 'Naslov strani blog objave za povezave "Preberi več". Ta lastnost je uporabljena v privzeti predlogi komponente.',
+ 'posts_except_post' => 'Izvzete objave',
+ 'posts_except_post_description' => 'Vnesite ID/URL objave ali spremenljivko z ID-jem/URL-jem objave, ki jo želite izvzeti. Za določitev večjega števila objav lahko uporabite seznam, ločen z vejicami.',
+ 'posts_except_post_validation' => 'Izvzete objave morajo biti posamezna povezava ali ID objave ali pa seznam povezav oz. ID-jev objav, ločen z vejicami.',
+ 'posts_except_categories' => 'Izvzete kategorije',
+ 'posts_except_categories_description' => 'Vnesite seznam povezav kategorij, ločen z vejicami ali pa spremenljivko, ki vključuje takšen seznam kategorij, ki jih želite izvzeti.',
+ 'posts_except_categories_validation' => 'Izvzete kategorije morajo biti posamezna povezava kategorije ali pa seznam povezav kategorij, ločen z vejicami.',
+ 'rssfeed_blog' => 'Stran z blogom',
+ 'rssfeed_blog_description' => 'Naslov glavne strani bloga za ustvarjanje povezav. Ta lastnost je uporabljena v privzeti predlogi komponente.',
+ 'rssfeed_title' => 'RSS vir',
+ 'rssfeed_description' => 'Ustvari vir RSS, ki vsebuje objave iz bloga.',
+ 'group_links' => 'Povezave',
+ 'group_exceptions' => 'Izjeme',
+ ],
+ 'sorting' => [
+ 'title_asc' => 'Naslov (naraščajoče)',
+ 'title_desc' => 'Naslov (padajoče)',
+ 'created_asc' => 'Ustvarjeno (naraščajoče)',
+ 'created_desc' => 'Ustvarjeno (padajoče)',
+ 'updated_asc' => 'Posodobljeno (naraščajoče)',
+ 'updated_desc' => 'Posodobljeno (padajoče)',
+ 'published_asc' => 'Objavljeno (naraščajoče)',
+ 'published_desc' => 'Objavljeno (padajoče)',
+ 'random' => 'Naključno',
+ ],
+ 'import' => [
+ 'update_existing_label' => 'Posodobi obstoječe objave',
+ 'update_existing_comment' => 'Označite kvadratek, če želite posodobiti objave, ki imajo popolnoma enak ID, naslov ali povezavo.',
+ 'auto_create_categories_label' => 'Ustvari kategorije, določene v uvozni datoteki',
+ 'auto_create_categories_comment' => "Za uporabo te možnosti morate ali povezati stolpec 'Kategorije' ali pa označiti privzete kategorije za uporabo iz spodnjega seznama.",
+ 'categories_label' => 'Kategorije',
+ 'categories_comment' => 'Izberite kategorije, na katere bodo povezavne uvožene objave (neobvezno).',
+ 'default_author_label' => 'Privzeti avtor objave (neobvezno)',
+ 'default_author_comment' => "Uvoz bo poskusil uporabiti obstoječega avtorja, če povežete stolpec 'E-pošta avtorja', sicer se bo uporabil zgoraj navedeni avtor.",
+ 'default_author_placeholder' => '-- izberite avtorja --',
+ ],
+];
diff --git a/plugins/rainlab/blog/lang/tr/lang.php b/plugins/rainlab/blog/lang/tr/lang.php
new file mode 100644
index 00000000..25f23f97
--- /dev/null
+++ b/plugins/rainlab/blog/lang/tr/lang.php
@@ -0,0 +1,161 @@
+ [
+ 'name' => 'Blog',
+ 'description' => 'Sağlam blog platformu.',
+ ],
+ 'blog' => [
+ 'menu_label' => 'Blog',
+ 'menu_description' => 'Blog Gönderilerini Yönet',
+ 'posts' => 'Gönderiler',
+ 'create_post' => 'Blog gönderisi',
+ 'categories' => 'Kategoriler',
+ 'create_category' => 'Blog kategorisi',
+ 'tab' => 'Blog',
+ 'access_posts' => 'Gönderileri yönetebilsin',
+ 'access_categories' => 'Blog kategorilerini yönetebilsin',
+ 'access_other_posts' => 'Diğer kullanıcıların gönderilerini yönetebilsin',
+ 'access_import_export' => 'Gönderileri içeri/dışarı aktarabilsin',
+ 'access_publish' => 'Gönderi yayınlayabilsin',
+ 'manage_settings' => 'Blog ayarlarını yönet',
+ 'delete_confirm' => 'Emin misiniz?',
+ 'chart_published' => 'Yayınlandı',
+ 'chart_drafts' => 'Taslaklar',
+ 'chart_total' => 'Toplam',
+ 'settings_description' => 'Blog ayarlarını yönet',
+ 'show_all_posts_label' => 'Tüm gönderileri yönetim paneli kullanıcılarına göster',
+ 'show_all_posts_comment' => 'Hem yayınlanmış hem de yayınlanmamış gönderileri önyüzde yönetim paneli kullanıcılarına göster.',
+ 'tab_general' => 'Genel',
+ ],
+ 'posts' => [
+ 'list_title' => 'Blog gönderilerini yönet',
+ 'filter_category' => 'Kategori',
+ 'filter_published' => 'Yayınlanan',
+ 'filter_date' => 'Tarih',
+ 'new_post' => 'Yeni gönderi',
+ 'export_post' => 'Gönderileri dışarı aktar',
+ 'import_post' => 'Gönderileri içeri aktar',
+ ],
+ 'post' => [
+ 'title' => 'Başlık',
+ 'title_placeholder' => 'Yeni gönderi başlığı',
+ 'content' => 'İçerik',
+ 'content_html' => 'HTML İçeriği',
+ 'slug' => 'Kısa URL',
+ 'slug_placeholder' => 'yeni-gonderi-basligi',
+ 'categories' => 'Kategoriler',
+ 'author_email' => 'Yazar E-mail',
+ 'created' => 'Oluşturuldu',
+ 'created_date' => 'Oluşturulma tarihi',
+ 'updated' => 'Güncellendi',
+ 'updated_date' => 'Güncellenme tarihi',
+ 'published' => 'Yayınlandı',
+ 'published_date' => 'Yayınlanma tarihi',
+ 'published_validation' => 'Lütfen yayınlama tarihini belirtiniz',
+ 'tab_edit' => 'Düzenle',
+ 'tab_categories' => 'Kategoriler',
+ 'categories_comment' => 'Gönderinin ait olduğu kategorileri seçiniz',
+ 'categories_placeholder' => 'Kategori yok, öncelikle bir kategori oluşturmalısınız!',
+ 'tab_manage' => 'Yönet',
+ 'published_on' => 'Yayınlandı',
+ 'excerpt' => 'Alıntı',
+ 'summary' => 'Özet',
+ 'featured_images' => 'Öne Çıkan Görseller',
+ 'delete_confirm' => 'Bu yazıyı silmek istiyor musunuz?',
+ 'delete_success' => 'Gönderi(ler) silindi.',
+ 'close_confirm' => 'Gönderi kaydedilmedi.',
+ 'return_to_posts' => 'Gönderi listesine dön',
+ ],
+ 'categories' => [
+ 'list_title' => 'Blog kategorilerini yönet',
+ 'new_category' => 'Yeni kategori',
+ 'uncategorized' => 'Kategorisiz',
+ ],
+ 'category' => [
+ 'name' => 'İsim',
+ 'name_placeholder' => 'Yeni kategori adı',
+ 'description' => 'Açıklama',
+ 'slug' => 'Kısa URL',
+ 'slug_placeholder' => 'yeni-kategori-basligi',
+ 'posts' => 'Gönderiler',
+ 'delete_confirm' => 'Bu kategoriyi silmek istiyor musunuz?',
+ 'delete_success' => 'Kategori(ler) silindi.',
+ 'return_to_categories' => 'Kategori listesine dön',
+ 'reorder' => 'Kategorileri yeniden sırala',
+ ],
+ 'menuitem' => [
+ 'blog_category' => 'Blog kategorisi',
+ 'all_blog_categories' => 'Tüm blog kategorileri',
+ 'blog_post' => 'Blog gönderisi',
+ 'all_blog_posts' => 'Tüm blog gönderileri',
+ 'category_blog_posts' => 'Blog kategori gönderileri',
+ ],
+ 'settings' => [
+ 'category_title' => 'Kategori Listesi',
+ 'category_description' => 'Kategorilerin listesini sayfada göster.',
+ 'category_slug' => 'Kategori Kısa URL',
+ 'category_slug_description' => 'Verilen kısa URLi kullanarak blog kategorisini görüntüle. Bu özellik şu anki aktif kategoriyi işaretlemek için varsayılan kısmi bileşeni tarafından kullanılır',
+ 'category_display_empty' => 'Boş kategorileri göster',
+ 'category_display_empty_description' => 'Herhangi bir gönderi olmayan kategorileri göster.',
+ 'category_page' => 'Kategori sayfası',
+ 'category_page_description' => 'Kategori bağlantıları için kategori sayfası dosyasının adı. Bu özellik varsayılan kısmi bileşeni tarafından kullanılır.',
+ 'post_title' => 'Gönderi',
+ 'post_description' => 'Sayfada bir blog gönderisi gösterir.',
+ 'post_slug' => 'Gönderi Kısa URL',
+ 'post_slug_description' => 'Verilen kısa URL ile blog gönderisine bakın.',
+ 'post_category' => 'Kategori sayfası',
+ 'post_category_description' => 'Kategori bağlantıları için kategori sayfası dosyasının adı. Bu özellik varsayılan kısmi bileşeni tarafından kullanılır.',
+ 'posts_title' => 'Gönderi listesi',
+ 'posts_description' => 'Sayfada son blog gönderilerinin listesini gösterir.',
+ 'posts_pagination' => 'Sayfa numarası',
+ 'posts_pagination_description' => 'Bu değer kullanıcının hangi sayfada olduğunu belirlemek için kullanılır.',
+ 'posts_filter' => 'Kategori filtresi',
+ 'posts_filter_description' => 'Gönderileri filtrelemek için kategori kısa URLsi ya da URL parametresi girin. Tüm gönderiler için boş bırakın.',
+ 'posts_per_page' => 'Sayfa başına gönderi',
+ 'posts_per_page_validation' => 'Sayfa başına gönderi için geçersiz format',
+ 'posts_no_posts' => 'Gönderi mesajı yok',
+ 'posts_no_posts_description' => 'Eğer bir gönderi yoksa gönderi listesinde görüntülenecek mesaj. Bu özellik varsayılan kısmi bileşeni tarafından kullanılır.',
+ 'posts_no_posts_default' => 'Gönderi yok',
+ 'posts_order' => 'Gönderi Sırası',
+ 'posts_order_description' => 'Gönderilerin sıralama türü',
+ 'posts_category' => 'Kategori sayfası',
+ 'posts_category_description' => '"Yayınlanan" kategori bağlantıları için kategori sayfası dosyasının adı. Bu özellik varsayılan kısmi bileşeni tarafından kullanılır.',
+ 'posts_post' => 'Gönderi sayfası',
+ 'posts_post_description' => '"Daha fazla bilgi edinin" bağlantıları için gönderi sayfası dosyasının adı. Bu özellik varsayılan kısmi bileşeni tarafından kullanılır.',
+ 'posts_except_post' => 'Hariç tutulacak gönderi',
+ 'posts_except_post_description' => 'Hariç tutmak istediğiniz gönderinin ID/URL ini veya ID/URL içeren bir değişken girin. Birden çok gönderi belirtmek için virgülle ayrılmış liste kullanabilirsiniz.',
+ 'posts_except_post_validation' => 'Hariç tutulacak gönderi değeri tek bir kısa URL veya ID, veya virgülle ayrılmış kısa URL veya ID listesi olmalıdır.',
+ 'posts_except_categories' => 'Hariç tutulacak kategoriler',
+ 'posts_except_categories_description' => 'Hariç tutmak istediğiniz kategori listesini içeren virgülle ayrılmış bir kategori listesi veya listeyi içeren bir değişken girin.',
+ 'posts_except_categories_validation' => 'Hariç tutulacak kategoriler değeri tek bir kısa URL, veya virgülle ayrılmış kısa URL listesi olmalıdır.',
+ 'rssfeed_blog' => 'Blog sayfası',
+ 'rssfeed_blog_description' => 'Linkleri üretmek için ana blog sayfasının adı. Bu özellik, varsayılan kısmi bileşeni tarafından kullanılır.',
+ 'rssfeed_title' => 'RSS Beslemesi',
+ 'rssfeed_description' => 'Blog içerisindeki gönderileri veren RSS beslemesi oluşturur.',
+ 'group_links' => 'Linkler',
+ 'group_exceptions' => 'Hariç olanlar',
+ ],
+ 'sorting' => [
+ 'title_asc' => 'Başlık (a-z)',
+ 'title_desc' => 'Başlık (z-a)',
+ 'created_asc' => 'Oluşturulma (yeniden eskiye)',
+ 'created_desc' => 'Oluşturulma (eskiden yeniye)',
+ 'updated_asc' => 'Güncellenme (yeniden eskiye)',
+ 'updated_desc' => 'Güncellenme (eskiden yeniye)',
+ 'published_asc' => 'Yayınlanma (yeniden eskiye)',
+ 'published_desc' => 'Yayınlanma (eskiden yeniye)',
+ 'random' => 'Rastgele',
+ ],
+ 'import' => [
+ 'update_existing_label' => 'Mevcut gönderileri güncelle',
+ 'update_existing_comment' => 'Tam olarak aynı ID, başlık veya kısa URL içeren gönderileri güncellemek için bu kutuyu işaretleyin.',
+ 'auto_create_categories_label' => 'İçe aktarma dosyasında bulunan kategorileri oluştur',
+ 'auto_create_categories_comment' => 'Bu özelliği kullanmak için Kategoriler sütununu eşleştirmelisiniz, aksi takdirde aşağıdaki öğelerden kullanılacak varsayılan kategorileri seçmelisiniz.',
+ 'categories_label' => 'Kategoriler',
+ 'categories_comment' => 'İçe aktarılan gönderilerin ait olacağı kategorileri seçin (isteğe bağlı).',
+ 'default_author_label' => 'Varsayılan gönderi yazarı (isteğe bağlı)',
+ 'default_author_comment' => 'İçe aktarma, Yazar E-postası sütunuyla eşleşirse mevcut bir yazarı gönderi için kullanmaya çalışır, aksi takdirde yukarıda belirtilen yazar seçilir.',
+ 'default_author_placeholder' => '-- yazar seçin --',
+ ],
+];
\ No newline at end of file
diff --git a/plugins/rainlab/blog/lang/zh-cn/lang.php b/plugins/rainlab/blog/lang/zh-cn/lang.php
new file mode 100644
index 00000000..f1e2e3de
--- /dev/null
+++ b/plugins/rainlab/blog/lang/zh-cn/lang.php
@@ -0,0 +1,124 @@
+ [
+ 'name' => '博客',
+ 'description' => '一个强大的博客平台.'
+ ],
+ 'blog' => [
+ 'menu_label' => '博客',
+ 'menu_description' => '管理博客帖子',
+ 'posts' => '帖子',
+ 'create_post' => '博客帖子',
+ 'categories' => '分类',
+ 'create_category' => '博客分类',
+ 'tab' => '博客',
+ 'access_posts' => '管理博客帖子',
+ 'access_categories' => '管理博客分类',
+ 'access_other_posts' => '管理其他用户帖子',
+ 'access_import_export' => '允许导入和导出',
+ 'access_publish' => '允许发布帖子',
+ 'delete_confirm' => '你确定?',
+ 'chart_published' => '已发布',
+ 'chart_drafts' => '草稿',
+ 'chart_total' => '总数'
+ ],
+ 'posts' => [
+ 'list_title' => '管理博客帖子',
+ 'filter_category' => '分类',
+ 'filter_published' => '发布',
+ 'filter_date' => '日期',
+ 'new_post' => '创建帖子',
+ 'export_post' => '导出帖子',
+ 'import_post' => '导入帖子'
+ ],
+ 'post' => [
+ 'title' => '标题',
+ 'title_placeholder' => '新帖子标题',
+ 'content' => '内容',
+ 'content_html' => 'HTML 内容',
+ 'slug' => '别名',
+ 'slug_placeholder' => 'new-post-slug',
+ 'categories' => '分类',
+ 'author_email' => '作者邮箱',
+ 'created' => '创建时间',
+ 'created_date' => '创建日期',
+ 'updated' => '更新时间',
+ 'updated_date' => '更新日期',
+ 'published' => '发布时间',
+ 'published_date' => '发布日期',
+ 'published_validation' => '请指定发布日期',
+ 'tab_edit' => '编辑',
+ 'tab_categories' => '分类',
+ 'categories_comment' => '选择帖子属于那个分类',
+ 'categories_placeholder' => '没有分类,你应该先创建一个分类!',
+ 'tab_manage' => '管理',
+ 'published_on' => '发布于',
+ 'excerpt' => '摘录',
+ 'summary' => '总结',
+ 'featured_images' => '特色图片',
+ 'delete_confirm' => '确定删除该帖子?',
+ 'close_confirm' => '该帖子未保存.',
+ 'return_to_posts' => '返回帖子列表'
+ ],
+ 'categories' => [
+ 'list_title' => '管理博客分类',
+ 'new_category' => '新分类',
+ 'uncategorized' => '未分类'
+ ],
+ 'category' => [
+ 'name' => '名称',
+ 'name_placeholder' => '新分类名称',
+ 'description' => '描述',
+ 'slug' => '别名',
+ 'slug_placeholder' => 'new-category-slug',
+ 'posts' => '帖子',
+ 'delete_confirm' => '确定删除分类?',
+ 'return_to_categories' => '返回博客分类列表',
+ 'reorder' => '重新排序分类'
+ ],
+ 'menuitem' => [
+ 'blog_category' => '博客分类',
+ 'all_blog_categories' => '所有博客分类',
+ 'blog_post' => '博客帖子',
+ 'all_blog_posts' => '所有博客帖子'
+ ],
+ 'settings' => [
+ 'category_title' => '分类列表',
+ 'category_description' => '在页面上显示帖子分类列表.',
+ 'category_slug' => '分类别名',
+ 'category_slug_description' => "用分类别名查找博客分类. 该值被默认组件的partial用来激活当前分类.",
+ 'category_display_empty' => '显示空的分类',
+ 'category_display_empty_description' => '显示没有帖子的分类.',
+ 'category_page' => '分类页',
+ 'category_page_description' => '用来生成分类链接的分类页面文件名称. 该属性被默认组件partial所使用.',
+ 'post_title' => '帖子',
+ 'post_description' => '在页面上显示博客帖子.',
+ 'post_slug' => '帖子别名',
+ 'post_slug_description' => "用帖子别名查找博客帖子.",
+ 'post_category' => '分类页',
+ 'post_category_description' => '用来生成分类链接的分类页面文件名称. 该属性被默认组件partial所使用.',
+ 'posts_title' => '帖子列表',
+ 'posts_description' => '在页面上显示最近发布的博客帖子列表.',
+ 'posts_pagination' => '页数',
+ 'posts_pagination_description' => '该值用来判定用户所在页面.',
+ 'posts_filter' => '过滤分类',
+ 'posts_filter_description' => '输入分类的别名(slug)或者URL参数来过滤帖子. 留空显示所有帖子.',
+ 'posts_per_page' => '每页帖子数',
+ 'posts_per_page_validation' => '每一页帖子数量的值的格式错误',
+ 'posts_no_posts' => '没有帖子的消息',
+ 'posts_no_posts_description' => '如果博客帖子列表中一个帖子都没有要显示的提示消息. 该属性被默认组件partial所使用.',
+ 'posts_order' => '帖子排序',
+ 'posts_order_description' => '帖子排序的属性',
+ 'posts_category' => '分类页',
+ 'posts_category_description' => '用来生成"发布到"分类链接的分类页文件名称. 该属性被默认组件partial所使用.',
+ 'posts_post' => '帖子页',
+ 'posts_post_description' => ' 查看帖子的"详情"的页面文件. 该属性被默认组件partial所使用.',
+ 'posts_except_post' => '排除的帖子',
+ 'posts_except_post_description' => '输入帖子的ID/URL或者变量来排除你不想看见的帖子',
+ 'rssfeed_blog' => '博客页面',
+ 'rssfeed_blog_description' => '生成博客帖子首页面文件名称. 该属性被默认组件partial所使用.',
+ 'rssfeed_title' => 'RSS Feed',
+ 'rssfeed_description' => '从博客生成一个包含帖子的RSS Feed.'
+ ]
+];
diff --git a/plugins/rainlab/blog/models/Category.php b/plugins/rainlab/blog/models/Category.php
new file mode 100644
index 00000000..7f833552
--- /dev/null
+++ b/plugins/rainlab/blog/models/Category.php
@@ -0,0 +1,315 @@
+ 'required',
+ 'slug' => 'required|between:3,64|unique:rainlab_blog_categories',
+ 'code' => 'nullable|unique:rainlab_blog_categories',
+ ];
+
+ /**
+ * @var array Attributes that support translation, if available.
+ */
+ public $translatable = [
+ 'name',
+ 'description',
+ ['slug', 'index' => true]
+ ];
+
+ protected $guarded = [];
+
+ public $belongsToMany = [
+ 'posts' => ['RainLab\Blog\Models\Post',
+ 'table' => 'rainlab_blog_posts_categories',
+ 'order' => 'published_at desc',
+ 'scope' => 'isPublished'
+ ],
+ 'posts_count' => ['RainLab\Blog\Models\Post',
+ 'table' => 'rainlab_blog_posts_categories',
+ 'scope' => 'isPublished',
+ 'count' => true
+ ]
+ ];
+
+ public function beforeValidate()
+ {
+ // Generate a URL slug for this model
+ if (!$this->exists && !$this->slug) {
+ $this->slug = Str::slug($this->name);
+ }
+ }
+
+ public function afterDelete()
+ {
+ $this->posts()->detach();
+ }
+
+ public function getPostCountAttribute()
+ {
+ return optional($this->posts_count->first())->count ?? 0;
+ }
+
+ /**
+ * Count posts in this and nested categories
+ * @return int
+ */
+ public function getNestedPostCount()
+ {
+ return $this->post_count + $this->children->sum(function ($category) {
+ return $category->getNestedPostCount();
+ });
+ }
+
+ /**
+ * Sets the "url" attribute with a URL to this object
+ *
+ * @param string $pageName
+ * @param Cms\Classes\Controller $controller
+ *
+ * @return string
+ */
+ public function setUrl($pageName, $controller)
+ {
+ $params = [
+ 'id' => $this->id,
+ 'slug' => $this->slug
+ ];
+
+ return $this->url = $controller->pageUrl($pageName, $params, false);
+ }
+
+ /**
+ * Handler for the pages.menuitem.getTypeInfo event.
+ * Returns a menu item type information. The type information is returned as array
+ * with the following elements:
+ * - references - a list of the item type reference options. The options are returned in the
+ * ["key"] => "title" format for options that don't have sub-options, and in the format
+ * ["key"] => ["title"=>"Option title", "items"=>[...]] for options that have sub-options. Optional,
+ * required only if the menu item type requires references.
+ * - nesting - Boolean value indicating whether the item type supports nested items. Optional,
+ * false if omitted.
+ * - dynamicItems - Boolean value indicating whether the item type could generate new menu items.
+ * Optional, false if omitted.
+ * - cmsPages - a list of CMS pages (objects of the Cms\Classes\Page class), if the item type requires a CMS page reference to
+ * resolve the item URL.
+ * @param string $type Specifies the menu item type
+ * @return array Returns an array
+ */
+ public static function getMenuTypeInfo($type)
+ {
+ $result = [];
+
+ if ($type == 'blog-category') {
+ $result = [
+ 'references' => self::listSubCategoryOptions(),
+ 'nesting' => true,
+ 'dynamicItems' => true
+ ];
+ }
+
+ if ($type == 'all-blog-categories') {
+ $result = [
+ 'dynamicItems' => true
+ ];
+ }
+
+ if ($result) {
+ $theme = Theme::getActiveTheme();
+
+ $pages = CmsPage::listInTheme($theme, true);
+ $cmsPages = [];
+ foreach ($pages as $page) {
+ if (!$page->hasComponent('blogPosts')) {
+ continue;
+ }
+
+ /*
+ * Component must use a category filter with a routing parameter
+ * eg: categoryFilter = "{{ :somevalue }}"
+ */
+ $properties = $page->getComponentProperties('blogPosts');
+ if (!isset($properties['categoryFilter']) || !preg_match('/{{\s*:/', $properties['categoryFilter'])) {
+ continue;
+ }
+
+ $cmsPages[] = $page;
+ }
+
+ $result['cmsPages'] = $cmsPages;
+ }
+
+ return $result;
+ }
+
+ protected static function listSubCategoryOptions()
+ {
+ $category = self::getNested();
+
+ $iterator = function($categories) use (&$iterator) {
+ $result = [];
+
+ foreach ($categories as $category) {
+ if (!$category->children) {
+ $result[$category->id] = $category->name;
+ }
+ else {
+ $result[$category->id] = [
+ 'title' => $category->name,
+ 'items' => $iterator($category->children)
+ ];
+ }
+ }
+
+ return $result;
+ };
+
+ return $iterator($category);
+ }
+
+ /**
+ * Handler for the pages.menuitem.resolveItem event.
+ * Returns information about a menu item. The result is an array
+ * with the following keys:
+ * - url - the menu item URL. Not required for menu item types that return all available records.
+ * The URL should be returned relative to the website root and include the subdirectory, if any.
+ * Use the Url::to() helper to generate the URLs.
+ * - isActive - determines whether the menu item is active. Not required for menu item types that
+ * return all available records.
+ * - items - an array of arrays with the same keys (url, isActive, items) + the title key.
+ * The items array should be added only if the $item's $nesting property value is TRUE.
+ * @param \RainLab\Pages\Classes\MenuItem $item Specifies the menu item.
+ * @param \Cms\Classes\Theme $theme Specifies the current theme.
+ * @param string $url Specifies the current page URL, normalized, in lower case
+ * The URL is specified relative to the website root, it includes the subdirectory name, if any.
+ * @return mixed Returns an array. Returns null if the item cannot be resolved.
+ */
+ public static function resolveMenuItem($item, $url, $theme)
+ {
+ $result = null;
+
+ if ($item->type == 'blog-category') {
+ if (!$item->reference || !$item->cmsPage) {
+ return;
+ }
+
+ $category = self::find($item->reference);
+ if (!$category) {
+ return;
+ }
+
+ $pageUrl = self::getCategoryPageUrl($item->cmsPage, $category, $theme);
+ if (!$pageUrl) {
+ return;
+ }
+
+ $pageUrl = Url::to($pageUrl);
+
+ $result = [];
+ $result['url'] = $pageUrl;
+ $result['isActive'] = $pageUrl == $url;
+ $result['mtime'] = $category->updated_at;
+
+ if ($item->nesting) {
+ $iterator = function($categories) use (&$iterator, &$item, &$theme, $url) {
+ $branch = [];
+
+ foreach ($categories as $category) {
+
+ $branchItem = [];
+ $branchItem['url'] = self::getCategoryPageUrl($item->cmsPage, $category, $theme);
+ $branchItem['isActive'] = $branchItem['url'] == $url;
+ $branchItem['title'] = $category->name;
+ $branchItem['mtime'] = $category->updated_at;
+
+ if ($category->children) {
+ $branchItem['items'] = $iterator($category->children);
+ }
+
+ $branch[] = $branchItem;
+ }
+
+ return $branch;
+ };
+
+ $result['items'] = $iterator($category->children);
+ }
+ }
+ elseif ($item->type == 'all-blog-categories') {
+ $result = [
+ 'items' => []
+ ];
+
+ $categories = self::with('posts_count')->orderBy('name')->get();
+ foreach ($categories as $category) {
+ try {
+ $postCount = $category->posts_count->first()->count ?? null;
+ if ($postCount === 0) {
+ continue;
+ }
+ }
+ catch (\Exception $ex) {}
+
+ $categoryItem = [
+ 'title' => $category->name,
+ 'url' => self::getCategoryPageUrl($item->cmsPage, $category, $theme),
+ 'mtime' => $category->updated_at
+ ];
+
+ $categoryItem['isActive'] = $categoryItem['url'] == $url;
+
+ $result['items'][] = $categoryItem;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Returns URL of a category page.
+ *
+ * @param $pageCode
+ * @param $category
+ * @param $theme
+ */
+ protected static function getCategoryPageUrl($pageCode, $category, $theme)
+ {
+ $page = CmsPage::loadCached($theme, $pageCode);
+ if (!$page) {
+ return;
+ }
+
+ $properties = $page->getComponentProperties('blogPosts');
+ if (!isset($properties['categoryFilter'])) {
+ return;
+ }
+
+ /*
+ * Extract the routing parameter name from the category filter
+ * eg: {{ :someRouteParam }}
+ */
+ if (!preg_match('/^\{\{([^\}]+)\}\}$/', $properties['categoryFilter'], $matches)) {
+ return;
+ }
+
+ $paramName = substr(trim($matches[1]), 1);
+ $url = CmsPage::url($page->getBaseFileName(), [$paramName => $category->slug]);
+
+ return $url;
+ }
+}
diff --git a/plugins/rainlab/blog/models/Post.php b/plugins/rainlab/blog/models/Post.php
new file mode 100644
index 00000000..5f7ee6cf
--- /dev/null
+++ b/plugins/rainlab/blog/models/Post.php
@@ -0,0 +1,708 @@
+ 'required',
+ 'slug' => ['required', 'regex:/^[a-z0-9\/\:_\-\*\[\]\+\?\|]*$/i', 'unique:rainlab_blog_posts'],
+ 'content' => 'required',
+ 'excerpt' => ''
+ ];
+
+ /**
+ * @var array Attributes that support translation, if available.
+ */
+ public $translatable = [
+ 'title',
+ 'content',
+ 'content_html',
+ 'excerpt',
+ 'metadata',
+ ['slug', 'index' => true]
+ ];
+
+ /**
+ * @var array Attributes to be stored as JSON
+ */
+ protected $jsonable = ['metadata'];
+
+ /**
+ * The attributes that should be mutated to dates.
+ * @var array
+ */
+ protected $dates = ['published_at'];
+
+ /**
+ * The attributes on which the post list can be ordered.
+ * @var array
+ */
+ public static $allowedSortingOptions = [
+ 'title asc' => 'rainlab.blog::lang.sorting.title_asc',
+ 'title desc' => 'rainlab.blog::lang.sorting.title_desc',
+ 'created_at asc' => 'rainlab.blog::lang.sorting.created_asc',
+ 'created_at desc' => 'rainlab.blog::lang.sorting.created_desc',
+ 'updated_at asc' => 'rainlab.blog::lang.sorting.updated_asc',
+ 'updated_at desc' => 'rainlab.blog::lang.sorting.updated_desc',
+ 'published_at asc' => 'rainlab.blog::lang.sorting.published_asc',
+ 'published_at desc' => 'rainlab.blog::lang.sorting.published_desc',
+ 'random' => 'rainlab.blog::lang.sorting.random'
+ ];
+
+ /*
+ * Relations
+ */
+ public $belongsTo = [
+ 'user' => BackendUser::class
+ ];
+
+ public $belongsToMany = [
+ 'categories' => [
+ Category::class,
+ 'table' => 'rainlab_blog_posts_categories',
+ 'order' => 'name'
+ ]
+ ];
+
+ public $attachMany = [
+ 'featured_images' => [\System\Models\File::class, 'order' => 'sort_order'],
+ 'content_images' => \System\Models\File::class
+ ];
+
+ /**
+ * @var array The accessors to append to the model's array form.
+ */
+ protected $appends = ['summary', 'has_summary'];
+
+ public $preview = null;
+
+ /**
+ * Limit visibility of the published-button
+ *
+ * @param $fields
+ * @param null $context
+ * @return void
+ */
+ public function filterFields($fields, $context = null)
+ {
+ if (!isset($fields->published, $fields->published_at)) {
+ return;
+ }
+
+ $user = BackendAuth::getUser();
+
+ if (!$user->hasAnyAccess(['rainlab.blog.access_publish'])) {
+ $fields->published->hidden = true;
+ $fields->published_at->hidden = true;
+ }
+ else {
+ $fields->published->hidden = false;
+ $fields->published_at->hidden = false;
+ }
+ }
+
+ public function afterValidate()
+ {
+ if ($this->published && !$this->published_at) {
+ throw new ValidationException([
+ 'published_at' => Lang::get('rainlab.blog::lang.post.published_validation')
+ ]);
+ }
+ }
+
+ public function getUserOptions()
+ {
+ $options = [];
+
+ foreach (BackendUser::all() as $user) {
+ $options[$user->id] = $user->fullname . ' ('.$user->login.')';
+ }
+
+ return $options;
+ }
+
+ public function beforeSave()
+ {
+ if (empty($this->user)) {
+ $user = BackendAuth::getUser();
+ if (!is_null($user)) {
+ $this->user = $user->id;
+ }
+ }
+ $this->content_html = self::formatHtml($this->content);
+ }
+
+ /**
+ * Sets the "url" attribute with a URL to this object.
+ * @param string $pageName
+ * @param Controller $controller
+ * @param array $params Override request URL parameters
+ *
+ * @return string
+ */
+ public function setUrl($pageName, $controller, $params = [])
+ {
+ $params = array_merge([
+ 'id' => $this->id,
+ 'slug' => $this->slug,
+ ], $params);
+
+ if (empty($params['category'])) {
+ $params['category'] = $this->categories->count() ? $this->categories->first()->slug : null;
+ }
+
+ // Expose published year, month and day as URL parameters.
+ if ($this->published) {
+ $params['year'] = $this->published_at->format('Y');
+ $params['month'] = $this->published_at->format('m');
+ $params['day'] = $this->published_at->format('d');
+ }
+
+ return $this->url = $controller->pageUrl($pageName, $params);
+ }
+
+ /**
+ * Used to test if a certain user has permission to edit post,
+ * returns TRUE if the user is the owner or has other posts access.
+ * @param BackendUser $user
+ * @return bool
+ */
+ public function canEdit(BackendUser $user)
+ {
+ return ($this->user_id == $user->id) || $user->hasAnyAccess(['rainlab.blog.access_other_posts']);
+ }
+
+ public static function formatHtml($input, $preview = false)
+ {
+ $result = Markdown::parse(trim($input));
+
+ // Check to see if the HTML should be cleaned from potential XSS
+ $user = BackendAuth::getUser();
+ if (!$user || !$user->hasAccess('backend.allow_unsafe_markdown')) {
+ $result = Html::clean($result);
+ }
+
+ if ($preview) {
+ $result = str_replace('', '', $result);
+ }
+
+ $result = TagProcessor::instance()->processTags($result, $preview);
+
+ return $result;
+ }
+
+ //
+ // Scopes
+ //
+
+ public function scopeIsPublished($query)
+ {
+ return $query
+ ->whereNotNull('published')
+ ->where('published', true)
+ ->whereNotNull('published_at')
+ ->where('published_at', '<', Carbon::now())
+ ;
+ }
+
+ /**
+ * Lists posts for the frontend
+ *
+ * @param $query
+ * @param array $options Display options
+ * @return Post
+ */
+ public function scopeListFrontEnd($query, $options)
+ {
+ /*
+ * Default options
+ */
+ extract(array_merge([
+ 'page' => 1,
+ 'perPage' => 30,
+ 'sort' => 'created_at',
+ 'categories' => null,
+ 'exceptCategories' => null,
+ 'category' => null,
+ 'search' => '',
+ 'published' => true,
+ 'exceptPost' => null
+ ], $options));
+
+ $searchableFields = ['title', 'slug', 'excerpt', 'content'];
+
+ if ($published) {
+ $query->isPublished();
+ }
+
+ /*
+ * Except post(s)
+ */
+ if ($exceptPost) {
+ $exceptPosts = (is_array($exceptPost)) ? $exceptPost : [$exceptPost];
+ $exceptPostIds = [];
+ $exceptPostSlugs = [];
+
+ foreach ($exceptPosts as $exceptPost) {
+ $exceptPost = trim($exceptPost);
+
+ if (is_numeric($exceptPost)) {
+ $exceptPostIds[] = $exceptPost;
+ } else {
+ $exceptPostSlugs[] = $exceptPost;
+ }
+ }
+
+ if (count($exceptPostIds)) {
+ $query->whereNotIn('id', $exceptPostIds);
+ }
+ if (count($exceptPostSlugs)) {
+ $query->whereNotIn('slug', $exceptPostSlugs);
+ }
+ }
+
+ /*
+ * Sorting
+ */
+ if (in_array($sort, array_keys(static::$allowedSortingOptions))) {
+ if ($sort == 'random') {
+ $query->inRandomOrder();
+ } else {
+ @list($sortField, $sortDirection) = explode(' ', $sort);
+
+ if (is_null($sortDirection)) {
+ $sortDirection = "desc";
+ }
+
+ $query->orderBy($sortField, $sortDirection);
+ }
+ }
+
+ /*
+ * Search
+ */
+ $search = trim($search);
+ if (strlen($search)) {
+ $query->searchWhere($search, $searchableFields);
+ }
+
+ /*
+ * Categories
+ */
+ if ($categories !== null) {
+ $categories = is_array($categories) ? $categories : [$categories];
+ $query->whereHas('categories', function($q) use ($categories) {
+ $q->withoutGlobalScope(NestedTreeScope::class)->whereIn('id', $categories);
+ });
+ }
+
+ /*
+ * Except Categories
+ */
+ if (!empty($exceptCategories)) {
+ $exceptCategories = is_array($exceptCategories) ? $exceptCategories : [$exceptCategories];
+ array_walk($exceptCategories, 'trim');
+
+ $query->whereDoesntHave('categories', function ($q) use ($exceptCategories) {
+ $q->withoutGlobalScope(NestedTreeScope::class)->whereIn('slug', $exceptCategories);
+ });
+ }
+
+ /*
+ * Category, including children
+ */
+ if ($category !== null) {
+ $category = Category::find($category);
+
+ $categories = $category->getAllChildrenAndSelf()->lists('id');
+ $query->whereHas('categories', function($q) use ($categories) {
+ $q->withoutGlobalScope(NestedTreeScope::class)->whereIn('id', $categories);
+ });
+ }
+
+ return $query->paginate($perPage, $page);
+ }
+
+ /**
+ * Allows filtering for specifc categories.
+ * @param Illuminate\Query\Builder $query QueryBuilder
+ * @param array $categories List of category ids
+ * @return Illuminate\Query\Builder QueryBuilder
+ */
+ public function scopeFilterCategories($query, $categories)
+ {
+ return $query->whereHas('categories', function($q) use ($categories) {
+ $q->withoutGlobalScope(NestedTreeScope::class)->whereIn('id', $categories);
+ });
+ }
+
+ //
+ // Summary / Excerpt
+ //
+
+ /**
+ * Used by "has_summary", returns true if this post uses a summary (more tag).
+ * @return boolean
+ */
+ public function getHasSummaryAttribute()
+ {
+ $more = Config::get('rainlab.blog::summary_separator', '');
+ $length = Config::get('rainlab.blog::summary_default_length', 600);
+
+ return (
+ !!strlen(trim($this->excerpt)) ||
+ strpos($this->content_html, $more) !== false ||
+ strlen(Html::strip($this->content_html)) > $length
+ );
+ }
+
+ /**
+ * Used by "summary", if no excerpt is provided, generate one from the content.
+ * Returns the HTML content before the tag or a limited 600
+ * character version.
+ *
+ * @return string
+ */
+ public function getSummaryAttribute()
+ {
+ $excerpt = $this->excerpt;
+ if (strlen(trim($excerpt))) {
+ return $excerpt;
+ }
+
+ $more = Config::get('rainlab.blog::summary_separator', '');
+
+ if (strpos($this->content_html, $more) !== false) {
+ $parts = explode($more, $this->content_html);
+
+ return array_get($parts, 0);
+ }
+
+ $length = Config::get('rainlab.blog::summary_default_length', 600);
+
+ return Html::limit($this->content_html, $length);
+ }
+
+ //
+ // Next / Previous
+ //
+
+ /**
+ * Apply a constraint to the query to find the nearest sibling
+ *
+ * // Get the next post
+ * Post::applySibling()->first();
+ *
+ * // Get the previous post
+ * Post::applySibling(-1)->first();
+ *
+ * // Get the previous post, ordered by the ID attribute instead
+ * Post::applySibling(['direction' => -1, 'attribute' => 'id'])->first();
+ *
+ * @param $query
+ * @param array $options
+ * @return
+ */
+ public function scopeApplySibling($query, $options = [])
+ {
+ if (!is_array($options)) {
+ $options = ['direction' => $options];
+ }
+
+ extract(array_merge([
+ 'direction' => 'next',
+ 'attribute' => 'published_at'
+ ], $options));
+
+ $isPrevious = in_array($direction, ['previous', -1]);
+ $directionOrder = $isPrevious ? 'asc' : 'desc';
+ $directionOperator = $isPrevious ? '>' : '<';
+
+ $query->where('id', '<>', $this->id);
+
+ if (!is_null($this->$attribute)) {
+ $query->where($attribute, $directionOperator, $this->$attribute);
+ }
+
+ return $query->orderBy($attribute, $directionOrder);
+ }
+
+ /**
+ * Returns the next post, if available.
+ * @return self
+ */
+ public function nextPost()
+ {
+ return self::isPublished()->applySibling()->first();
+ }
+
+ /**
+ * Returns the previous post, if available.
+ * @return self
+ */
+ public function previousPost()
+ {
+ return self::isPublished()->applySibling(-1)->first();
+ }
+
+ //
+ // Menu helpers
+ //
+
+ /**
+ * Handler for the pages.menuitem.getTypeInfo event.
+ * Returns a menu item type information. The type information is returned as array
+ * with the following elements:
+ * - references - a list of the item type reference options. The options are returned in the
+ * ["key"] => "title" format for options that don't have sub-options, and in the format
+ * ["key"] => ["title"=>"Option title", "items"=>[...]] for options that have sub-options. Optional,
+ * required only if the menu item type requires references.
+ * - nesting - Boolean value indicating whether the item type supports nested items. Optional,
+ * false if omitted.
+ * - dynamicItems - Boolean value indicating whether the item type could generate new menu items.
+ * Optional, false if omitted.
+ * - cmsPages - a list of CMS pages (objects of the Cms\Classes\Page class), if the item type requires a CMS page reference to
+ * resolve the item URL.
+ *
+ * @param string $type Specifies the menu item type
+ * @return array Returns an array
+ */
+ public static function getMenuTypeInfo($type)
+ {
+ $result = [];
+
+ if ($type == 'blog-post') {
+ $references = [];
+
+ $posts = self::orderBy('title')->get();
+ foreach ($posts as $post) {
+ $references[$post->id] = $post->title;
+ }
+
+ $result = [
+ 'references' => $references,
+ 'nesting' => false,
+ 'dynamicItems' => false
+ ];
+ }
+
+ if ($type == 'all-blog-posts') {
+ $result = [
+ 'dynamicItems' => true
+ ];
+ }
+
+ if ($type == 'category-blog-posts') {
+ $references = [];
+
+ $categories = Category::orderBy('name')->get();
+ foreach ($categories as $category) {
+ $references[$category->id] = $category->name;
+ }
+
+ $result = [
+ 'references' => $references,
+ 'dynamicItems' => true
+ ];
+ }
+
+ if ($result) {
+ $theme = Theme::getActiveTheme();
+
+ $pages = CmsPage::listInTheme($theme, true);
+ $cmsPages = [];
+
+ foreach ($pages as $page) {
+ if (!$page->hasComponent('blogPost')) {
+ continue;
+ }
+
+ /*
+ * Component must use a categoryPage filter with a routing parameter and post slug
+ * eg: categoryPage = "{{ :somevalue }}", slug = "{{ :somevalue }}"
+ */
+ $properties = $page->getComponentProperties('blogPost');
+ if (!isset($properties['categoryPage']) || !preg_match('/{{\s*:/', $properties['slug'])) {
+ continue;
+ }
+
+ $cmsPages[] = $page;
+ }
+
+ $result['cmsPages'] = $cmsPages;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Handler for the pages.menuitem.resolveItem event.
+ * Returns information about a menu item. The result is an array
+ * with the following keys:
+ * - url - the menu item URL. Not required for menu item types that return all available records.
+ * The URL should be returned relative to the website root and include the subdirectory, if any.
+ * Use the Url::to() helper to generate the URLs.
+ * - isActive - determines whether the menu item is active. Not required for menu item types that
+ * return all available records.
+ * - items - an array of arrays with the same keys (url, isActive, items) + the title key.
+ * The items array should be added only if the $item's $nesting property value is TRUE.
+ *
+ * @param \RainLab\Pages\Classes\MenuItem $item Specifies the menu item.
+ * @param \Cms\Classes\Theme $theme Specifies the current theme.
+ * @param string $url Specifies the current page URL, normalized, in lower case
+ * The URL is specified relative to the website root, it includes the subdirectory name, if any.
+ * @return mixed Returns an array. Returns null if the item cannot be resolved.
+ */
+ public static function resolveMenuItem($item, $url, $theme)
+ {
+ $result = null;
+
+ if ($item->type == 'blog-post') {
+ if (!$item->reference || !$item->cmsPage) {
+ return;
+ }
+
+ $category = self::find($item->reference);
+ if (!$category) {
+ return;
+ }
+
+ $pageUrl = self::getPostPageUrl($item->cmsPage, $category, $theme);
+ if (!$pageUrl) {
+ return;
+ }
+
+ $pageUrl = Url::to($pageUrl);
+
+ $result = [];
+ $result['url'] = $pageUrl;
+ $result['isActive'] = $pageUrl == $url;
+ $result['mtime'] = $category->updated_at;
+ }
+ elseif ($item->type == 'all-blog-posts') {
+ $result = [
+ 'items' => []
+ ];
+
+ $posts = self::isPublished()
+ ->orderBy('title')
+ ->get()
+ ;
+
+ foreach ($posts as $post) {
+ $postItem = [
+ 'title' => $post->title,
+ 'url' => self::getPostPageUrl($item->cmsPage, $post, $theme),
+ 'mtime' => $post->updated_at
+ ];
+
+ $postItem['isActive'] = $postItem['url'] == $url;
+
+ $result['items'][] = $postItem;
+ }
+ }
+ elseif ($item->type == 'category-blog-posts') {
+ if (!$item->reference || !$item->cmsPage) {
+ return;
+ }
+
+ $category = Category::find($item->reference);
+ if (!$category) {
+ return;
+ }
+
+ $result = [
+ 'items' => []
+ ];
+
+ $query = self::isPublished()
+ ->orderBy('title');
+
+ $categories = $category->getAllChildrenAndSelf()->lists('id');
+ $query->whereHas('categories', function($q) use ($categories) {
+ $q->withoutGlobalScope(NestedTreeScope::class)->whereIn('id', $categories);
+ });
+
+ $posts = $query->get();
+
+ foreach ($posts as $post) {
+ $postItem = [
+ 'title' => $post->title,
+ 'url' => self::getPostPageUrl($item->cmsPage, $post, $theme),
+ 'mtime' => $post->updated_at
+ ];
+
+ $postItem['isActive'] = $postItem['url'] == $url;
+
+ $result['items'][] = $postItem;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Returns URL of a post page.
+ *
+ * @param $pageCode
+ * @param $category
+ * @param $theme
+ */
+ protected static function getPostPageUrl($pageCode, $category, $theme)
+ {
+ $page = CmsPage::loadCached($theme, $pageCode);
+ if (!$page) {
+ return;
+ }
+
+ $properties = $page->getComponentProperties('blogPost');
+ if (!isset($properties['slug'])) {
+ return;
+ }
+
+ /*
+ * Extract the routing parameter name from the category filter
+ * eg: {{ :someRouteParam }}
+ */
+ if (!preg_match('/^\{\{([^\}]+)\}\}$/', $properties['slug'], $matches)) {
+ return;
+ }
+
+ $paramName = substr(trim($matches[1]), 1);
+ $params = [
+ $paramName => $category->slug,
+ 'year' => $category->published_at->format('Y'),
+ 'month' => $category->published_at->format('m'),
+ 'day' => $category->published_at->format('d')
+ ];
+ $url = CmsPage::url($page->getBaseFileName(), $params);
+
+ return $url;
+ }
+}
diff --git a/plugins/rainlab/blog/models/PostExport.php b/plugins/rainlab/blog/models/PostExport.php
new file mode 100644
index 00000000..024d94a7
--- /dev/null
+++ b/plugins/rainlab/blog/models/PostExport.php
@@ -0,0 +1,94 @@
+ [
+ 'Backend\Models\User',
+ 'key' => 'user_id'
+ ]
+ ];
+
+ public $belongsToMany = [
+ 'post_categories' => [
+ 'RainLab\Blog\Models\Category',
+ 'table' => 'rainlab_blog_posts_categories',
+ 'key' => 'post_id',
+ 'otherKey' => 'category_id'
+ ]
+ ];
+
+ public $hasMany = [
+ 'featured_images' => [
+ 'System\Models\File',
+ 'order' => 'sort_order',
+ 'key' => 'attachment_id',
+ 'conditions' => "field = 'featured_images' AND attachment_type = 'RainLab\\\\Blog\\\\Models\\\\Post'"
+ ]
+ ];
+
+ /**
+ * The accessors to append to the model's array form.
+ * @var array
+ */
+ protected $appends = [
+ 'author_email',
+ 'categories',
+ 'featured_image_urls'
+ ];
+
+ public function exportData($columns, $sessionKey = null)
+ {
+ $result = self::make()
+ ->with([
+ 'post_user',
+ 'post_categories',
+ 'featured_images'
+ ])
+ ->get()
+ ->toArray()
+ ;
+
+ return $result;
+ }
+
+ public function getAuthorEmailAttribute()
+ {
+ if (!$this->post_user) {
+ return '';
+ }
+
+ return $this->post_user->email;
+ }
+
+ public function getCategoriesAttribute()
+ {
+ if (!$this->post_categories) {
+ return '';
+ }
+
+ return $this->encodeArrayValue($this->post_categories->lists('name'));
+ }
+
+ public function getFeaturedImageUrlsAttribute()
+ {
+ if (!$this->featured_images) {
+ return '';
+ }
+
+ return $this->encodeArrayValue($this->featured_images->map(function ($image) {
+ return $image->getPath();
+ }));
+ }
+}
diff --git a/plugins/rainlab/blog/models/PostImport.php b/plugins/rainlab/blog/models/PostImport.php
new file mode 100644
index 00000000..858d9f05
--- /dev/null
+++ b/plugins/rainlab/blog/models/PostImport.php
@@ -0,0 +1,155 @@
+ 'required',
+ 'content' => 'required'
+ ];
+
+ protected $authorEmailCache = [];
+
+ protected $categoryNameCache = [];
+
+ public function getDefaultAuthorOptions()
+ {
+ return AuthorModel::all()->lists('full_name', 'email');
+ }
+
+ public function getCategoriesOptions()
+ {
+ return Category::lists('name', 'id');
+ }
+
+ public function importData($results, $sessionKey = null)
+ {
+ $firstRow = reset($results);
+
+ // Validation
+ if ($this->auto_create_categories && !array_key_exists('categories', $firstRow)) {
+ throw new ApplicationException('Please specify a match for the Categories column.');
+ }
+
+ // Import
+ foreach ($results as $row => $data) {
+ try {
+
+ if (!$title = array_get($data, 'title')) {
+ $this->logSkipped($row, 'Missing post title');
+ continue;
+ }
+
+ // Find or create
+ $post = Post::make();
+
+ if ($this->update_existing) {
+ $post = $this->findDuplicatePost($data) ?: $post;
+ }
+
+ $postExists = $post->exists;
+
+ // Set attributes
+ $except = ['id', 'categories', 'author_email'];
+
+ foreach (array_except($data, $except) as $attribute => $value) {
+ if (in_array($attribute, $post->getDates()) && empty($value)) {
+ continue;
+ }
+ $post->{$attribute} = isset($value) ? $value : null;
+ }
+
+ if ($author = $this->findAuthorFromEmail($data)) {
+ $post->user_id = $author->id;
+ }
+
+ $post->forceSave();
+
+ if ($categoryIds = $this->getCategoryIdsForPost($data)) {
+ $post->categories()->sync($categoryIds, false);
+ }
+
+ // Log results
+ if ($postExists) {
+ $this->logUpdated();
+ }
+ else {
+ $this->logCreated();
+ }
+ }
+ catch (Exception $ex) {
+ $this->logError($row, $ex->getMessage());
+ }
+ }
+ }
+
+ protected function findAuthorFromEmail($data)
+ {
+ if (!$email = array_get($data, 'email', $this->default_author)) {
+ return null;
+ }
+
+ if (isset($this->authorEmailCache[$email])) {
+ return $this->authorEmailCache[$email];
+ }
+
+ $author = AuthorModel::where('email', $email)->first();
+ return $this->authorEmailCache[$email] = $author;
+ }
+
+ protected function findDuplicatePost($data)
+ {
+ if ($id = array_get($data, 'id')) {
+ return Post::find($id);
+ }
+
+ $title = array_get($data, 'title');
+ $post = Post::where('title', $title);
+
+ if ($slug = array_get($data, 'slug')) {
+ $post->orWhere('slug', $slug);
+ }
+
+ return $post->first();
+ }
+
+ protected function getCategoryIdsForPost($data)
+ {
+ $ids = [];
+
+ if ($this->auto_create_categories) {
+ $categoryNames = $this->decodeArrayValue(array_get($data, 'categories'));
+
+ foreach ($categoryNames as $name) {
+ if (!$name = trim($name)) {
+ continue;
+ }
+
+ if (isset($this->categoryNameCache[$name])) {
+ $ids[] = $this->categoryNameCache[$name];
+ }
+ else {
+ $newCategory = Category::firstOrCreate(['name' => $name]);
+ $ids[] = $this->categoryNameCache[$name] = $newCategory->id;
+ }
+ }
+ }
+ elseif ($this->categories) {
+ $ids = (array) $this->categories;
+ }
+
+ return $ids;
+ }
+}
diff --git a/plugins/rainlab/blog/models/Settings.php b/plugins/rainlab/blog/models/Settings.php
new file mode 100644
index 00000000..2c27a537
--- /dev/null
+++ b/plugins/rainlab/blog/models/Settings.php
@@ -0,0 +1,18 @@
+ ['boolean'],
+ ];
+}
diff --git a/plugins/rainlab/blog/models/category/columns.yaml b/plugins/rainlab/blog/models/category/columns.yaml
new file mode 100644
index 00000000..578d846e
--- /dev/null
+++ b/plugins/rainlab/blog/models/category/columns.yaml
@@ -0,0 +1,13 @@
+# ===================================
+# Column Definitions
+# ===================================
+
+columns:
+
+ name:
+ label: rainlab.blog::lang.category.name
+ searchable: true
+
+ post_count:
+ label: rainlab.blog::lang.category.posts
+ sortable: false
diff --git a/plugins/rainlab/blog/models/category/fields.yaml b/plugins/rainlab/blog/models/category/fields.yaml
new file mode 100644
index 00000000..adcf1bc3
--- /dev/null
+++ b/plugins/rainlab/blog/models/category/fields.yaml
@@ -0,0 +1,23 @@
+# ===================================
+# Field Definitions
+# ===================================
+
+fields:
+
+ name:
+ label: rainlab.blog::lang.category.name
+ placeholder: rainlab.blog::lang.category.name_placeholder
+ span: left
+
+ slug:
+ label: rainlab.blog::lang.category.slug
+ span: right
+ placeholder: rainlab.blog::lang.category.slug_placeholder
+ preset: name
+
+ description:
+ label: 'rainlab.blog::lang.category.description'
+ size: large
+ oc.commentPosition: ''
+ span: full
+ type: textarea
diff --git a/plugins/rainlab/blog/models/post/columns.yaml b/plugins/rainlab/blog/models/post/columns.yaml
new file mode 100644
index 00000000..6567fcc6
--- /dev/null
+++ b/plugins/rainlab/blog/models/post/columns.yaml
@@ -0,0 +1,36 @@
+# ===================================
+# Column Definitions
+# ===================================
+
+columns:
+
+ title:
+ label: rainlab.blog::lang.post.title
+ searchable: true
+
+ # author:
+ # label: Author
+ # relation: user
+ # select: login
+ # searchable: true
+
+ categories:
+ label: rainlab.blog::lang.post.categories
+ relation: categories
+ select: name
+ searchable: true
+ sortable: false
+
+ created_at:
+ label: rainlab.blog::lang.post.created
+ type: date
+ invisible: true
+
+ updated_at:
+ label: rainlab.blog::lang.post.updated
+ type: date
+ invisible: true
+
+ published_at:
+ label: rainlab.blog::lang.post.published
+ type: date
diff --git a/plugins/rainlab/blog/models/post/fields.yaml b/plugins/rainlab/blog/models/post/fields.yaml
new file mode 100644
index 00000000..ed7f5902
--- /dev/null
+++ b/plugins/rainlab/blog/models/post/fields.yaml
@@ -0,0 +1,77 @@
+# ===================================
+# Field Definitions
+# ===================================
+
+fields:
+ title:
+ label: rainlab.blog::lang.post.title
+ span: left
+ placeholder: rainlab.blog::lang.post.title_placeholder
+
+ slug:
+ label: rainlab.blog::lang.post.slug
+ span: right
+ placeholder: rainlab.blog::lang.post.slug_placeholder
+ preset:
+ field: title
+ type: slug
+
+ toolbar:
+ type: partial
+ path: post_toolbar
+ cssClass: collapse-visible
+
+secondaryTabs:
+ stretch: true
+ fields:
+ content:
+ tab: rainlab.blog::lang.post.tab_edit
+ type: RainLab\Blog\FormWidgets\BlogMarkdown
+ cssClass: field-slim blog-post-preview
+ stretch: true
+ mode: split
+
+ categories:
+ tab: rainlab.blog::lang.post.tab_categories
+ type: relation
+ commentAbove: rainlab.blog::lang.post.categories_comment
+ placeholder: rainlab.blog::lang.post.categories_placeholder
+
+ published:
+ tab: rainlab.blog::lang.post.tab_manage
+ label: rainlab.blog::lang.post.published
+ span: left
+ type: checkbox
+
+ user:
+ tab: rainlab.blog::lang.post.tab_manage
+ label: rainlab.blog::lang.post.published_by
+ span: right
+ type: dropdown
+ emptyOption: rainlab.blog::lang.post.current_user
+
+ published_at:
+ tab: rainlab.blog::lang.post.tab_manage
+ label: rainlab.blog::lang.post.published_on
+ span: left
+ cssClass: checkbox-align
+ type: datepicker
+ mode: datetime
+ trigger:
+ action: enable
+ field: published
+ condition: checked
+
+ excerpt:
+ tab: rainlab.blog::lang.post.tab_manage
+ label: rainlab.blog::lang.post.excerpt
+ type: textarea
+ size: small
+
+ featured_images:
+ tab: rainlab.blog::lang.post.tab_manage
+ label: rainlab.blog::lang.post.featured_images
+ type: fileupload
+ mode: image
+ imageWidth: 200
+ imageHeight: 200
diff --git a/plugins/rainlab/blog/models/post/scopes.yaml b/plugins/rainlab/blog/models/post/scopes.yaml
new file mode 100644
index 00000000..6ddfc4c3
--- /dev/null
+++ b/plugins/rainlab/blog/models/post/scopes.yaml
@@ -0,0 +1,43 @@
+# ===================================
+# Filter Scope Definitions
+# ===================================
+
+scopes:
+
+ category:
+
+ # Filter name
+ label: rainlab.blog::lang.posts.filter_category
+
+ # Model Class name
+ modelClass: RainLab\Blog\Models\Category
+
+ # Model attribute to display for the name
+ nameFrom: name
+
+ # Apply query scope
+ scope: FilterCategories
+
+ published:
+
+ # Filter name
+ label: rainlab.blog::lang.posts.filter_published
+
+ # Filter type
+ type: switch
+
+ # SQL Conditions
+ conditions:
+ - published <> '1'
+ - published = '1'
+
+ created_at:
+
+ # Filter name
+ label: rainlab.blog::lang.posts.filter_date
+
+ # Filter type
+ type: daterange
+
+ # SQL Conditions
+ conditions: created_at >= ':after' AND created_at <= ':before'
diff --git a/plugins/rainlab/blog/models/postexport/columns.yaml b/plugins/rainlab/blog/models/postexport/columns.yaml
new file mode 100644
index 00000000..b500a8fa
--- /dev/null
+++ b/plugins/rainlab/blog/models/postexport/columns.yaml
@@ -0,0 +1,19 @@
+# ===================================
+# Column Definitions
+# ===================================
+
+columns:
+
+ id: ID
+ title: rainlab.blog::lang.post.title
+ content: rainlab.blog::lang.post.content
+ content_html: rainlab.blog::lang.post.content_html
+ excerpt: rainlab.blog::lang.post.excerpt
+ slug: rainlab.blog::lang.post.slug
+ categories: rainlab.blog::lang.post.categories
+ author_email: rainlab.blog::lang.post.author_email
+ featured_image_urls: rainlab.blog::lang.post.featured_images
+ created_at: rainlab.blog::lang.post.created_date
+ updated_at: rainlab.blog::lang.post.updated_date
+ published: rainlab.blog::lang.post.published
+ published_at: rainlab.blog::lang.post.published_date
diff --git a/plugins/rainlab/blog/models/postimport/columns.yaml b/plugins/rainlab/blog/models/postimport/columns.yaml
new file mode 100644
index 00000000..a2584ea5
--- /dev/null
+++ b/plugins/rainlab/blog/models/postimport/columns.yaml
@@ -0,0 +1,17 @@
+# ===================================
+# Column Definitions
+# ===================================
+
+columns:
+
+ id: ID
+ title: rainlab.blog::lang.post.title
+ content: rainlab.blog::lang.post.content
+ excerpt: rainlab.blog::lang.post.excerpt
+ slug: rainlab.blog::lang.post.slug
+ categories: rainlab.blog::lang.post.categories
+ author_email: rainlab.blog::lang.post.author_email
+ created_at: rainlab.blog::lang.post.created_date
+ updated_at: rainlab.blog::lang.post.updated_date
+ published: rainlab.blog::lang.post.published
+ published_at: rainlab.blog::lang.post.published_date
diff --git a/plugins/rainlab/blog/models/postimport/fields.yaml b/plugins/rainlab/blog/models/postimport/fields.yaml
new file mode 100644
index 00000000..3e7600ad
--- /dev/null
+++ b/plugins/rainlab/blog/models/postimport/fields.yaml
@@ -0,0 +1,37 @@
+# ===================================
+# Form Field Definitions
+# ===================================
+
+fields:
+
+ update_existing:
+ label: rainlab.blog::lang.import.update_existing_label
+ comment: rainlab.blog::lang.import.update_existing_comment
+ type: checkbox
+ default: true
+ span: left
+
+ auto_create_categories:
+ label: rainlab.blog::lang.import.auto_create_categories_label
+ comment: rainlab.blog::lang.import.auto_create_categories_comment
+ type: checkbox
+ default: true
+ span: right
+
+ categories:
+ label: rainlab.blog::lang.import.categories_label
+ commentAbove: rainlab.blog::lang.import.categories_comment
+ type: checkboxlist
+ span: right
+ cssClass: field-indent
+ trigger:
+ action: hide
+ field: auto_create_categories
+ condition: checked
+
+ default_author:
+ label: rainlab.blog::lang.import.default_author_label
+ comment: rainlab.blog::lang.import.default_author_comment
+ type: dropdown
+ placeholder: rainlab.blog::lang.import.default_author_placeholder
+ span: left
diff --git a/plugins/rainlab/blog/models/settings/fields.yaml b/plugins/rainlab/blog/models/settings/fields.yaml
new file mode 100644
index 00000000..3138a595
--- /dev/null
+++ b/plugins/rainlab/blog/models/settings/fields.yaml
@@ -0,0 +1,17 @@
+tabs:
+ fields:
+ show_all_posts:
+ span: auto
+ label: rainlab.blog::lang.blog.show_all_posts_label
+ comment: rainlab.blog::lang.blog.show_all_posts_comment
+ type: switch
+ default: 1
+ tab: rainlab.blog::lang.blog.tab_general
+
+ use_legacy_editor:
+ span: auto
+ label: rainlab.blog::lang.blog.use_legacy_editor_label
+ comment: rainlab.blog::lang.blog.use_legacy_editor_comment
+ type: switch
+ default: 0
+ tab: rainlab.blog::lang.blog.tab_general
diff --git a/plugins/rainlab/blog/updates/categories_add_nested_fields.php b/plugins/rainlab/blog/updates/categories_add_nested_fields.php
new file mode 100644
index 00000000..c5a2bd21
--- /dev/null
+++ b/plugins/rainlab/blog/updates/categories_add_nested_fields.php
@@ -0,0 +1,34 @@
+integer('parent_id')->unsigned()->index()->nullable();
+ $table->integer('nest_left')->nullable();
+ $table->integer('nest_right')->nullable();
+ $table->integer('nest_depth')->nullable();
+ });
+
+ foreach (CategoryModel::all() as $category) {
+ $category->setDefaultLeftAndRight();
+ $category->save();
+ }
+ }
+
+ public function down()
+ {
+ }
+
+}
\ No newline at end of file
diff --git a/plugins/rainlab/blog/updates/create_categories_table.php b/plugins/rainlab/blog/updates/create_categories_table.php
new file mode 100644
index 00000000..988a9d44
--- /dev/null
+++ b/plugins/rainlab/blog/updates/create_categories_table.php
@@ -0,0 +1,41 @@
+engine = 'InnoDB';
+ $table->increments('id');
+ $table->string('name')->nullable();
+ $table->string('slug')->nullable()->index();
+ $table->string('code')->nullable();
+ $table->text('description')->nullable();
+ $table->integer('parent_id')->unsigned()->index()->nullable();
+ $table->integer('nest_left')->nullable();
+ $table->integer('nest_right')->nullable();
+ $table->integer('nest_depth')->nullable();
+ $table->timestamps();
+ });
+
+ Schema::create('rainlab_blog_posts_categories', function($table)
+ {
+ $table->engine = 'InnoDB';
+ $table->integer('post_id')->unsigned();
+ $table->integer('category_id')->unsigned();
+ $table->primary(['post_id', 'category_id']);
+ });
+ }
+
+ public function down()
+ {
+ Schema::dropIfExists('rainlab_blog_categories');
+ Schema::dropIfExists('rainlab_blog_posts_categories');
+ }
+
+}
diff --git a/plugins/rainlab/blog/updates/create_posts_table.php b/plugins/rainlab/blog/updates/create_posts_table.php
new file mode 100644
index 00000000..8ec081b3
--- /dev/null
+++ b/plugins/rainlab/blog/updates/create_posts_table.php
@@ -0,0 +1,32 @@
+engine = 'InnoDB';
+ $table->increments('id');
+ $table->integer('user_id')->unsigned()->nullable()->index();
+ $table->string('title')->nullable();
+ $table->string('slug')->index();
+ $table->text('excerpt')->nullable();
+ $table->longText('content')->nullable();
+ $table->longText('content_html')->nullable();
+ $table->timestamp('published_at')->nullable();
+ $table->boolean('published')->default(false);
+ $table->timestamps();
+ });
+ }
+
+ public function down()
+ {
+ Schema::dropIfExists('rainlab_blog_posts');
+ }
+
+}
diff --git a/plugins/rainlab/blog/updates/posts_add_metadata.php b/plugins/rainlab/blog/updates/posts_add_metadata.php
new file mode 100644
index 00000000..ccd96bb3
--- /dev/null
+++ b/plugins/rainlab/blog/updates/posts_add_metadata.php
@@ -0,0 +1,31 @@
+mediumText('metadata')->nullable();
+ });
+ }
+
+ public function down()
+ {
+ if (Schema::hasColumn('rainlab_blog_posts', 'metadata')) {
+ Schema::table('rainlab_blog_posts', function ($table) {
+ $table->dropColumn('metadata');
+ });
+ }
+ }
+
+}
diff --git a/plugins/rainlab/blog/updates/seed_all_tables.php b/plugins/rainlab/blog/updates/seed_all_tables.php
new file mode 100644
index 00000000..7be9344f
--- /dev/null
+++ b/plugins/rainlab/blog/updates/seed_all_tables.php
@@ -0,0 +1,34 @@
+ 'First blog post',
+ 'slug' => 'first-blog-post',
+ 'content' => '
+This is your first ever **blog post**! It might be a good idea to update this post with some more relevant content.
+
+You can edit this content by selecting **Blog** from the administration back-end menu.
+
+*Enjoy the good times!*
+ ',
+ 'excerpt' => 'The first ever blog post is here. It might be a good idea to update this post with some more relevant content.',
+ 'published_at' => Carbon::now(),
+ 'published' => true
+ ]);
+
+ Category::create([
+ 'name' => trans('rainlab.blog::lang.categories.uncategorized'),
+ 'slug' => 'uncategorized',
+ ]);
+ }
+
+}
diff --git a/plugins/rainlab/blog/updates/update_timestamp_nullable.php b/plugins/rainlab/blog/updates/update_timestamp_nullable.php
new file mode 100644
index 00000000..7de7961c
--- /dev/null
+++ b/plugins/rainlab/blog/updates/update_timestamp_nullable.php
@@ -0,0 +1,20 @@
+ 'Notify',
+ 'description' => 'Notification Services',
+ 'author' => 'Alexey Bobkov, Samuel Georges',
+ 'icon' => 'icon-bullhorn'
+ ];
+ }
+
+ /**
+ * registerSettings
+ */
+ public function registerSettings()
+ {
+ return [
+ 'notifications' => [
+ 'label' => 'Notification Rules',
+ 'description' => 'Manage the events and actions that trigger notifications.',
+ 'category' => SettingsManager::CATEGORY_NOTIFICATIONS,
+ 'icon' => 'icon-bullhorn',
+ 'url' => Backend::url('rainlab/notify/notifications'),
+ 'permissions' => ['rainlab.notify.manage_notifications'],
+ 'order' => 600,
+ 'keywords' => 'notify'
+ ],
+ ];
+ }
+
+ /**
+ * registerNotificationRules
+ */
+ public function registerNotificationRules()
+ {
+ return [
+ 'groups' => [],
+ 'events' => [],
+ 'actions' => [
+ \RainLab\Notify\NotifyRules\SaveDatabaseAction::class,
+ \RainLab\Notify\NotifyRules\SendMailTemplateAction::class,
+ ],
+ 'conditions' => [
+ \RainLab\Notify\NotifyRules\ExecutionContextCondition::class,
+ ],
+ ];
+ }
+
+ /**
+ * registerPermissions
+ */
+ public function registerPermissions()
+ {
+ return [
+ 'rainlab.notify.manage_notifications' => [
+ 'tab' => SettingsManager::CATEGORY_NOTIFICATIONS,
+ 'label' => 'Notifications management'
+ ],
+ ];
+ }
+}
diff --git a/plugins/rainlab/notify/README.md b/plugins/rainlab/notify/README.md
new file mode 100644
index 00000000..d1edf67c
--- /dev/null
+++ b/plugins/rainlab/notify/README.md
@@ -0,0 +1,400 @@
+# Notification engine
+
+**Plugin is currently in Beta status. Proceed with caution.**
+
+Adds support for sending notifications across a variety of different channels, including mail, SMS and Slack.
+
+Notifications are managed in the back-end area by navigating to *Settings > Notification rules*.
+
+## Notification workflow
+
+When a notification fires, it uses the following workflow:
+
+1. Plugin registers associated actions, conditions and events using `registerNotificationRules`
+1. A notification class is bound to a system event using `Notifier::bindEvent`
+1. A system event is fired `Event::fire`
+1. The parameters of the event are captured, along with any global context parameters
+1. A command is pushed on the queue to process the notification `Queue::push`
+1. The command finds all notification rules using the notification class and triggers them
+1. The notification conditions are checked and only proceed if met
+1. The notification actions are triggered
+
+Here is an example of a plugin registering notification rules. The `groups` definition will create containers that are used to better organise events. The `presets` definition specifies notification rules defined by the system.
+
+```php
+public function registerNotificationRules()
+{
+ return [
+ 'events' => [
+ \RainLab\User\NotifyRules\UserActivatedEvent::class,
+ ],
+ 'actions' => [
+ \RainLab\User\NotifyRules\SaveToDatabaseAction::class,
+ ],
+ 'conditions' => [
+ \RainLab\User\NotifyRules\UserAttributeCondition::class
+ ],
+ 'groups' => [
+ 'user' => [
+ 'label' => 'User',
+ 'icon' => 'icon-user'
+ ],
+ ],
+ 'presets' => '$/rainlab/user/config/notify_presets.yaml',
+ ];
+}
+```
+
+Here is an example of triggering a notification. The system event `rainlab.user.activate` is bound to the `UserActivatedEvent` class.
+
+```php
+// Bind to a system event
+\RainLab\Notify\Classes\Notifier::bindEvents([
+ 'rainlab.user.activate' => \RainLab\User\NotifyRules\UserActivatedEvent::class
+]);
+
+// Fire the system event
+Event::fire('rainlab.user.activate', [$this]);
+```
+
+Here is an example of registering context parameters, which are available globally to all notifications.
+
+```php
+\RainLab\Notify\Classes\Notifier::instance()->registerCallback(function($manager) {
+ $manager->registerGlobalParams([
+ 'user' => Auth::getUser()
+ ]);
+});
+```
+
+Here is an example of an event preset:
+
+```yaml
+# ===================================
+# Event Presets
+# ===================================
+
+welcome_email:
+ name: Send welcome email to user
+ event: RainLab\User\NotifyRules\UserRegisteredEvent
+ items:
+ - action: RainLab\Notify\NotifyRules\SendMailTemplateAction
+ mail_template: rainlab.user::mail.welcome
+ send_to_mode: user
+ conditions:
+ - condition: RainLab\Notify\NotifyRules\ExecutionContextCondition
+ subcondition: environment
+ operator: is
+ value: dev
+ condition_text: Application environment is dev
+```
+
+## Creating Event classes
+
+An event class is responsible for preparing the parameters passed to the conditions and actions. The static method `makeParamsFromEvent` will take the arguments provided by the system event and convert them in to parameters.
+
+```php
+class UserActivatedEvent extends \RainLab\Notify\Classes\EventBase
+{
+ /**
+ * @var array Local conditions supported by this event.
+ */
+ public $conditions = [
+ \RainLab\User\NotifyRules\UserAttributeCondition::class
+ ];
+
+ /**
+ * Returns information about this event, including name and description.
+ */
+ public function eventDetails()
+ {
+ return [
+ 'name' => 'Activated',
+ 'description' => 'A user is activated',
+ 'group' => 'user'
+ ];
+ }
+
+ /**
+ * Defines the usable parameters provided by this class.
+ */
+ public function defineParams()
+ {
+ return [
+ 'name' => [
+ 'title' => 'Name',
+ 'label' => 'Name of the user',
+ ],
+ // ...
+ ];
+ }
+
+ public static function makeParamsFromEvent(array $args, $eventName = null)
+ {
+ return [
+ 'user' => array_get($args, 0)
+ ];
+ }
+}
+```
+
+## Creating Action classes
+
+Action classes define the final step in a notification and subsequently perform the notification itself. Some examples might be sending and email or writing to the database.
+
+```php
+class SendMailTemplateAction extends \RainLab\Notify\Classes\ActionBase
+{
+ /**
+ * Returns information about this event, including name and description.
+ */
+ public function actionDetails()
+ {
+ return [
+ 'name' => 'Compose a mail message',
+ 'description' => 'Send a message to a recipient',
+ 'icon' => 'icon-envelope'
+ ];
+ }
+
+ /**
+ * Field configuration for the action.
+ */
+ public function defineFormFields()
+ {
+ return 'fields.yaml';
+ }
+
+ public function getText()
+ {
+ $template = $this->host->template_name;
+
+ return 'Send a message using '.$template;
+ }
+
+ /**
+ * Triggers this action.
+ * @param array $params
+ * @return void
+ */
+ public function triggerAction($params)
+ {
+ $email = 'test@email.tld';
+ $template = $this->host->template_name;
+
+ Mail::sendTo($email, $template, $params);
+ }
+}
+```
+
+A form fields definition file is used to provide form fields when the action is established. These values are accessed from condition using the host model via the `$this->host` property.
+
+```yaml
+# ===================================
+# Field Definitions
+# ===================================
+
+fields:
+
+ template_name:
+ label: Template name
+ type: text
+```
+
+An action may choose to provide no form fields by simply returning false from the `defineFormFields` method.
+
+```php
+public function defineFormFields()
+{
+ return false;
+}
+```
+
+## Creating Condition classes
+
+A condition class should specify how it should appear in the user interface, providing a name, title and summary text. It also must declare an `isTrue` method for evaluating whether the condition is true or not.
+
+```php
+class MyCondition extends \RainLab\Notify\Classes\ConditionBase
+{
+ /**
+ * Return either ConditionBase::TYPE_ANY or ConditionBase::TYPE_LOCAL
+ */
+ public function getConditionType()
+ {
+ // If the condition should appear for all events
+ return ConditionBase::TYPE_ANY;
+
+ // If the condition should appear only for some events
+ return ConditionBase::TYPE_LOCAL;
+ }
+
+ /**
+ * Field configuration for the condition.
+ */
+ public function defineFormFields()
+ {
+ return 'fields.yaml';
+ }
+
+ public function getName()
+ {
+ return 'My condition is checked';
+ }
+
+ public function getTitle()
+ {
+ return 'My condition';
+ }
+
+ public function getText()
+ {
+ $value = $this->host->mycondition;
+
+ return 'My condition is '.$value;
+ }
+
+ /**
+ * Checks whether the condition is TRUE for specified parameters
+ * @param array $params
+ * @return bool
+ */
+ public function isTrue(&$params)
+ {
+ return true;
+ }
+}
+```
+
+A form fields definition file is used to provide form fields when the condition is established. These values are accessed from condition using the host model via the `$this->host` property.
+
+```yaml
+# ===================================
+# Field Definitions
+# ===================================
+
+fields:
+
+ mycondition:
+ label: My condition
+ type: dropdown
+ options:
+ true: True
+ false: False
+```
+
+## Model attribute condition classes
+
+Model attribute conditions are designed specially for applying conditions to sets of model attributes.
+
+```php
+class UserAttributeCondition extends \RainLab\Notify\Classes\ModelAttributesConditionBase
+{
+ protected $modelClass = \RainLab\User\Models\User::class;
+
+ public function getGroupingTitle()
+ {
+ return 'User attribute';
+ }
+
+ public function getTitle()
+ {
+ return 'User attribute';
+ }
+
+ /**
+ * Checks whether the condition is TRUE for specified parameters
+ * @param array $params Specifies a list of parameters as an associative array.
+ * @return bool
+ */
+ public function isTrue(&$params)
+ {
+ $hostObj = $this->host;
+
+ $attribute = $hostObj->subcondition;
+
+ if (!$user = array_get($params, 'user')) {
+ throw new ApplicationException('Error evaluating the user attribute condition: the user object is not found in the condition parameters.');
+ }
+
+ return parent::evalIsTrue($user);
+ }
+}
+```
+
+An attributes definition file is used to specify which attributes should be included in the condition.
+
+```yaml
+# ===================================
+# Condition Attribute Definitions
+# ===================================
+
+attributes:
+
+ name:
+ label: Name
+
+ email:
+ label: Email address
+
+ country:
+ label: Country
+ type: relation
+ relation:
+ model: RainLab\Location\Models\Country
+ label: Name
+ nameFrom: name
+ keyFrom: id
+```
+
+## Save to database action
+
+There is a dedicated table in the database for storing events and their parameters. This table is accessed using the `RainLab\Notify\Models\Notification` model and can be referenced as a relation from your own models. In this example the `MyProject` model contains its own notification channel called `notifications`.
+
+```php
+class MyProject extends Model
+{
+ // ...
+
+ public $morphMany = [
+ 'my_notifications' => [
+ \RainLab\Notify\Models\Notification::class,
+ 'name' => 'notifiable'
+ ]
+ ];
+}
+```
+
+This channel should be registered with the `RainLab\Notify\NotifyRules\SaveDatabaseAction` so it appears as a related object when selecting the action.
+
+```php
+SaveDatabaseAction::extend(function ($action) {
+ $action->addTableDefinition([
+ 'label' => 'Project activity',
+ 'class' => MyProject::class,
+ 'relation' => 'my_notifications',
+ 'param' => 'project'
+ ]);
+});
+```
+
+The **label** is shown as the related object, the **class** references the model class, the **relation** refers to the relation name. The **param** defines the parameter name, passed to the triggering event.
+
+So essentially if you pass a `project` to the event parameters, or if `project` is a global parameter, a notification model is created with the parameters stored in the `data` attribute. Equivalent to the following code:
+
+```php
+$myproject->my_notifications()->create([
+ // ...
+ 'data' => $params
+]);
+```
+
+## Dynamically adding conditions to events
+
+Events can be extended to include new local conditions. Simply add the condition class to the event `$conditions` array property.
+
+```php
+UserActivatedEvent::extend(function($event) {
+ $event->conditions[] = \RainLab\UserPlus\NotifyRules\UserLocationAttributeCondition::class;
+});
+```
diff --git a/plugins/rainlab/notify/classes/ActionBase.php b/plugins/rainlab/notify/classes/ActionBase.php
new file mode 100644
index 00000000..c64b09ee
--- /dev/null
+++ b/plugins/rainlab/notify/classes/ActionBase.php
@@ -0,0 +1,168 @@
+ 'Action',
+ 'description' => 'Action Description',
+ 'icon' => 'icon-dot-circle-o'
+ ];
+ }
+
+ public function __construct($host = null)
+ {
+ /*
+ * Paths
+ */
+ $this->viewPath = $this->configPath = $this->guessConfigPathFrom($this);
+
+ /*
+ * Parse the config, if available
+ */
+ if ($formFields = $this->defineFormFields()) {
+ $this->fieldConfig = $this->makeConfig($formFields);
+ }
+
+ if (!$this->host = $host) {
+ return;
+ }
+
+ $this->boot($host);
+ }
+
+ /**
+ * Boot method called when the condition class is first loaded
+ * with an existing model.
+ * @return array
+ */
+ public function boot($host)
+ {
+ // Set default data
+ if (!$host->exists) {
+ $this->initConfigData($host);
+ }
+
+ // Apply validation rules
+ $host->rules = array_merge($host->rules, $this->defineValidationRules());
+ }
+
+ public function triggerAction($params)
+ {
+ }
+
+ public function getTitle()
+ {
+ return $this->getActionName();
+ }
+
+ public function getText()
+ {
+ return $this->getActionDescription();
+ }
+
+ public function getActionName()
+ {
+ return array_get($this->actionDetails(), 'name');
+ }
+
+ public function getActionDescription()
+ {
+ return array_get($this->actionDetails(), 'description');
+ }
+
+ public function getActionIcon()
+ {
+ return array_get($this->actionDetails(), 'icon', 'icon-dot-circle-o');
+ }
+
+ /**
+ * Extra field configuration for the condition.
+ */
+ public function defineFormFields()
+ {
+ return 'fields.yaml';
+ }
+
+ /**
+ * Determines if this action uses form fields.
+ * @return bool
+ */
+ public function hasFieldConfig()
+ {
+ return !!$this->fieldConfig;
+ }
+
+ /**
+ * Returns the field configuration used by this model.
+ */
+ public function getFieldConfig()
+ {
+ return $this->fieldConfig;
+ }
+
+ /**
+ * Initializes configuration data when the condition is first created.
+ * @param Model $host
+ */
+ public function initConfigData($host) {}
+
+ /**
+ * Defines validation rules for the custom fields.
+ * @return array
+ */
+ public function defineValidationRules()
+ {
+ return [];
+ }
+
+ /**
+ * Spins over types registered in plugin base class with `registerNotificationRules`.
+ * @return array
+ */
+ public static function findActions()
+ {
+ $results = [];
+ $bundles = PluginManager::instance()->getRegistrationMethodValues('registerNotificationRules');
+
+ foreach ($bundles as $plugin => $bundle) {
+ foreach ((array) array_get($bundle, 'actions', []) as $conditionClass) {
+ if (!class_exists($conditionClass)) {
+ continue;
+ }
+
+ $obj = new $conditionClass;
+ $results[$conditionClass] = $obj;
+ }
+ }
+
+ return $results;
+ }
+}
diff --git a/plugins/rainlab/notify/classes/CompoundCondition.php b/plugins/rainlab/notify/classes/CompoundCondition.php
new file mode 100644
index 00000000..56c7a8be
--- /dev/null
+++ b/plugins/rainlab/notify/classes/CompoundCondition.php
@@ -0,0 +1,178 @@
+host->condition_type == 0
+ ? __('ALL of subconditions should be') . ' '
+ : __('ANY of subconditions should be') . ' ';
+
+ $result .= $this->host->condition == 'false' ? 'FALSE' : 'TRUE';
+
+ return $result;
+ }
+
+ /**
+ * getJoinText returns the text to use when joining two rules within.
+ * @return string
+ */
+ public function getJoinText()
+ {
+ return $this->host->condition_type == 0 ? 'AND' : 'OR';
+ }
+
+ /**
+ * Returns a list of condition types (`ConditionBase::TYPE_*` constants)
+ * that can be added to this compound condition
+ */
+ public function getAllowedSubtypes()
+ {
+ return [];
+ }
+
+ /**
+ * defineFormFields
+ */
+ public function defineFormFields()
+ {
+ return 'fields.yaml';
+ }
+
+ public function initConfigData($host)
+ {
+ $host->condition_type = 0;
+ $host->condition = 'true';
+ }
+
+ public function getConditionOptions()
+ {
+ $options = [
+ 'true' => 'TRUE',
+ 'false' => 'FALSE'
+ ];
+
+ return $options;
+ }
+
+ public function getConditionTypeOptions()
+ {
+ $options = [
+ '0' => 'ALL subconditions should meet the requirement',
+ '1' => 'ANY subconditions should meet the requirement'
+ ];
+
+ return $options;
+ }
+
+ /**
+ * getChildOptions
+ */
+ public function getChildOptions(array $options)
+ {
+ extract(array_merge([
+ 'extraRules' => [],
+ ], $options));
+
+ $result = [
+ 'Compound condition' => CompoundCondition::class
+ ];
+
+ $classes = $extraRules + self::findConditionsByType(ConditionBase::TYPE_ANY);
+
+ $result = $this->addClassesSubconditions($classes, $result);
+
+ return $result;
+ }
+
+ protected function addClassesSubconditions($classes, $list)
+ {
+ foreach ($classes as $conditionClass => $obj) {
+
+ $subConditions = $obj->listSubconditions();
+
+ if ($subConditions) {
+ $groupName = $obj->getGroupingTitle();
+
+ foreach ($subConditions as $name => $subcondition) {
+ if (!$groupName) {
+ $list[$name] = $conditionClass.':'.$subcondition;
+ }
+ else {
+ if (!array_key_exists($groupName, $list)) {
+ $list[$groupName] = [];
+ }
+
+ $list[$groupName][$name] = $conditionClass.':'.$subcondition;
+ }
+ }
+ }
+ else {
+ $list[$obj->getName()] = $conditionClass;
+ }
+ }
+
+ return $list;
+ }
+
+ /**
+ * Checks whether the condition is TRUE for specified parameters.
+ *
+ * @param array $params
+ * @return bool
+ */
+ public function isTrue(&$params)
+ {
+ $hostObj = $this->host;
+
+ $requiredConditionValue = $hostObj->condition == 'true' ? true : false;
+
+ foreach ($hostObj->children as $subcondition) {
+ $subconditionResult = $subcondition->getConditionObject()->isTrue($params) ? true : false;
+
+ /*
+ * All
+ */
+ if ($hostObj->condition_type == 0) {
+ if ($subconditionResult !== $requiredConditionValue) {
+ return false;
+ }
+
+ }
+ /*
+ * Any
+ */
+ else {
+ if ($subconditionResult === $requiredConditionValue) {
+ return true;
+ }
+ }
+ }
+
+ /*
+ * All
+ */
+ if ($hostObj->condition_type == 0) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/plugins/rainlab/notify/classes/ConditionBase.php b/plugins/rainlab/notify/classes/ConditionBase.php
new file mode 100644
index 00000000..81cfaafc
--- /dev/null
+++ b/plugins/rainlab/notify/classes/ConditionBase.php
@@ -0,0 +1,211 @@
+viewPath = $this->configPath = $this->guessConfigPathFrom($this);
+
+ /*
+ * Parse the config
+ */
+ $this->fieldConfig = $this->makeConfig($this->defineFormFields());
+
+ if (!$this->host = $host) {
+ return;
+ }
+
+ $this->boot($host);
+ }
+
+ /**
+ * Boot method called when the condition class is first loaded
+ * with an existing model.
+ * @return array
+ */
+ public function boot($host)
+ {
+ // Set default data
+ if (!$host->exists) {
+ $this->initConfigData($host);
+ }
+
+ // Apply validation rules
+ $host->rules = array_merge($host->rules, $this->defineValidationRules());
+
+ // Inject view paths to the controller through the Form widget
+ $host->bindEvent('model.form.filterFields', function($form) {
+ $form->getController()->addViewPath($this->getViewPaths());
+ });
+ }
+
+ /**
+ * Extra field configuration for the condition.
+ */
+ public function defineFormFields()
+ {
+ return 'fields.yaml';
+ }
+
+ /**
+ * Initializes configuration data when the condition is first created.
+ * @param Model $host
+ */
+ public function initConfigData($host) {}
+
+ /**
+ * Defines validation rules for the custom fields.
+ * @return array
+ */
+ public function defineValidationRules()
+ {
+ return [];
+ }
+
+ public function getText()
+ {
+ return 'Condition Text';
+ }
+
+ /**
+ * Returns a condition name for displaying in the condition selection drop-down menu
+ */
+ public function getName()
+ {
+ return 'Condition';
+ }
+
+ /**
+ * Returns a condition title for displaying in the condition settings form
+ */
+ public function getTitle()
+ {
+ return 'Condition';
+ }
+
+ /**
+ * This function should return one of the `ConditionBase::TYPE_*` constants
+ * depending on a place where the condition is valid
+ */
+ public function getConditionType()
+ {
+ return ConditionBase::TYPE_ANY;
+ }
+
+ public function listSubconditions()
+ {
+ return [];
+ }
+
+ /**
+ * Returns a title to use for grouping subconditions
+ * in the Create Condition drop-down menu
+ */
+ public function getGroupingTitle()
+ {
+ return null;
+ }
+
+ /**
+ * Returns the field configuration used by this model.
+ */
+ public function getFieldConfig()
+ {
+ return $this->fieldConfig;
+ }
+
+ /**
+ * Spins over types registered in plugin base class with `registerNotificationRules`,
+ * checks if the condition type matches and adds it to an array that is returned.
+ *
+ * @param string $type Use `self::TYPE_*` constants
+ * @return array
+ */
+ public static function findConditionsByType($type)
+ {
+ $results = [];
+ $bundles = PluginManager::instance()->getRegistrationMethodValues(static::$registrationMethod);
+
+ foreach ($bundles as $plugin => $bundle) {
+ foreach ((array) array_get($bundle, 'conditions', []) as $conditionClass) {
+ if (!class_exists($conditionClass)) {
+ continue;
+ }
+
+ $obj = new $conditionClass;
+ if ($obj->getConditionType() != $type) {
+ continue;
+ }
+
+ $results[$conditionClass] = $obj;
+ }
+ }
+
+ return $results;
+ }
+
+ public function setFormFields($fields)
+ {
+
+ }
+
+ public function setCustomData()
+ {
+ }
+
+ public function onPreRender($controller, $widget)
+ {
+ }
+
+ /**
+ * Checks whether the condition is TRUE for specified parameters
+ * @param array $params
+ * @return bool
+ */
+ public function isTrue(&$params)
+ {
+ return false;
+ }
+
+ public function getChildOptions(array $options)
+ {
+ return [];
+ }
+}
diff --git a/plugins/rainlab/notify/classes/EventBase.php b/plugins/rainlab/notify/classes/EventBase.php
new file mode 100644
index 00000000..db6241e0
--- /dev/null
+++ b/plugins/rainlab/notify/classes/EventBase.php
@@ -0,0 +1,237 @@
+ 'Event',
+ 'description' => 'Event description',
+ 'group' => 'groupcode'
+ ];
+ }
+
+ public function __construct($host = null)
+ {
+ $this->host = $host;
+ }
+
+ /**
+ * Defines the parameters used by this class.
+ * This method should be used as an override in the extended class.
+ */
+ public function defineParams()
+ {
+ return [];
+ }
+
+ /**
+ * Local conditions supported by this event.
+ */
+ public function defineConditions()
+ {
+ return $this->conditions;
+ }
+
+ /**
+ * Sets multiple params.
+ * @param array $params
+ * @return void
+ */
+ public function setParams($params)
+ {
+ $this->params = $params;
+ }
+
+ /**
+ * Returns all params.
+ * @return array
+ */
+ public function getParams()
+ {
+ return $this->params;
+ }
+
+ /**
+ * Generates event parameters based on arguments from the triggering system event.
+ * @param array $args
+ * @param string $eventName
+ * @return void
+ */
+ public static function makeParamsFromEvent(array $args, $eventName = null)
+ {
+ }
+
+ public function getEventName()
+ {
+ return Lang::get(array_get($this->eventDetails(), 'name'));
+ }
+
+ public function getEventDescription()
+ {
+ return Lang::get(array_get($this->eventDetails(), 'description'));
+ }
+
+ public function getEventGroup()
+ {
+ return array_get($this->eventDetails(), 'group');
+ }
+
+ /**
+ * Resolves an event or action identifier from a class name or object.
+ * @param mixed Class name or object
+ * @return string Identifier in format of vendor-plugin-class
+ */
+ public function getEventIdentifier()
+ {
+ $namespace = Str::normalizeClassName(get_called_class());
+ if (strpos($namespace, '\\') === null) {
+ return $namespace;
+ }
+
+ $parts = explode('\\', $namespace);
+ $class = array_pop($parts);
+ $slice = array_slice($parts, 1, 2);
+ $code = strtolower(implode('-', $slice) . '-' . $class);
+
+ return $code;
+ }
+
+ /**
+ * Spins over types registered in plugin base class with `registerNotificationRules`.
+ * @return array
+ */
+ public static function findEvents()
+ {
+ $results = [];
+ $bundles = PluginManager::instance()->getRegistrationMethodValues('registerNotificationRules');
+
+ foreach ($bundles as $plugin => $bundle) {
+ foreach ((array) array_get($bundle, 'events', []) as $conditionClass) {
+ if (!class_exists($conditionClass)) {
+ continue;
+ }
+
+ $obj = new $conditionClass;
+ $results[$conditionClass] = $obj;
+ }
+ }
+
+ return $results;
+ }
+
+ public static function findEventGroups()
+ {
+ $results = [];
+ $bundles = PluginManager::instance()->getRegistrationMethodValues('registerNotificationRules');
+
+ foreach ($bundles as $plugin => $bundle) {
+ if ($groups = array_get($bundle, 'groups')) {
+ $results += $groups;
+ }
+ }
+
+ return $results;
+ }
+
+ public static function findEventsByGroup($group)
+ {
+ $results = [];
+
+ foreach (self::findEvents() as $conditionClass => $obj) {
+ if ($obj->getEventGroup() != $group) {
+ continue;
+ }
+
+ $results[$conditionClass] = $obj;
+ }
+
+ return $results;
+ }
+
+ public static function findEventByIdentifier($identifier)
+ {
+ foreach (self::findEvents() as $class => $obj) {
+ if ($obj->getEventIdentifier() == $identifier) {
+ return $obj;
+ }
+ }
+ }
+
+ /**
+ * Spins over preset registered in plugin base class with `registerNotificationRules`.
+ * @return array
+ */
+ public static function findEventPresets()
+ {
+ $results = [];
+ $bundles = PluginManager::instance()->getRegistrationMethodValues('registerNotificationRules');
+
+ foreach ($bundles as $plugin => $bundle) {
+ if (!$presets = array_get($bundle, 'presets')) {
+ continue;
+ }
+
+ if (!is_array($presets)) {
+ $presets = Yaml::parse(File::get(File::symbolizePath($presets)));
+ }
+
+ if ($presets && is_array($presets)) {
+ $results += $presets;
+ }
+ }
+
+ return $results;
+ }
+
+ public static function findEventPresetsByClass($className)
+ {
+ $results = [];
+
+ foreach (self::findEventPresets() as $code => $definition) {
+ if (!$eventClass = array_get($definition, 'event')) {
+ continue;
+ }
+
+ if ($eventClass != $className) {
+ continue;
+ }
+
+ $results[$code] = $definition;
+ }
+
+ return $results;
+ }
+}
diff --git a/plugins/rainlab/notify/classes/EventParams.php b/plugins/rainlab/notify/classes/EventParams.php
new file mode 100644
index 00000000..cf175773
--- /dev/null
+++ b/plugins/rainlab/notify/classes/EventParams.php
@@ -0,0 +1,59 @@
+eventClass = $eventClass;
+
+ $this->params = $this->serializeParams($params);
+ }
+
+ /**
+ * Execute the job.
+ *
+ * @return void
+ */
+ public function handle()
+ {
+ $this->delete();
+
+ Notifier::instance()->fireEvent($this->eventClass, $this->unserializeParams());
+ }
+
+ protected function serializeParams($params)
+ {
+ $result = [];
+
+ foreach ($params as $param => $value) {
+ $result[$param] = $this->getSerializedPropertyValue($value);
+ }
+
+ return $result;
+ }
+
+ protected function unserializeParams()
+ {
+ $result = [];
+
+ foreach ($this->params as $param => $value) {
+ $result[$param] = $this->getRestoredPropertyValue($value);
+ }
+
+ return $result;
+ }
+}
diff --git a/plugins/rainlab/notify/classes/ModelAttributesConditionBase.php b/plugins/rainlab/notify/classes/ModelAttributesConditionBase.php
new file mode 100644
index 00000000..b6183a6a
--- /dev/null
+++ b/plugins/rainlab/notify/classes/ModelAttributesConditionBase.php
@@ -0,0 +1,743 @@
+ 'is',
+ 'is_not' => 'is not',
+ 'equals_or_greater' => 'equals or greater than',
+ 'equals_or_less' => 'equals or less than',
+ 'contains' => 'contains',
+ 'does_not_contain' => 'does not contain',
+ 'greater' => 'greater than',
+ 'less' => 'less than',
+ 'one_of' => 'is one of',
+ 'not_one_of' => 'is not one of'
+ ];
+
+ protected $modelObj = null;
+ protected $referenceInfo = null;
+ protected $modelAttributes = null;
+
+ protected static $modelObjCache = [];
+ protected static $attributeControlTypeCache = [];
+
+ public function __construct($host = null)
+ {
+ parent::__construct($host);
+
+ /*
+ * This is used as a base class, so register view path from here too
+ */
+ $this->addViewPath($this->guessViewPathFrom(__CLASS__));
+ }
+
+ public function initConfigData($host)
+ {
+ $host->operator = 'is';
+ }
+
+ public function setCustomData()
+ {
+ $this->host->condition_control_type = $this->evalControlType();
+ }
+
+ //
+ // Definitions
+ //
+
+ /**
+ * This function should return one of the `ConditionBase::TYPE_*` constants
+ * depending on a place where the condition is valid
+ */
+ public function getConditionType()
+ {
+ return ConditionBase::TYPE_LOCAL;
+ }
+
+ public function defineModelAttributes($type = null)
+ {
+ return 'attributes.yaml';
+ }
+
+ public function defineValidationRules()
+ {
+ return [
+ 'value' => 'required'
+ ];
+ }
+
+ public function defineFormFields()
+ {
+ return plugins_path('rainlab/notify/classes/modelattributesconditionbase/fields.yaml');
+ }
+
+ //
+ // Text helpers
+ //
+
+ public function getText()
+ {
+ $host = $this->host;
+ $attributes = $this->listModelAttributes();
+
+ if (isset($attributes[$host->subcondition])) {
+ $result = $this->getConditionTextPrefix($host, $attributes);
+ }
+ else {
+ $result = __('Unknown Attribute');
+ }
+
+ $result .= ' '.array_get($this->operators, $host->operator, $host->operator).' ';
+
+ $controlType = $this->getValueControlType();
+
+ if ($controlType == 'text') {
+ $result .= $host->value;
+ }
+ else {
+ $textValue = $this->getCustomTextValue();
+ if ($textValue !== false) {
+ return $result.' '.$textValue;
+ }
+
+ $referenceInfo = $this->prepareReferenceListInfo();
+ $modelObj = $referenceInfo->referenceModel;
+
+ if (!count($referenceInfo->columns)) {
+ return $result;
+ }
+
+ if (!strlen($host->value)) {
+ return $result .= '?';
+ }
+
+ $visibleField = $referenceInfo->primaryColumn;
+
+ if ($controlType == 'dropdown') {
+ $obj = $modelObj->where('id', $host->value)->first();
+ if ($obj) {
+ $result .= e($obj->{$visibleField});
+ }
+ }
+ else {
+ $ids = explode(',', $host->value);
+ foreach ($ids as &$id) {
+ $id = trim(e($id));
+ }
+
+ $records = $modelObj
+ ->whereIn('id', $ids)
+ ->orderBy($visibleField)
+ ->get();
+
+ $recordNames = [];
+ foreach ($records as $record) {
+ $recordNames[] = $record->{$visibleField};
+ }
+
+ $result .= '('.implode(', ', $recordNames).')';
+ }
+ }
+
+ return $result;
+ }
+
+ protected function getConditionTextPrefix($parametersHost, $attributes)
+ {
+ return $attributes[$parametersHost->subcondition];
+ }
+
+ public function getCustomTextValue()
+ {
+ return false;
+ }
+
+ //
+ // Options
+ //
+
+ /**
+ * getSubconditionOptions
+ */
+ public function getSubconditionOptions()
+ {
+ return $this->listModelAttributes();
+ }
+
+ /**
+ * getOperatorOptions
+ */
+ public function getOperatorOptions()
+ {
+ $hostObj = $this->host;
+ $options = [];
+ $attribute = $hostObj->subcondition;
+
+ $currentOperatorValue = $hostObj->operator;
+
+ $model = $this->getModelObj();
+ $definitions = $this->listModelAttributeInfo();
+
+ if (!isset($definitions[$attribute])) {
+ $options = ['none' => 'Unknown attribute selected'];
+ }
+ else {
+ $columnType = array_get($definitions[$attribute], 'type');
+
+ if ($columnType != ConditionBase::CAST_RELATION) {
+ if ($columnType == ConditionBase::CAST_STRING) {
+ $options = [
+ 'is' => 'is',
+ 'is_not' => 'is not',
+ 'contains' => 'contains',
+ 'does_not_contain' => 'does not contain'
+ ];
+ }
+ else {
+ $options = [
+ 'is' => 'is',
+ 'is_not' => 'is not',
+ 'equals_or_greater' => 'equals or greater than',
+ 'equals_or_less' => 'equals or less than',
+ 'greater' => 'greater than',
+ 'less' => 'less than'
+ ];
+ }
+ }
+ else {
+ $options = [
+ 'is' => 'is',
+ 'is_not' => 'is not',
+ 'one_of' => 'is one of',
+ 'not_one_of' => 'is not one of'
+ ];
+ }
+ }
+
+ if (!array_key_exists($currentOperatorValue, $options)) {
+ $keys = array_keys($options);
+ if (count($keys)) {
+ $hostObj->operator = $options[$keys[0]];
+ }
+ else {
+ $hostObj->operator = null;
+ }
+ }
+
+ return $options;
+ }
+
+ public function getValueDropdownOptions()
+ {
+ $hostObj = $this->host;
+ $attribute = $hostObj->subcondition;
+ $definitions = $this->listModelAttributeInfo();
+
+ if (!isset($definitions[$attribute])) {
+ return [];
+ }
+
+ $columnType = array_get($definitions[$attribute], 'type');
+
+ if ($columnType != ConditionBase::CAST_RELATION) {
+ return [];
+ }
+
+ $referenceInfo = $this->prepareReferenceListInfo();
+ $referenceModel = $referenceInfo->referenceModel;
+ $nameFrom = $referenceInfo->primaryColumn;
+ $keyFrom = $referenceInfo->primaryKey;
+
+ // Determine if the model uses a tree trait
+ $treeTraits = [
+ \October\Rain\Database\Traits\NestedTree::class,
+ \October\Rain\Database\Traits\SimpleTree::class
+ ];
+ $usesTree = count(array_intersect($treeTraits, class_uses($referenceModel))) > 0;
+
+ $results = $referenceModel->get();
+
+ return $usesTree
+ ? $results->listsNested($nameFrom, $keyFrom)
+ : $results->lists($nameFrom, $keyFrom);
+ }
+
+ //
+ // Control type
+ //
+
+ protected function evalControlType()
+ {
+ $hostObj = $this->host;
+ $attribute = $hostObj->subcondition;
+ $operator = $hostObj->operator;
+
+ $definitions = $this->listModelAttributeInfo();
+
+ if (!isset($definitions[$attribute])) {
+ return 'text';
+ }
+
+ $columnType = array_get($definitions[$attribute], 'type');
+
+ if ($columnType != ConditionBase::CAST_RELATION) {
+ return 'text';
+ }
+ else {
+ if ($operator == 'is' || $operator == 'is_not') {
+ return 'dropdown';
+ }
+
+ return 'multi_value';
+ }
+ }
+
+ public function getValueControlType()
+ {
+ if (App::runningInBackend()) {
+ return $this->evalControlType();
+ }
+
+ $hostObj = $this->host;
+
+ if ($controlType = $hostObj->condition_control_type) {
+ return $controlType;
+ }
+
+ $controlType = $this->evalControlType();
+
+ $this->getModelObj()
+ ->where('id', $hostObj->id)
+ ->update(['condition_control_type' => $controlType]);
+
+ return $hostObj->condition_control_type = $controlType;
+ }
+
+ //
+ // Attributes
+ //
+
+ public function listSubconditions()
+ {
+ $attributes = $this->listModelAttributes();
+
+ $result = [];
+
+ foreach ($attributes as $name => $code) {
+ $result[$code] = $name;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Returns the supported attributes by a condition as an array.
+ * The key is the attribute and the value is the label.
+ *
+ * @return array
+ */
+ protected function listModelAttributes()
+ {
+ $attributeInfo = $this->listModelAttributeInfo();
+
+ foreach ($attributeInfo as $attribute => $info) {
+ $attributes[$attribute] = array_get($info, 'label');
+ }
+
+ asort($attributes);
+
+ return $attributes;
+ }
+
+ protected function listModelAttributeInfo()
+ {
+ if ($this->modelAttributes) {
+ return $this->modelAttributes;
+ }
+
+ $config = $this->makeConfig($this->defineModelAttributes($this->getConditionType()));
+
+ $attributes = $config->attributes ?? [];
+
+ /*
+ * Set defaults
+ */
+ foreach ($attributes as $attribute => $info) {
+ if (!isset($info['type'])) {
+ $attributes[$attribute]['type'] = 'string';
+ }
+ }
+
+ return $this->modelAttributes = $attributes;
+ }
+
+ //
+ // Relation based attributes
+ //
+
+ public function getReferencePrimaryColumn($record)
+ {
+ $referenceInfo = $this->prepareReferenceListInfo();
+
+ return $record->{$referenceInfo->primaryColumn};
+ }
+
+ public function listSelectedReferenceRecords()
+ {
+ $referenceInfo = $this->prepareReferenceListInfo();
+ $model = $referenceInfo->referenceModel;
+
+ $value = $this->host->value;
+ $keys = strlen($value) ? explode(',', $value) : [];
+
+ if (count($keys)) {
+ $model = $model->whereIn('id', $keys);
+ }
+ else {
+ $model = $model->whereRaw('id <> id');
+ }
+
+ $orderField = $referenceInfo->primaryColumn;
+
+ return $model->orderBy($orderField)->get();
+ }
+
+ public function prepareReferenceListInfo()
+ {
+ if (!is_null($this->referenceInfo)) {
+ return $this->referenceInfo;
+ }
+
+ $model = $this->getModelObj();
+ $attribute = $this->host->subcondition;
+ $definitions = $this->listModelAttributeInfo();
+ $definition = array_get($definitions, $attribute);
+
+ $columns = array_get($definition, 'columns');
+ $primaryColumn = array_get($definition, 'relation.nameFrom', 'name');
+
+ if ($model->hasRelation($attribute)) {
+ $relationType = $model->getRelationType($attribute);
+ $relationModel = $model->makeRelation($attribute);
+ $relationObject = $model->{$attribute}();
+
+ // Some simpler relations can specify a custom local or foreign "other" key,
+ // which can be detected and implemented here automagically.
+ $primaryKey = in_array($relationType, ['hasMany', 'belongsTo', 'hasOne'])
+ ? $relationObject->getOtherKey()
+ : $relationModel->getKeyName();
+ }
+ elseif ($relationClass = array_get($definition, 'relation.model')) {
+ $relationModel = new $relationClass;
+ $primaryKey = array_get($definition, 'relation.keyFrom', 'id');
+ }
+ else {
+ throw new SystemException(sprintf('Model %s does not contain a relation "%s"', get_class($model), $attribute));
+ }
+
+ if (!$columns) {
+ $columns = [
+ $primaryColumn => [
+ 'label' => array_get($definition, 'relation.label', '?'),
+ 'searchable' => true
+ ]
+ ];
+ }
+
+ $this->referenceInfo = [];
+ $this->referenceInfo['referenceModel'] = $relationModel;
+ $this->referenceInfo['primaryKey'] = $primaryKey;
+ $this->referenceInfo['primaryColumn'] = $primaryColumn;
+ $this->referenceInfo['columns'] = $columns;
+
+ return $this->referenceInfo = (object) $this->referenceInfo;
+ }
+
+ public function onPreRender($controller, $widget)
+ {
+ $controlType = $this->getValueControlType();
+
+ if ($controlType != 'multi_value') {
+ return;
+ }
+
+ $selectionColumn = [
+ '_select_record' => [
+ 'label' => '',
+ 'sortable' => false,
+ 'type' => 'partial',
+ 'width' => '10px',
+ 'path' => plugins_path('rainlab/notify/classes/modelattributesconditionbase/_column_select_record.htm')
+ ]
+ ];
+
+ $referenceInfo = $this->prepareReferenceListInfo();
+ $filterModel = $referenceInfo->referenceModel;
+ $filterColumns = $selectionColumn + $referenceInfo->columns;
+
+ /*
+ * List widget
+ */
+ $config = $this->makeConfig();
+ $config->columns = $filterColumns;
+ $config->model = $filterModel;
+ $config->alias = $widget->alias . 'List';
+ $config->showSetup = false;
+ $config->showCheckboxes = false;
+ $config->recordsPerPage = 6;
+ $listWidget = $controller->makeWidget('Backend\Widgets\Lists', $config);
+
+ /*
+ * Search widget
+ */
+ $config = $this->makeConfig();
+ $config->alias = $widget->alias . 'Search';
+ $config->growable = false;
+ $config->prompt = 'backend::lang.list.search_prompt';
+ $searchWidget = $controller->makeWidget('Backend\Widgets\Search', $config);
+ $searchWidget->cssClasses[] = 'condition-filter-search';
+
+ $listWidget->bindToController();
+ $searchWidget->bindToController();
+
+ /*
+ * Extend list query
+ */
+ $listWidget->bindEvent('list.extendQueryBefore', function ($query) use ($filterModel) {
+ $this->prepareFilterQuery($query, $filterModel);
+ });
+
+ /*
+ * Link the Search Widget to the List Widget
+ */
+ $listWidget->setSearchTerm($searchWidget->getActiveTerm());
+
+ $searchWidget->bindEvent('search.submit', function () use (&$searchWidget, $listWidget) {
+ $listWidget->setSearchTerm($searchWidget->getActiveTerm());
+ return $listWidget->onRefresh();
+ });
+
+ $controller->vars['listWidget'] = $listWidget;
+ $controller->vars['searchWidget'] = $searchWidget;
+ $controller->vars['filterHostModel'] = $this->host;
+ }
+
+ public function prepareFilterQuery($query, $model)
+ {
+ }
+
+ //
+ // Condition check
+ //
+
+ /**
+ * Checks whether the condition is TRUE for a specified model
+ * @return bool
+ */
+ public function evalIsTrue($model, $customValue = '__no_eval_value__')
+ {
+ $hostObj = $this->host;
+
+ $operator = $hostObj->operator;
+ $attribute = $hostObj->subcondition;
+
+ $conditionValue = $hostObj->value;
+ $conditionValue = trim(mb_strtolower($conditionValue));
+
+ $controlType = $this->getValueControlType();
+
+ if ($controlType == 'text') {
+ if ($customValue === self::NO_EVAL_VALUE) {
+ $modelValue = trim(mb_strtolower($model->{$attribute}));
+ }
+ else {
+ $modelValue = trim(mb_strtolower($customValue));
+ }
+
+ if ($operator == 'is') {
+ return $modelValue == $conditionValue;
+ }
+
+ if ($operator == 'is_not') {
+ return $modelValue != $conditionValue;
+ }
+
+ if ($operator == 'contains') {
+ return mb_strpos($modelValue, $conditionValue) !== false;
+ }
+
+ if ($operator == 'does_not_contain') {
+ return mb_strpos($modelValue, $conditionValue) === false;
+ }
+
+ if ($operator == 'equals_or_greater') {
+ return $modelValue >= $conditionValue;
+ }
+
+ if ($operator == 'equals_or_less') {
+ return $modelValue <= $conditionValue;
+ }
+
+ if ($operator == 'greater') {
+ return $modelValue > $conditionValue;
+ }
+
+ if ($operator == 'less') {
+ return $modelValue < $conditionValue;
+ }
+ }
+
+ if ($controlType == 'dropdown') {
+ if ($customValue === self::NO_EVAL_VALUE) {
+ $modelValue = $model->{$attribute};
+ }
+ else {
+ $modelValue = $customValue;
+ }
+
+ if ($operator == 'is') {
+ if ($modelValue == null) {
+ return false;
+ }
+
+ if ($modelValue instanceof EloquentModel) {
+ return $modelValue->getKey() == $conditionValue;
+ }
+
+ if (
+ is_array($modelValue) &&
+ count($modelValue) == 1 &&
+ array_key_exists(0, $modelValue)
+ ) {
+ return $modelValue[0] == $conditionValue;
+ }
+
+ if ($modelValue instanceof EloquentCollection) {
+ if ($modelValue->count() != 1) {
+ return false;
+ }
+
+ return $modelValue[0]->getKey() == $conditionValue;
+ }
+ }
+
+ if ($operator == 'is_not') {
+ if ($modelValue == null) {
+ return true;
+ }
+
+ if ($modelValue instanceof EloquentModel) {
+ return $modelValue->getKey() != $conditionValue;
+ }
+
+ if (is_array($modelValue)) {
+ if (count($modelValue) != 1) {
+ return true;
+ }
+
+ if (!array_key_exists(0, $modelValue)) {
+ return true;
+ }
+
+ return $modelValue[0] != $conditionValue;
+ }
+
+ if ($modelValue instanceof EloquentCollection) {
+ if (!$modelValue->count() || $modelValue->count() > 1) {
+ return true;
+ }
+
+ return $modelValue->first()->getKey() != $conditionValue;
+ }
+ }
+ }
+
+ if ($controlType == 'multi_value') {
+ if ($customValue === self::NO_EVAL_VALUE) {
+ $modelValue = $model->{$attribute};
+ }
+ else {
+ $modelValue = $customValue;
+ }
+
+ if (
+ (!$modelValue instanceof EloquentCollection) &&
+ (!$modelValue instanceof EloquentModel) &&
+ !is_array($modelValue)
+ ) {
+ return false;
+ }
+
+ if (strlen($conditionValue)) {
+ $conditionValues = explode(',', $conditionValue);
+ foreach ($conditionValues as &$value) {
+ $value = trim($value);
+ }
+ } else {
+ $conditionValues = [];
+ }
+
+ if ($modelValue instanceof EloquentCollection) {
+ $modelKeys = array_keys($modelValue->lists('id', 'id'));
+ }
+ elseif ($modelValue instanceof EloquentModel) {
+ $modelKeys = [$modelValue->getKey()];
+ }
+ else {
+ $modelKeys = $modelValue;
+ }
+
+ if ($operator == 'is') {
+ $operator = 'one_of';
+ }
+ elseif ($operator == 'is_not') {
+ $operator = 'not_one_of';
+ }
+
+ if ($operator == 'one_of') {
+ return count(array_intersect($conditionValues, $modelKeys)) ? true : false;
+ }
+
+ if ($operator == 'not_one_of') {
+ return count(array_intersect($conditionValues, $modelKeys)) ? false : true;
+ }
+ }
+
+ return false;
+ }
+
+ //
+ // Helpers
+ //
+
+ public function getModelObj()
+ {
+ if ($this->modelObj === null) {
+ if (array_key_exists($this->modelClass, self::$modelObjCache)) {
+ $this->modelObj = self::$modelObjCache[$this->modelClass];
+ }
+ else {
+ $this->modelObj = self::$modelObjCache[$this->modelClass] = new $this->modelClass;
+ }
+ }
+
+ return $this->modelObj;
+ }
+}
diff --git a/plugins/rainlab/notify/classes/Notifier.php b/plugins/rainlab/notify/classes/Notifier.php
new file mode 100644
index 00000000..de0f520c
--- /dev/null
+++ b/plugins/rainlab/notify/classes/Notifier.php
@@ -0,0 +1,134 @@
+registerGlobalParams([...]);
+ * });
+ *
+ * @param callable $callback A callable function.
+ */
+ public function registerCallback(callable $callback)
+ {
+ $this->callbacks[] = $callback;
+ }
+
+ /**
+ * Helper to process callbacks once and once only.
+ * @return void
+ */
+ protected function processCallbacks()
+ {
+ if ($this->registered) {
+ return;
+ }
+
+ foreach ($this->callbacks as $callback) {
+ $callback($this);
+ }
+
+ $this->registered = true;
+ }
+
+ //
+ // Event binding
+ //
+
+ public static function bindEvents(array $events)
+ {
+ foreach ($events as $event => $class) {
+ self::bindEvent($event, $class);
+ }
+ }
+
+ public static function bindEvent($systemEventName, $notifyEventClass)
+ {
+ Event::listen($systemEventName, function() use ($notifyEventClass, $systemEventName) {
+ $params = $notifyEventClass::makeParamsFromEvent(func_get_args(), $systemEventName);
+
+ self::instance()->queueEvent($notifyEventClass, $params);
+ });
+ }
+
+ public function queueEvent($eventClass, array $params)
+ {
+ $params += $this->getContextVars();
+
+ // Use queue
+ if (true) {
+ Queue::push(new EventParams($eventClass, $params));
+ }
+ else {
+ $this->fireEvent($eventClass, $params);
+ }
+ }
+
+ public function fireEvent($eventClass, array $params)
+ {
+ $models = NotificationRuleModel::listRulesForEvent($eventClass);
+
+ foreach ($models as $model) {
+ $model->setParams($params);
+ $model->triggerRule();
+ }
+ }
+
+ public function registerGlobalParams(array $params)
+ {
+ if (!$this->registeredGlobalParams) {
+ $this->registeredGlobalParams = [];
+ }
+
+ $this->registeredGlobalParams = $params + $this->registeredGlobalParams;
+ }
+
+ public function getContextVars()
+ {
+ $this->processCallbacks();
+
+ $globals = $this->registeredGlobalParams ?: [];
+
+ return [
+ 'isBackend' => App::runningInBackend() ? 1 : 0,
+ 'isConsole' => App::runningInConsole() ? 1 : 0,
+ 'appLocale' => App::getLocale(),
+ 'sender' => null // unsafe:BackendAuth::getUser()
+ ] + $globals;
+ }
+}
diff --git a/plugins/rainlab/notify/classes/ScheduledAction.php b/plugins/rainlab/notify/classes/ScheduledAction.php
new file mode 100644
index 00000000..bfde1327
--- /dev/null
+++ b/plugins/rainlab/notify/classes/ScheduledAction.php
@@ -0,0 +1,63 @@
+action = $action->id;
+ $this->params = $this->serializeParams($params);
+ }
+
+ /**
+ * Execute the job.
+ *
+ * @return void
+ */
+ public function handle()
+ {
+ $this->delete();
+
+ if (! $actionClass = RuleAction::find($this->action)) {
+ return traceLog('Error: Could not restore action #' . $this->action);
+ }
+
+ $params = $this->unserializeParams();
+ $actionClass->triggerAction($params, false);
+ }
+
+ protected function serializeParams($params)
+ {
+ $result = [];
+
+ foreach ($params as $param => $value) {
+ $result[$param] = $this->getSerializedPropertyValue($value);
+ }
+
+ return $result;
+ }
+
+ protected function unserializeParams()
+ {
+ $result = [];
+
+ foreach ($this->params as $param => $value) {
+ $result[$param] = $this->getRestoredPropertyValue($value);
+ }
+
+ return $result;
+ }
+}
diff --git a/plugins/rainlab/notify/classes/compoundcondition/fields.yaml b/plugins/rainlab/notify/classes/compoundcondition/fields.yaml
new file mode 100644
index 00000000..551ae812
--- /dev/null
+++ b/plugins/rainlab/notify/classes/compoundcondition/fields.yaml
@@ -0,0 +1,14 @@
+# ===================================
+# Field Definitions
+# ===================================
+
+fields:
+ condition_type:
+ label: Condition Type
+ span: auto
+ type: dropdown
+
+ condition:
+ label: Required Value
+ span: auto
+ type: dropdown
diff --git a/plugins/rainlab/notify/classes/modelattributesconditionbase/_column_select_record.htm b/plugins/rainlab/notify/classes/modelattributesconditionbase/_column_select_record.htm
new file mode 100644
index 00000000..a2187452
--- /dev/null
+++ b/plugins/rainlab/notify/classes/modelattributesconditionbase/_column_select_record.htm
@@ -0,0 +1,10 @@
+
+
+
+
+
diff --git a/plugins/rainlab/notify/classes/modelattributesconditionbase/_field_value.htm b/plugins/rainlab/notify/classes/modelattributesconditionbase/_field_value.htm
new file mode 100644
index 00000000..1ac013dd
--- /dev/null
+++ b/plugins/rainlab/notify/classes/modelattributesconditionbase/_field_value.htm
@@ -0,0 +1,107 @@
+subcondition;
+ $controlType = $formModel->getValueControlType();
+?>
+
+
+
+
+
+ getValueDropdownOptions();
+ ?>
+
+ $option): ?>
+ isSelected($value) ? 'selected="selected"' : '' ?>
+ value="= $value ?>">= e(trans($option)) ?>
+
+
+
+
+ listSelectedReferenceRecords();
+ $hasData = $selectedRecords->count();
+ ?>
+
+
+
+ = $searchWidget->render() ?>
+ = $listWidget->render() ?>
+
+
+
= e(__('Selected Records')) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ = e($formModel->getReferencePrimaryColumn($record)) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/rainlab/notify/classes/modelattributesconditionbase/fields.yaml b/plugins/rainlab/notify/classes/modelattributesconditionbase/fields.yaml
new file mode 100644
index 00000000..d0c49815
--- /dev/null
+++ b/plugins/rainlab/notify/classes/modelattributesconditionbase/fields.yaml
@@ -0,0 +1,22 @@
+# ===================================
+# Field Definitions
+# ===================================
+
+fields:
+
+ subcondition:
+ label: Attribute
+ span: auto
+ type: dropdown
+
+ operator:
+ label: Operator
+ span: auto
+ type: dropdown
+ dependsOn: subcondition
+
+ value:
+ label: Value
+ dependsOn: [subcondition, operator]
+ type: partial
+ path: field_value
diff --git a/plugins/rainlab/notify/composer.json b/plugins/rainlab/notify/composer.json
new file mode 100644
index 00000000..f55725b5
--- /dev/null
+++ b/plugins/rainlab/notify/composer.json
@@ -0,0 +1,25 @@
+{
+ "name": "rainlab/notify-plugin",
+ "type": "october-plugin",
+ "description": "Notify plugin for October CMS",
+ "homepage": "https://octobercms.com/plugin/rainlab-pages",
+ "keywords": ["october", "octobercms", "pages"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Alexey Bobkov",
+ "email": "aleksey.bobkov@gmail.com",
+ "role": "Co-founder"
+ },
+ {
+ "name": "Samuel Georges",
+ "email": "daftspunky@gmail.com",
+ "role": "Co-founder"
+ }
+ ],
+ "require": {
+ "php": ">=5.5.9",
+ "composer/installers": "~1.0"
+ },
+ "minimum-stability": "dev"
+}
diff --git a/plugins/rainlab/notify/controllers/Notifications.php b/plugins/rainlab/notify/controllers/Notifications.php
new file mode 100644
index 00000000..20e65f60
--- /dev/null
+++ b/plugins/rainlab/notify/controllers/Notifications.php
@@ -0,0 +1,130 @@
+asExtension('ListController')->index();
+ }
+
+ public function create($eventAlias = null)
+ {
+ try {
+ if (!$eventAlias) {
+ throw new ApplicationException('Missing a rule code');
+ }
+
+ $this->eventAlias = $eventAlias;
+ $this->bodyClass = 'compact-container breadcrumb-fancy';
+ $this->asExtension('FormController')->create();
+ }
+ catch (Exception $ex) {
+ $this->handleError($ex);
+ }
+ }
+
+ public function update($recordId = null, $context = null)
+ {
+ $this->bodyClass = 'compact-container breadcrumb-fancy';
+ $this->asExtension('FormController')->update($recordId, $context);
+ }
+
+ public function formExtendModel($model)
+ {
+ if (!$model->exists) {
+ $model->applyEventClass($this->getEventClass());
+ $model->name = $model->getEventDescription();
+ }
+
+ return $model;
+ }
+
+ // public function formBeforeSave($model)
+ // {
+ // $model->is_custom = 1;
+ // }
+
+ public function index_onLoadRuleGroupForm()
+ {
+ try {
+ $groups = EventBase::findEventGroups();
+ $this->vars['eventGroups'] = $groups;
+ }
+ catch (Exception $ex) {
+ $this->handleError($ex);
+ }
+
+ return $this->makePartial('add_rule_group_form');
+ }
+
+ /**
+ * This handler requires the group code passed from `onLoadRuleGroupForm`
+ */
+ public function index_onLoadRuleEventForm()
+ {
+ try {
+ if (!$code = post('code')) {
+ throw new ApplicationException('Missing event group code');
+ }
+
+ $events = EventBase::findEventsByGroup($code);
+ $this->vars['events'] = $events;
+ }
+ catch (Exception $ex) {
+ $this->handleError($ex);
+ }
+
+ return $this->makePartial('add_rule_event_form');
+ }
+
+ protected function getEventClass()
+ {
+ $alias = post('event_alias', $this->eventAlias);
+
+ if ($this->eventClass !== null) {
+ return $this->eventClass;
+ }
+
+ if (!$event = EventBase::findEventByIdentifier($alias)) {
+ throw new ApplicationException('Unable to find event with alias: '. $alias);
+ }
+
+ return $this->eventClass = get_class($event);
+ }
+}
diff --git a/plugins/rainlab/notify/controllers/notifications/_add_rule_event_form.htm b/plugins/rainlab/notify/controllers/notifications/_add_rule_event_form.htm
new file mode 100644
index 00000000..8e939e22
--- /dev/null
+++ b/plugins/rainlab/notify/controllers/notifications/_add_rule_event_form.htm
@@ -0,0 +1,39 @@
+= Form::open(['id' => 'addRuleEventForm']) ?>
+
+
+
+ fatalError): ?>
+
= $fatalError ?>
+
+
+
+
+
+
+
+
+
+= Form::close() ?>
diff --git a/plugins/rainlab/notify/controllers/notifications/_add_rule_group_form.htm b/plugins/rainlab/notify/controllers/notifications/_add_rule_group_form.htm
new file mode 100644
index 00000000..23507f4b
--- /dev/null
+++ b/plugins/rainlab/notify/controllers/notifications/_add_rule_group_form.htm
@@ -0,0 +1,47 @@
+= Form::open(['id' => 'addRuleGroupForm']) ?>
+
+
+
+ fatalError): ?>
+
= $fatalError ?>
+
+
+
+
+
+
+
+
+
+= Form::close() ?>
diff --git a/plugins/rainlab/notify/controllers/notifications/_form_toolbar.htm b/plugins/rainlab/notify/controllers/notifications/_form_toolbar.htm
new file mode 100644
index 00000000..f2277a3f
--- /dev/null
+++ b/plugins/rainlab/notify/controllers/notifications/_form_toolbar.htm
@@ -0,0 +1,39 @@
+formGetContext() == 'create';
+ $pageUrl = isset($pageUrl) ? $pageUrl : null;
+?>
+
diff --git a/plugins/rainlab/notify/controllers/notifications/_list_toolbar.htm b/plugins/rainlab/notify/controllers/notifications/_list_toolbar.htm
new file mode 100644
index 00000000..52030b6f
--- /dev/null
+++ b/plugins/rainlab/notify/controllers/notifications/_list_toolbar.htm
@@ -0,0 +1,9 @@
+
diff --git a/plugins/rainlab/notify/controllers/notifications/config_form.yaml b/plugins/rainlab/notify/controllers/notifications/config_form.yaml
new file mode 100644
index 00000000..2e2bda06
--- /dev/null
+++ b/plugins/rainlab/notify/controllers/notifications/config_form.yaml
@@ -0,0 +1,16 @@
+# ===================================
+# Form Behavior Config
+# ===================================
+
+name: Notification Rule
+form: $/rainlab/notify/models/notificationrule/fields.yaml
+modelClass: RainLab\Notify\Models\NotificationRule
+defaultRedirect: rainlab/notify/notifications
+
+create:
+ redirect: rainlab/notify/notifications/update/:id
+ redirectClose: rainlab/notify/notifications
+
+update:
+ redirect: rainlab/notify/notifications
+ redirectClose: rainlab/notify/notifications
diff --git a/plugins/rainlab/notify/controllers/notifications/config_list.yaml b/plugins/rainlab/notify/controllers/notifications/config_list.yaml
new file mode 100644
index 00000000..1d923a2d
--- /dev/null
+++ b/plugins/rainlab/notify/controllers/notifications/config_list.yaml
@@ -0,0 +1,20 @@
+# ===================================
+# List Behavior Config
+# ===================================
+
+title: Notification Rules
+list: $/rainlab/notify/models/notificationrule/columns.yaml
+modelClass: RainLab\Notify\Models\NotificationRule
+recordUrl: rainlab/notify/notifications/update/:id
+noRecordsMessage: backend::lang.list.no_records
+recordsPerPage: 30
+showSetup: true
+showCheckboxes: true
+defaultSort:
+ column: count
+ direction: desc
+
+toolbar:
+ buttons: list_toolbar
+ search:
+ prompt: backend::lang.list.search_prompt
diff --git a/plugins/rainlab/notify/controllers/notifications/create.htm b/plugins/rainlab/notify/controllers/notifications/create.htm
new file mode 100644
index 00000000..c1d8cf21
--- /dev/null
+++ b/plugins/rainlab/notify/controllers/notifications/create.htm
@@ -0,0 +1,31 @@
+
+
+
+
+fatalError): ?>
+
+
+ = Form::open([
+ 'class' => 'layout',
+ 'data-change-monitor' => 'true',
+ 'data-window-close-confirm' => e(__('Are you sure?')),
+ 'id' => 'post-form'
+ ]) ?>
+
+
+ = $this->formRender() ?>
+
+ = Form::close() ?>
+
+
+
+
+
+
+
diff --git a/plugins/rainlab/notify/controllers/notifications/index.htm b/plugins/rainlab/notify/controllers/notifications/index.htm
new file mode 100644
index 00000000..766877d9
--- /dev/null
+++ b/plugins/rainlab/notify/controllers/notifications/index.htm
@@ -0,0 +1,2 @@
+
+= $this->listRender() ?>
diff --git a/plugins/rainlab/notify/controllers/notifications/update.htm b/plugins/rainlab/notify/controllers/notifications/update.htm
new file mode 100644
index 00000000..ab376896
--- /dev/null
+++ b/plugins/rainlab/notify/controllers/notifications/update.htm
@@ -0,0 +1,30 @@
+
+
+
+
+fatalError): ?>
+
+
+ = Form::open([
+ 'class' => 'layout',
+ 'data-change-monitor' => 'true',
+ 'data-window-close-confirm' => e(__('Are you sure?')),
+ 'id' => 'post-form'
+ ]) ?>
+
+ = $this->formRender() ?>
+
+ = Form::close() ?>
+
+
+
+
+
+
+
diff --git a/plugins/rainlab/notify/formwidgets/ActionBuilder.php b/plugins/rainlab/notify/formwidgets/ActionBuilder.php
new file mode 100644
index 00000000..02d39338
--- /dev/null
+++ b/plugins/rainlab/notify/formwidgets/ActionBuilder.php
@@ -0,0 +1,388 @@
+fillFromConfig([
+ // ]);
+
+ if ($widget = $this->makeActionFormWidget()) {
+ $widget->bindToController();
+ }
+
+ if ($widget = $this->makeActionScheduleFormWidget()) {
+ $widget->bindToController();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function loadAssets()
+ {
+ $this->addJs('js/actions.js', 'RainLab.Notify');
+ $this->addCss('css/actions.css', 'RainLab.Notify');
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function render()
+ {
+ $this->prepareVars();
+
+ return $this->makePartial('actions_container');
+ }
+
+ /**
+ * Prepares the list data
+ */
+ public function prepareVars()
+ {
+ $this->vars['name'] = $this->getFieldName();
+ $this->vars['formModel'] = $this->model;
+ $this->vars['actions'] = $this->getActions();
+ $this->vars['actionFormWidget'] = $this->actionFormWidget;
+ $connection = config('queue.default');
+ $this->vars['queueDriver'] = config("queue.connections.{$connection}.driver");
+ $this->vars['actionScheduleFormWidget'] = $this->actionScheduleFormWidget;
+ $this->vars['availableTags'] = $this->getAvailableTags();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getSaveValue($value)
+ {
+ $this->model->bindEvent('model.afterSave', function() {
+ $this->processSave();
+ });
+
+ return FormField::NO_SAVE_DATA;
+ }
+
+ protected function processSave()
+ {
+ $cache = $this->getCacheActionDataPayload();
+
+ foreach ($cache as $id => $data) {
+ $action = $this->findActionObj($id);
+
+ if ($attributes = $this->getCacheActionAttributes($action)) {
+ $action->fill($attributes);
+ }
+
+ $action->save(null, $this->sessionKey);
+ }
+ }
+
+ //
+ // AJAX
+ //
+
+ public function onLoadCreateActionForm()
+ {
+ try {
+ $actions = ActionBase::findActions();
+ $this->vars['actions'] = $actions;
+ }
+ catch (Exception $ex) {
+ $this->handleError($ex);
+ }
+
+ return $this->makePartial('create_action_form');
+ }
+
+ public function onSaveAction()
+ {
+ $this->restoreCacheActionDataPayload();
+
+ $action = $this->findActionObj();
+
+ $data = post('Action', []);
+ $schedule = post('ActionSchedule', []);
+ $action->fill($data);
+ $action->fill($schedule);
+ $action->validate();
+ $action->action_text = $action->getActionObject()->getText();
+
+ $action->applyCustomData();
+
+ $this->setCacheActionData($action);
+
+ return $this->renderActions($action);
+ }
+
+ public function onLoadActionSetup()
+ {
+ try {
+ $action = $this->findActionObj();
+
+ $data = $this->getCacheActionAttributes($action);
+
+ if (!is_null($this->actionFormWidget)) {
+ $this->actionFormWidget->setFormValues($data);
+ }
+ $this->actionScheduleFormWidget->setFormValues($data);
+
+ $this->prepareVars();
+ $this->vars['action'] = $action;
+ }
+ catch (Exception $ex) {
+ $this->handleError($ex);
+ }
+
+ return $this->makePartial('action_settings_form');
+ }
+
+ public function onCreateAction()
+ {
+ if (!$className = post('action_class')) {
+ throw new ApplicationException('Please specify an action');
+ }
+
+ $this->restoreCacheActionDataPayload();
+
+ $newAction = $this->getRelationModel();
+ $newAction->class_name = $className;
+ $newAction->save();
+
+ $this->model->rule_actions()->add($newAction, post('_session_key'));
+
+ $this->vars['newActionId'] = $newAction->id;
+
+ return $this->renderActions();
+ }
+
+ public function onDeleteAction()
+ {
+ $action = $this->findActionObj();
+
+ $this->model->rule_actions()->remove($action, post('_session_key'));
+
+ return $this->renderActions();
+ }
+
+ public function onCancelActionSettings()
+ {
+ $action = $this->findActionObj(post('new_action_id'));
+
+ $action->delete();
+
+ return $this->renderActions();
+ }
+
+ //
+ // Postback deferring
+ //
+
+ public function getCacheActionAttributes($action)
+ {
+ return array_get($this->getCacheActionData($action), 'attributes');
+ }
+
+ public function getCacheActionTitle($action)
+ {
+ return array_get($this->getCacheActionData($action), 'title');
+ }
+
+ public function getCacheActionText($action)
+ {
+ return array_get($this->getCacheActionData($action), 'text');
+ }
+
+ public function getCacheActionData($action, $default = null)
+ {
+ $cache = post('action_data', []);
+
+ if (is_array($cache) && array_key_exists($action->id, $cache)) {
+ return json_decode($cache[$action->id], true);
+ }
+
+ if ($default === false) {
+ return null;
+ }
+
+ return $this->makeCacheActionData($action);
+ }
+
+ public function makeCacheActionData($action)
+ {
+ $data = [
+ 'attributes' => $action->config_data,
+ 'title' => $action->getTitle(),
+ 'text' => $action->getText(),
+ ];
+
+ return $data;
+ }
+
+ public function setCacheActionData($action)
+ {
+ $cache = post('action_data', []);
+
+ $cache[$action->id] = json_encode($this->makeCacheActionData($action));
+
+ Request::merge([
+ 'action_data' => $cache
+ ]);
+ }
+
+ public function restoreCacheActionDataPayload()
+ {
+ Request::merge([
+ 'action_data' => json_decode(post('current_action_data'), true)
+ ]);
+ }
+
+ public function getCacheActionDataPayload()
+ {
+ return post('action_data', []);
+ }
+
+ //
+ // Helpers
+ //
+
+ protected function getAvailableTags()
+ {
+ $tags = [];
+
+ if ($this->model->methodExists('defineParams')) {
+ $params = $this->model->defineParams();
+
+ foreach ($params as $param => $definition) {
+ $tags[$param] = array_get($definition, 'label');
+ }
+ }
+
+ return $tags;
+ }
+
+ /**
+ * Updates the primary rule actions container
+ * @return array
+ */
+ protected function renderActions()
+ {
+ $this->prepareVars();
+
+ return [
+ '#'.$this->getId() => $this->makePartial('actions')
+ ];
+ }
+
+ protected function makeActionFormWidget()
+ {
+ if ($this->actionFormWidget !== null) {
+ return $this->actionFormWidget;
+ }
+
+ if (!$model = $this->findActionObj(null, false)) {
+ return null;
+ }
+
+ if (!$model->hasFieldConfig()) {
+ return null;
+ }
+
+ $config = $model->getFieldConfig();
+ $config->model = $model;
+ $config->alias = $this->alias . 'Form';
+ $config->arrayName = 'Action';
+
+ $widget = $this->makeWidget('Backend\Widgets\Form', $config);
+
+ return $this->actionFormWidget = $widget;
+ }
+
+ protected function makeActionScheduleFormWidget()
+ {
+ if ($this->actionScheduleFormWidget !== null) {
+ return $this->actionScheduleFormWidget;
+ }
+
+ if (!$model = $this->findActionObj(null, false)) {
+ return null;
+ }
+
+ $config = $this->makeConfig('$/rainlab/notify/models/ruleaction/fields_schedule.yaml');
+ $config->model = $model;
+ $config->alias = $this->alias . 'ScheduleForm';
+ $config->arrayName = 'ActionSchedule';
+
+ $widget = $this->makeWidget('Backend\Widgets\Form', $config);
+
+ return $this->actionScheduleFormWidget = $widget;
+ }
+
+ protected function getActions()
+ {
+ if ($this->actionsCache !== false) {
+ return $this->actionsCache;
+ }
+
+ $relationObject = $this->getRelationObject();
+ $actions = $relationObject->withDeferred($this->sessionKey)->get();
+
+ return $this->actionsCache = $actions ?: null;
+ }
+
+ protected function findActionObj($actionId = null, $throw = true)
+ {
+ $actionId = $actionId ? $actionId : post('current_action_id');
+
+ $action = null;
+
+ if (strlen($actionId)) {
+ $action = $this->getRelationModel()->find($actionId);
+ }
+
+ if ($throw && !$action) {
+ throw new ApplicationException('Action not found');
+ }
+
+ return $action;
+ }
+}
diff --git a/plugins/rainlab/notify/formwidgets/ConditionBuilder.php b/plugins/rainlab/notify/formwidgets/ConditionBuilder.php
new file mode 100644
index 00000000..083716de
--- /dev/null
+++ b/plugins/rainlab/notify/formwidgets/ConditionBuilder.php
@@ -0,0 +1,455 @@
+fillFromConfig([
+ 'conditionsRuleType',
+ ]);
+
+ if ($widget = $this->makeConditionFormWidget()) {
+ $widget->bindToController();
+ }
+
+ $this->initRootCondition();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function loadAssets()
+ {
+ $this->addJs('js/conditions.js', 'RainLab.Notify');
+ $this->addJs('js/conditions.multivalue.js', 'RainLab.Notify');
+ $this->addCss('css/conditions.css', 'RainLab.Notify');
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function render()
+ {
+ $this->prepareVars();
+
+ return $this->makePartial('conditions_container');
+ }
+
+ /**
+ * Prepares the list data
+ */
+ public function prepareVars()
+ {
+ $this->vars['name'] = $this->getFieldName();
+ $this->vars['rootCondition'] = $this->getConditionsRoot();
+ $this->vars['conditionFormWidget'] = $this->conditionFormWidget;
+ }
+
+ public function initRootCondition()
+ {
+ if ($this->getConditionsRoot()) {
+ return;
+ }
+
+ $relationObject = $this->getRelationObject();
+
+ $rootRule = $this->getRelationModel();
+ $rootRule->rule_host_type = $this->conditionsRuleType;
+ $rootRule->class_name = $rootRule->getRootConditionClass();
+ $rootRule->save();
+
+ $relationObject->add($rootRule, $this->sessionKey);
+
+ $this->conditionsRoot = $rootRule;
+ }
+
+ public function isRootCondition($condition)
+ {
+ if ($root = $this->getConditionsRoot()) {
+ return $condition->id === $root->id;
+ }
+
+ return false;
+ }
+
+ public function getConditionsRoot()
+ {
+ if ($this->conditionsRoot !== false) {
+ return $this->conditionsRoot;
+ }
+
+ $relationObject = $this->getRelationObject();
+ $rootCondition = $relationObject->withDeferred($this->sessionKey)->first();
+
+ return $this->conditionsRoot = $rootCondition ?: null;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getSaveValue($value)
+ {
+ $this->model->bindEvent('model.afterSave', function() {
+ $this->processSave();
+ });
+
+ return FormField::NO_SAVE_DATA;
+ }
+
+ protected function processSave()
+ {
+ $cache = $this->getCacheConditionDataPayload();
+
+ foreach ($cache as $id => $data) {
+ $condition = $this->findConditionObj($id);
+ $attributes = $this->getCacheConditionAttributes($condition);
+ $condition->fill($attributes);
+ $condition->save(null, $this->sessionKey.'_'.$condition->id);
+ }
+ }
+
+ //
+ // AJAX
+ //
+
+ public function onLoadConditionSetup()
+ {
+ try {
+ $condition = $this->findConditionObj();
+
+ $this->prepareVars();
+
+ $this->vars['condition'] = $condition;
+ }
+ catch (Exception $ex) {
+ $this->handleError($ex);
+ }
+
+ return $this->makePartial('condition_settings_form');
+ }
+
+ public function onLoadCreateChildCondition()
+ {
+ try {
+ $condition = $this->findConditionObj();
+
+ /*
+ * Look up parents
+ */
+ $parents = [$condition->id];
+ $parentsArray = post('condition_parent_id', []);
+ $currentId = $condition->id;
+
+ while (array_key_exists($currentId, $parentsArray) && $parentsArray[$currentId]) {
+ $parents[] = $currentId = $parentsArray[$currentId];
+ }
+
+ /*
+ * Custom rules provided by model
+ */
+ $extraRules = [];
+ if ($this->model->methodExists('getExtraConditionRules')) {
+ $extraRules = $this->model->getExtraConditionRules();
+ }
+
+ /*
+ * Look up conditions
+ */
+ $options = $condition->getChildOptions([
+ 'ruleType' => $this->conditionsRuleType,
+ 'parentIds' => $parents,
+ 'extraRules' => $extraRules
+ ]);
+
+ $this->prepareVars();
+ $this->vars['condition'] = $condition;
+ $this->vars['options'] = $options;
+ }
+ catch (Exception $ex) {
+ $this->handleError($ex);
+ }
+
+ return $this->makePartial('create_child_form');
+ }
+
+ public function onSaveCondition()
+ {
+ $this->restoreCacheConditionDataPayload();
+
+ $condition = $this->findConditionObj();
+
+ $data = post('Condition', []);
+ $condition->fill($data);
+ $condition->validate();
+ $condition->condition_text = $condition->getConditionObject()->getText();
+
+ $condition->applyCustomData();
+
+ $this->setCacheConditionData($condition);
+
+ return $this->renderConditions($condition);
+ }
+
+ public function onCreateCondition()
+ {
+ if (!$className = post('condition_class')) {
+ throw new ValidationException(['condition_class' => 'Please specify a condition']);
+ }
+
+ $this->restoreCacheConditionDataPayload();
+
+ $subcondition = null;
+
+ $parts = explode(':', $className);
+ if (count($parts) > 1) {
+ $subcondition = $parts[1];
+ $className = $parts[0];
+ }
+
+ $parentCondition = $this->findConditionObj();
+
+ $newCondition = $this->getRelationModel();
+ $newCondition->class_name = $className;
+ $newCondition->rule_host_type = $parentCondition->rule_host_type;
+
+ if ($subcondition) {
+ $newCondition->subcondition = $subcondition;
+ }
+
+ $newCondition->save();
+
+ $parentCondition->children()->add($newCondition, post('_session_key').'_'.$parentCondition->id);
+
+ $this->vars['newConditionId'] = $newCondition->id;
+
+ return $this->renderConditions($parentCondition);
+ }
+
+ public function onDeleteCondition()
+ {
+ $parentCondition = null;
+
+ $condition = $this->findConditionObj();
+
+ if ($parentId = $this->getParentIdFromCondition($condition)) {
+ $parentCondition = $this->findConditionObj($parentId);
+
+ if ($parentCondition) {
+ $parentCondition->children()->remove($condition, post('_session_key').'_'.$parentCondition->id);
+ }
+ }
+
+ return $this->renderConditions($parentCondition);
+ }
+
+ public function onCancelConditionSettings()
+ {
+ $condition = $this->findConditionObj(post('new_condition_id'));
+
+ $condition->delete();
+
+ return $this->renderConditions();
+ }
+
+ //
+ // Postback deferring
+ //
+
+ public function getCacheConditionAttributes($condition)
+ {
+ return array_get($this->getCacheConditionData($condition), 'attributes');
+ }
+
+ public function getCacheConditionText($condition)
+ {
+ return array_get($this->getCacheConditionData($condition), 'text');
+ }
+
+ public function getCacheConditionJoinText($condition)
+ {
+ return array_get($this->getCacheConditionData($condition), 'joinText');
+ }
+
+ public function getCacheConditionData($condition, $default = null)
+ {
+ $cache = post('condition_data', []);
+
+ if (is_array($cache) && array_key_exists($condition->id, $cache)) {
+ return json_decode($cache[$condition->id], true);
+ }
+
+ if ($default === false) {
+ return null;
+ }
+
+ return $this->makeCacheConditionData($condition);
+ }
+
+ public function makeCacheConditionData($condition)
+ {
+ $data = [
+ 'attributes' => $condition->config_data,
+ 'text' => $condition->getText()
+ ];
+
+ if ($condition->isCompound()) {
+ $data['joinText'] = $condition->getJoinText();
+ }
+
+ return $data;
+ }
+
+ public function setCacheConditionData($condition)
+ {
+ $cache = post('condition_data', []);
+
+ $cache[$condition->id] = json_encode($this->makeCacheConditionData($condition));
+
+ Request::merge([
+ 'condition_data' => $cache
+ ]);
+ }
+
+ public function restoreCacheConditionDataPayload()
+ {
+ Request::merge([
+ 'condition_data' => json_decode(post('current_condition_data', []), true)
+ ]);
+ }
+
+ public function getCacheConditionDataPayload()
+ {
+ return post('condition_data');
+ }
+
+ //
+ // Helpers
+ //
+
+ public function getParentIdFromCondition($condition)
+ {
+ if ($parentId = post('current_parent_id')) {
+ return $parentId;
+ }
+
+ $parentIds = post('condition_parent_id', []);
+
+ if (isset($parentIds[$condition->id])) {
+ return $parentIds[$condition->id];
+ }
+ }
+
+ /**
+ * Updates the primary rule conditions container
+ * @return array
+ */
+ protected function renderConditions($currentCondition = null)
+ {
+ if ($currentCondition && $this->isRootCondition($currentCondition)) {
+ $condition = $currentCondition;
+ }
+ else {
+ $condition = $this->getConditionsRoot();
+ }
+
+ return [
+ '#'.$this->getId() => $this->makePartial('conditions', ['condition' => $condition])
+ ];
+ }
+
+ protected function makeConditionFormWidget()
+ {
+ if ($this->conditionFormWidget !== null) {
+ return $this->conditionFormWidget;
+ }
+
+ if (!$model = $this->findConditionObj(null, false)) {
+ return null;
+ }
+
+ $config = $model->getFieldConfig();
+ $config->model = $model;
+ $config->alias = $this->alias . 'Form';
+ $config->arrayName = 'Condition';
+
+ $widget = $this->makeWidget('Backend\Widgets\Form', $config);
+
+ /*
+ * Set form values based on postback or cached attributes
+ */
+ if (!$data = post('Condition')) {
+ $data = $this->getCacheConditionAttributes($model);
+ }
+
+ $widget->setFormValues($data);
+
+ /*
+ * Allow conditions to register their own widgets
+ */
+ $model->onPreRender($this->controller, $this);
+
+ return $this->conditionFormWidget = $widget;
+ }
+
+ protected function findConditionObj($conditionId = null, $throw = true)
+ {
+ $conditionId = $conditionId ? $conditionId : post('current_condition_id');
+
+ $condition = null;
+
+ if (strlen($conditionId)) {
+ $condition = $this->getRelationModel()->find($conditionId);
+ }
+
+ if ($throw && !$condition) {
+ throw new ApplicationException('Condition not found');
+ }
+
+ return $condition;
+ }
+}
diff --git a/plugins/rainlab/notify/formwidgets/actionbuilder/assets/css/actions.css b/plugins/rainlab/notify/formwidgets/actionbuilder/assets/css/actions.css
new file mode 100644
index 00000000..817a9407
--- /dev/null
+++ b/plugins/rainlab/notify/formwidgets/actionbuilder/assets/css/actions.css
@@ -0,0 +1,227 @@
+.action-set {
+ margin-top: 10px;
+}
+.action-set ol {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+.action-set ol > li {
+ -webkit-transition: width 1s;
+ transition: width 1s;
+}
+.action-set ol > li > div {
+ font-size: 14px;
+ font-weight: normal;
+ position: relative;
+}
+.action-set ol > li > div > a,
+.action-set ol > li > div div.content {
+ color: #2b3e50;
+ padding: 10px 45px 10px 91px;
+ display: block;
+ line-height: 150%;
+ text-decoration: none;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+.action-set ol > li > div span.icon {
+ display: block;
+ position: absolute;
+ width: 22px;
+ height: 22px;
+ left: 20px;
+ top: 15px;
+ text-align: center;
+ font-weight: 500;
+ line-height: 22px;
+ color: #999;
+}
+.action-set ol > li > div span.icon > i {
+ line-height: 22px;
+ font-weight: normal;
+ font-size: 22px;
+ color: #999;
+}
+.action-set ol > li > div:not(.no-hover):hover,
+.action-set ol > li > div.popover-highlight {
+ background-color: #4ea5e0 !important;
+}
+.action-set ol > li > div:not(.no-hover):hover span.icon > i,
+.action-set ol > li > div.popover-highlight span.icon > i {
+ color: #fff !important;
+}
+.action-set ol > li > div:not(.no-hover):hover > a,
+.action-set ol > li > div.popover-highlight > a {
+ color: #ffffff !important;
+}
+.action-set ol > li > div:not(.no-hover):hover:before,
+.action-set ol > li > div.popover-highlight:before {
+ background-position: 0px -80px;
+}
+.action-set ol > li > div:not(.no-hover):hover:after,
+.action-set ol > li > div.popover-highlight:after {
+ top: 0 !important;
+ bottom: 0 !important;
+}
+.action-set ol > li > div:not(.no-hover):hover span,
+.action-set ol > li > div.popover-highlight span {
+ color: #ffffff !important;
+}
+.action-set ol > li > div:not(.no-hover):hover span.drag-handle,
+.action-set ol > li > div.popover-highlight span.drag-handle {
+ cursor: move;
+ opacity: 1;
+ filter: alpha(opacity=100);
+}
+.action-set ol > li > div:not(.no-hover):active {
+ background-color: #3498db !important;
+}
+.action-set ol > li > div:not(.no-hover):active > a {
+ color: #ffffff !important;
+}
+.action-set ol > li > div span.icon:first-child {
+ font-size: 11px;
+ color: #ccc;
+}
+.action-set ol > li > div span.icon:first-child > i {
+ color: #ccc;
+ font-size: 15px;
+}
+.action-set ol > li > div span.icon:last-child {
+ left: 52px;
+}
+.action-set ol > li > div span.comment {
+ display: block;
+ font-weight: 400;
+ color: #95a5a6;
+ font-size: 13px;
+ margin-top: 2px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+.action-set ol > li > div > .subpanel {
+ right: 0;
+ top: 0;
+ height: 100%;
+ position: absolute;
+ z-index: 200;
+ padding: 0 20px;
+}
+.action-set ol > li > div > ul.submenu {
+ position: absolute;
+ right: 0;
+ top: 0;
+ padding: 0;
+ list-style: none;
+ z-index: 200;
+ height: 100%;
+ display: none;
+ margin: 0;
+ font-size: 0;
+}
+.action-set ol > li > div > ul.submenu li {
+ font-size: 12px;
+ height: 100%;
+ display: inline-block;
+ background: #2581b8;
+ border-right: 1px solid #328ec8;
+}
+.action-set ol > li > div > ul.submenu li p {
+ display: table;
+ height: 100%;
+ padding: 0;
+ margin: 0;
+}
+.action-set ol > li > div > ul.submenu li p a {
+ display: table-cell;
+ vertical-align: middle;
+ height: 100%;
+ padding: 0 20px;
+ font-size: 13px;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ color: #ffffff;
+ text-decoration: none;
+}
+.action-set ol > li > div > ul.submenu li p a i.control-icon {
+ font-size: 22px;
+}
+.action-set ol > li > div:hover > ul.submenu {
+ display: block;
+}
+.action-set ol > li > div .checkbox {
+ position: absolute;
+ top: -2px;
+ right: 0;
+}
+.action-set ol > li > div .checkbox label {
+ margin-right: 0;
+}
+.action-set ol > li > div .checkbox label:before {
+ border-color: #cccccc;
+}
+.action-set ol > li > div.popover-highlight {
+ background-color: #4ea5e0 !important;
+}
+.action-set ol > li > div.popover-highlight:before {
+ background-position: 0px -80px;
+}
+.action-set ol > li > div.popover-highlight > a {
+ color: #ffffff !important;
+ cursor: default;
+}
+.action-set ol > li > div.popover-highlight span {
+ color: #ffffff !important;
+}
+.action-set ol > li > div.popover-highlight > ul.submenu,
+.action-set ol > li > div.popover-highlight > span.drag-handle {
+ display: none !important;
+}
+.action-set ol > li.active > div {
+ background: #dddddd;
+}
+.action-set ol > li.active > div:after {
+ position: absolute;
+ width: 4px;
+ left: 0;
+ top: -1px;
+ bottom: -1px;
+ background: #e67e22;
+ display: block;
+ content: ' ';
+}
+.action-set ol > li.active > div > span.comment,
+.action-set ol > li.active > div > span.expand {
+ color: #8f8f8f;
+}
+.action-set a.menu-control {
+ display: block;
+ margin: 20px;
+ padding: 13px 15px;
+ border: dotted 2px #ebebeb;
+ color: #bdc3c7;
+ font-size: 12px;
+ font-weight: 600;
+ text-transform: uppercase;
+ border-radius: 5px;
+ vertical-align: middle;
+}
+.action-set a.menu-control:hover,
+.action-set a.menu-control:focus {
+ text-decoration: none;
+ background-color: #4ea5e0;
+ color: #ffffff;
+ border: none;
+ padding: 15px 17px;
+}
+.action-set a.menu-control:active {
+ background: #3498db;
+ color: #ffffff;
+}
+.action-set a.menu-control i {
+ margin-right: 10px;
+ font-size: 14px;
+}
diff --git a/plugins/rainlab/notify/formwidgets/actionbuilder/assets/js/actions.js b/plugins/rainlab/notify/formwidgets/actionbuilder/assets/js/actions.js
new file mode 100644
index 00000000..ea7a12d6
--- /dev/null
+++ b/plugins/rainlab/notify/formwidgets/actionbuilder/assets/js/actions.js
@@ -0,0 +1,156 @@
+/*
+ * Global helpers
+ */
+function showActionSettings(id) {
+ var $control = $('[data-action-id='+id+']').closest('[data-control="ruleactions"]')
+
+ $control.ruleActions('onShowNewActionSettings', id)
+}
+
+/*
+ * Plugin definition
+ */
++function ($) { "use strict";
+ var Base = $.oc.foundation.base,
+ BaseProto = Base.prototype
+
+ var RuleActions = function (element, options) {
+ this.$el = $(element)
+ this.options = options || {}
+
+ $.oc.foundation.controlUtils.markDisposable(element)
+ Base.call(this)
+ this.init()
+ }
+
+ RuleActions.prototype = Object.create(BaseProto)
+ RuleActions.prototype.constructor = RuleActions
+
+ RuleActions.prototype.init = function() {
+ this.$el.on('click', '[data-actions-settings]', this.proxy(this.onShowSettings))
+ this.$el.on('click', '[data-actions-delete]', this.proxy(this.onDeleteAction))
+ this.$el.one('dispose-control', this.proxy(this.dispose))
+ }
+
+ RuleActions.prototype.dispose = function() {
+ this.$el.off('click', '[data-actions-settings]', this.proxy(this.onShowSettings))
+ this.$el.off('click', '[data-actions-delete]', this.proxy(this.onDeleteAction))
+ this.$el.off('dispose-control', this.proxy(this.dispose))
+ this.$el.removeData('oc.ruleActions')
+
+ this.$el = null
+
+ // In some cases options could contain callbacks,
+ // so it's better to clean them up too.
+ this.options = null
+
+ BaseProto.dispose.call(this)
+ }
+
+ RuleActions.prototype.onDeleteAction = function(event) {
+ var $el = $(event.target),
+ actionId = getActionIdFromElement($el)
+
+ $el.request(this.options.deleteHandler, {
+ data: { current_action_id: actionId },
+ confirm: 'Do you really want to delete this action?'
+ })
+ }
+
+ RuleActions.prototype.onShowNewActionSettings = function(actionId) {
+ var $el = $('[data-action-id='+actionId+']')
+
+ var popup_size = 'giant';
+ // Action does not use settings
+ if ($el.hasClass('no-form')) {
+ // Popup will contain scheduling option only
+ popup_size = 'medium'
+ }
+
+ $el.popup({
+ handler: this.options.settingsHandler,
+ extraData: { current_action_id: actionId },
+ size: popup_size
+ })
+
+ // This will not fire on successful save because the target element
+ // is replaced by the time the popup loader has finished to call it
+ $el.one('hide.oc.popup', this.proxy(this.onCancelAction))
+ }
+
+ RuleActions.prototype.onCancelAction = function(event) {
+ var $el = $(event.target),
+ actionId = getActionIdFromElement($el)
+
+ $el.request(this.options.cancelHandler, {
+ data: { new_action_id: actionId }
+ })
+
+ return false
+ }
+
+ RuleActions.prototype.onShowSettings = function(event) {
+ var $el = $(event.target),
+ actionId = getActionIdFromElement($el)
+
+ var popup_size = 'giant';
+ // Action does not use settings
+ if ($el.closest('li.action-item').hasClass('no-form')) {
+ popup_size = 'medium'
+ }
+
+ $el.popup({
+ handler: this.options.settingsHandler,
+ extraData: { current_action_id: actionId },
+ size: popup_size
+ })
+
+ return false
+ }
+
+ function getActionIdFromElement($el) {
+ var $item = $el.closest('li.action-item')
+
+ return $item.data('action-id')
+ }
+
+ RuleActions.DEFAULTS = {
+ settingsHandler: null,
+ deleteHandler: null,
+ cancelHandler: null,
+ createHandler: null
+ }
+
+ // PLUGIN DEFINITION
+ // ============================
+
+ var old = $.fn.ruleActions
+
+ $.fn.ruleActions = function (option) {
+ var args = Array.prototype.slice.call(arguments, 1), items, result
+
+ items = this.each(function () {
+ var $this = $(this)
+ var data = $this.data('oc.ruleActions')
+ var options = $.extend({}, RuleActions.DEFAULTS, $this.data(), typeof option == 'object' && option)
+ if (!data) $this.data('oc.ruleActions', (data = new RuleActions(this, options)))
+ if (typeof option == 'string') result = data[option].apply(data, args)
+ if (typeof result != 'undefined') return false
+ })
+
+ return result ? result : items
+ }
+
+ $.fn.ruleActions.Constructor = RuleActions
+
+ $.fn.ruleActions.noConflict = function () {
+ $.fn.ruleActions = old
+ return this
+ }
+
+ // Add this only if required
+ $(document).render(function (){
+ $('[data-control="ruleactions"]').ruleActions()
+ })
+
+}(window.jQuery);
diff --git a/plugins/rainlab/notify/formwidgets/actionbuilder/assets/less/actions.less b/plugins/rainlab/notify/formwidgets/actionbuilder/assets/less/actions.less
new file mode 100644
index 00000000..16440bb2
--- /dev/null
+++ b/plugins/rainlab/notify/formwidgets/actionbuilder/assets/less/actions.less
@@ -0,0 +1,273 @@
+@import "../../../../../../../modules/backend/assets/less/core/boot.less";
+
+//
+// Actions
+// --------------------------------------------------
+
+@color-actions-item-bg: #ffffff;
+@color-actions-item-title: #2b3e50;
+@color-actions-item-comment: #95a5a6;
+@color-actions-control: #bdc3c7;
+@color-actions-hover-bg: @highlight-hover-bg;
+@color-actions-hover-text: @highlight-hover-text;
+@color-actions-active-bg: @highlight-active-bg;
+@color-actions-active-text: @highlight-active-text;
+@color-actions-item-active-comment: #8f8f8f;
+@color-actions-submenu-text: #ffffff;
+@color-actions-light-submenu-bg: #2581b8;
+@color-actions-light-submenu-border: #328ec8;
+
+.action-set {
+ margin-top: 10px;
+
+ ol {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+
+ > li {
+ .transition(width 1s);
+
+ > div {
+ font-size: @font-size-base;
+ font-weight: normal;
+ position: relative;
+
+ > a, div.content {
+ color: @color-actions-item-title;
+ padding: 10px 45px 10px 91px;
+ display: block;
+ line-height: 150%;
+ text-decoration: none;
+ .box-sizing(border-box);
+ }
+
+ span.icon {
+ display: block;
+ position: absolute;
+ width: 22px;
+ height: 22px;
+ left: 20px;
+ top: 15px;
+ text-align: center;
+ font-weight: 500;
+ line-height: 22px;
+ color: #999;
+
+ > i {
+ line-height: 22px;
+ font-weight: normal;
+ font-size: 22px;
+ color: #999;
+ }
+ }
+
+ &:not(.no-hover):hover, &.popover-highlight {
+ background-color: @color-actions-hover-bg !important;
+
+ span.icon > i {
+ color: #fff !important;
+ }
+
+ > a {
+ color: @color-actions-hover-text !important;
+ }
+
+ &:before {
+ background-position: 0px -80px;
+ }
+
+ &:after {
+ top: 0 !important;
+ bottom: 0 !important;
+ }
+
+ span {
+ color: @color-actions-hover-text !important;
+
+ &.drag-handle {
+ cursor: move;
+ .opacity(1);
+ }
+ }
+ }
+
+ &:not(.no-hover):active {
+ background-color: @color-actions-active-bg !important;
+
+ > a {
+ color: @color-actions-active-text !important;
+ }
+ }
+
+ span.icon:first-child {
+ font-size: 11px;
+ color: #ccc;
+
+ > i {
+ color: #ccc;
+ font-size: 15px;
+ }
+ }
+ span.icon:last-child {
+ left: 52px;
+ }
+
+ span.comment {
+ display: block;
+ font-weight: 400;
+ color: @color-actions-item-comment;
+ font-size: @font-size-base - 1;
+ margin-top: 2px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ > .subpanel {
+ right: 0;
+ top: 0;
+ height: 100%;
+ position: absolute;
+ z-index: 200;
+ padding: 0 20px;
+ }
+
+ > ul.submenu {
+ position: absolute;
+ right: 0;
+ top: 0;
+ padding: 0;
+ list-style: none;
+ z-index: 200;
+ height: 100%;
+ display: none;
+ margin: 0;
+ font-size: 0;
+
+ li {
+ font-size: @font-size-base - 2;
+ height: 100%;
+ display: inline-block;
+ background: @color-actions-light-submenu-bg;
+ border-right: 1px solid @color-actions-light-submenu-border;
+
+ p {
+ display: table;
+ height: 100%;
+ padding: 0;
+ margin: 0;
+
+ a {
+ display: table-cell;
+ vertical-align: middle;
+ height: 100%;
+ padding: 0 20px;
+ font-size: @font-size-base - 1;
+ .box-sizing(border-box);
+ color: @color-actions-submenu-text;
+ text-decoration: none;
+
+ i.control-icon {
+ font-size: 22px;
+ }
+ }
+ }
+ }
+ }
+
+ &:hover {
+ > ul.submenu {
+ display: block;
+ }
+ }
+
+ .checkbox {
+ position: absolute;
+ top: -2px;
+ right: 0;
+
+ label {
+ margin-right: 0;
+
+ &:before {
+ border-color: @color-filelist-cb-border;
+ }
+ }
+ }
+
+ &.popover-highlight {
+ background-color: @color-actions-hover-bg !important;
+
+ &:before {
+ background-position: 0px -80px;
+ }
+
+ > a {
+ color: @color-actions-hover-text !important;
+ cursor: default;
+ }
+
+ span {
+ color: @color-actions-hover-text !important;
+ }
+
+ > ul.submenu, > span.drag-handle {
+ display: none !important;
+ }
+ }
+ }
+
+ &.active {
+ > div {
+ background: @color-list-active;
+
+ &:after {
+ position: absolute;
+ width: 4px;
+ left: 0;
+ top: -1px;
+ bottom: -1px;
+ background: @color-list-active-border;
+ display: block;
+ content: ' ';
+ }
+
+ > span.comment, > span.expand {
+ color: @color-actions-item-active-comment;
+ }
+ }
+ }
+ }
+ }
+
+ a.menu-control {
+ display: block;
+ margin: 20px;
+ padding: 13px 15px;
+ border: dotted 2px #ebebeb;
+ color: #bdc3c7;
+ font-size: @font-size-base - 2;
+ font-weight: 600;
+ text-transform: uppercase;
+ border-radius: 5px;
+ vertical-align: middle;
+
+ &:hover, &:focus {
+ text-decoration: none;
+ background-color: @color-actions-hover-bg;
+ color: @color-actions-hover-text;
+ border: none;
+ padding: 15px 17px;
+ }
+
+ &:active {
+ background: @color-actions-active-bg;
+ color: @color-actions-active-text;
+ }
+
+ i {
+ margin-right: 10px;
+ font-size: 14px;
+ }
+ }
+}
\ No newline at end of file
diff --git a/plugins/rainlab/notify/formwidgets/actionbuilder/partials/_action.htm b/plugins/rainlab/notify/formwidgets/actionbuilder/partials/_action.htm
new file mode 100644
index 00000000..ecda30b0
--- /dev/null
+++ b/plugins/rainlab/notify/formwidgets/actionbuilder/partials/_action.htm
@@ -0,0 +1,29 @@
+
+
+
+
+
diff --git a/plugins/rainlab/notify/formwidgets/actionbuilder/partials/_action_settings_form.htm b/plugins/rainlab/notify/formwidgets/actionbuilder/partials/_action_settings_form.htm
new file mode 100644
index 00000000..5ca84f8b
--- /dev/null
+++ b/plugins/rainlab/notify/formwidgets/actionbuilder/partials/_action_settings_form.htm
@@ -0,0 +1,77 @@
+= Form::open(['id' => 'propertyForm']) ?>
+
+
+
+ fatalError): ?>
+
+
+
+
+
+
+
+
+ = $actionFormWidget->render() ?>
+
+
+ = $this->makePartial('available_tags') ?>
+
+ = $this->makePartial('schedule') ?>
+
+
+
+ = $this->makePartial('schedule') ?>
+
+
+
+
+
+
+
+
= e(trans($this->fatalError)) ?>
+
+
+
+
+
+
diff --git a/plugins/rainlab/notify/formwidgets/actionbuilder/partials/_actions.htm b/plugins/rainlab/notify/formwidgets/actionbuilder/partials/_actions.htm
new file mode 100644
index 00000000..afabf2d7
--- /dev/null
+++ b/plugins/rainlab/notify/formwidgets/actionbuilder/partials/_actions.htm
@@ -0,0 +1,31 @@
+
+
+
+
+
+ = e(__('ON')) ?>
+
+
+
+ = e($formModel->getEventName()) ?>
+
+
+
+
+
+ = $this->makePartial('action', ['action' => $action]) ?>
+
+
+
+
+
+
+
+
+
diff --git a/plugins/rainlab/notify/formwidgets/actionbuilder/partials/_actions_container.htm b/plugins/rainlab/notify/formwidgets/actionbuilder/partials/_actions_container.htm
new file mode 100644
index 00000000..3e50fe9d
--- /dev/null
+++ b/plugins/rainlab/notify/formwidgets/actionbuilder/partials/_actions_container.htm
@@ -0,0 +1,14 @@
+
+
+ = $this->makePartial('actions', ['actions' => $actions]) ?>
+
+
+
+
diff --git a/plugins/rainlab/notify/formwidgets/actionbuilder/partials/_available_tags.htm b/plugins/rainlab/notify/formwidgets/actionbuilder/partials/_available_tags.htm
new file mode 100644
index 00000000..324de43f
--- /dev/null
+++ b/plugins/rainlab/notify/formwidgets/actionbuilder/partials/_available_tags.htm
@@ -0,0 +1,23 @@
+
+
+
+
+
diff --git a/plugins/rainlab/notify/formwidgets/actionbuilder/partials/_create_action_form.htm b/plugins/rainlab/notify/formwidgets/actionbuilder/partials/_create_action_form.htm
new file mode 100644
index 00000000..01b04d99
--- /dev/null
+++ b/plugins/rainlab/notify/formwidgets/actionbuilder/partials/_create_action_form.htm
@@ -0,0 +1,46 @@
+= Form::open(['id' => 'addRuleActionForm']) ?>
+
+
+
+
+
+
+ fatalError): ?>
+
= $fatalError ?>
+
+
+
+
+
+
+
+
+
+= Form::close() ?>
diff --git a/plugins/rainlab/notify/formwidgets/actionbuilder/partials/_schedule.htm b/plugins/rainlab/notify/formwidgets/actionbuilder/partials/_schedule.htm
new file mode 100644
index 00000000..d011fdef
--- /dev/null
+++ b/plugins/rainlab/notify/formwidgets/actionbuilder/partials/_schedule.htm
@@ -0,0 +1,22 @@
+
diff --git a/plugins/rainlab/notify/formwidgets/conditionbuilder/assets/css/conditions.css b/plugins/rainlab/notify/formwidgets/conditionbuilder/assets/css/conditions.css
new file mode 100644
index 00000000..84cba645
--- /dev/null
+++ b/plugins/rainlab/notify/formwidgets/conditionbuilder/assets/css/conditions.css
@@ -0,0 +1,227 @@
+ul.condition-set {
+ padding: 5px 0 0;
+ margin: 0;
+ list-style: none;
+}
+ul.condition-set ul {
+ padding: 0;
+ margin: 0;
+ list-style: none;
+}
+ul.condition-set li {
+ margin: 0;
+ padding: 0;
+}
+ul.condition-set .condition-item {
+ border-radius: 4px;
+}
+ul.condition-set .condition-item.is-root {
+ border-radius: 0;
+}
+ul.condition-set .condition-item.collapsed .compound-content {
+ border-left-color: transparent !important;
+}
+ul.condition-set .condition-item.collapsed .condition-set {
+ display: none;
+}
+ul.condition-set .condition-item.collapsed.is-inner .compound-add-item {
+ display: none;
+}
+ul.condition-set .condition-item .condition-delete {
+ position: absolute;
+ display: block;
+ width: 10px;
+ height: 10px;
+ right: 7px;
+ top: 7px;
+}
+ul.condition-set a.condition-collapse {
+ text-decoration: none;
+ display: block;
+ position: absolute;
+ right: 8px;
+ top: 2px;
+}
+ul.condition-set a.condition-collapse > i {
+ -webkit-transition: transform 0.3s;
+ transition: transform 0.3s;
+ width: 15px;
+ height: 15px;
+ color: #bdc3c7;
+ font-size: 12px;
+ display: block;
+}
+ul.condition-set a.condition-collapse:hover > i {
+ color: #999;
+}
+ul.condition-set li.collapsed a.condition-collapse > i {
+ -webkit-transform: scale(1, -1);
+ -ms-transform: scale(1, -1);
+ transform: scale(1, -1);
+}
+ul.condition-set .compound-join {
+ text-align: center;
+ padding: 3px 0 2px 0;
+}
+ul.condition-set .compound-join a {
+ color: #333333;
+ text-decoration: none;
+}
+ul.condition-set .compound-join a:hover {
+ text-decoration: underline;
+}
+ul.condition-set .condition-item.is-single {
+ padding: 8px 20px 9px 24px;
+ background: #fff;
+ border: 1px solid #eee;
+ position: relative;
+}
+ul.condition-set .condition-item.is-single:before {
+ font-family: FontAwesome;
+ font-weight: normal;
+ font-style: normal;
+ text-decoration: inherit;
+ -webkit-font-smoothing: antialiased;
+ *margin-right: .3em;
+ content: "\f126";
+ line-height: 100%;
+ font-size: 16px;
+ color: #bdc3c7;
+ position: absolute;
+ width: 15px;
+ height: 15px;
+ left: 12px;
+ top: 11px;
+}
+ul.condition-set .condition-item.is-single .condition-text {
+ padding-left: 10px;
+}
+ul.condition-set .condition-item.is-single .condition-text a {
+ color: #333333;
+ text-decoration: none;
+}
+ul.condition-set .condition-item.is-single .condition-text a:hover {
+ text-decoration: underline;
+}
+ul.condition-set .condition-item.is-single .condition-text a span.operator {
+ font-weight: 500;
+}
+ul.condition-set .condition-item.is-single a.condition-delete {
+ right: 12px;
+ top: 9px;
+}
+ul.condition-set .condition-item.is-compound .compound-content {
+ position: relative;
+ border-left: 1px solid #dbdee0;
+ margin-left: 5px;
+}
+ul.condition-set .condition-item.is-compound .compound-content:before {
+ color: #bdc3c7;
+ font-family: FontAwesome;
+ font-weight: normal;
+ font-style: normal;
+ text-decoration: inherit;
+ -webkit-font-smoothing: antialiased;
+ *margin-right: .3em;
+ content: "\f111";
+ font-size: 8px;
+ position: absolute;
+ left: -4px;
+ top: -2px;
+}
+ul.condition-set .condition-item.is-compound .compound-content h4 {
+ padding: 0 30px 5px 5px;
+ margin: 0;
+ position: relative;
+ top: -5px;
+ font-size: 13px;
+ line-height: 130%;
+ font-weight: 400;
+ color: #666;
+}
+ul.condition-set .condition-item.is-compound .compound-content h4 a.condition-text {
+ color: #333333;
+ padding-left: 10px;
+}
+ul.condition-set .condition-item.is-compound .compound-content h4 a.condition-text:hover {
+ text-decoration: underline;
+}
+ul.condition-set .condition-item.is-compound .compound-content .condition-set {
+ padding-left: 15px;
+}
+ul.condition-set .condition-item.is-compound .compound-add-item {
+ position: relative;
+ margin-top: 10px;
+ margin-left: 20px;
+ border: 2px dotted #e0e0e0;
+ border-radius: 5px;
+}
+ul.condition-set .condition-item.is-compound .compound-add-item:before {
+ color: #bdc3c7;
+ font-family: FontAwesome;
+ font-weight: normal;
+ font-style: normal;
+ text-decoration: inherit;
+ -webkit-font-smoothing: antialiased;
+ *margin-right: .3em;
+ content: "\f067";
+ font-size: 16px;
+ position: absolute;
+ left: -23px;
+ top: -11px;
+}
+ul.condition-set .condition-item.is-compound .compound-add-item > a {
+ color: #bdc3c7;
+ text-align: center;
+ display: block;
+ text-decoration: none;
+ padding: 13px 15px;
+ text-transform: uppercase;
+ font-weight: 600;
+ font-size: 12px;
+}
+ul.condition-set .condition-item.is-compound .compound-add-item:hover,
+ul.condition-set .condition-item.is-compound .compound-add-item:focus {
+ background-color: #4ea5e0;
+ border-color: #4ea5e0;
+}
+ul.condition-set .condition-item.is-compound .compound-add-item:hover:before,
+ul.condition-set .condition-item.is-compound .compound-add-item:focus:before {
+ color: #999;
+}
+ul.condition-set .condition-item.is-compound .compound-add-item:hover > a,
+ul.condition-set .condition-item.is-compound .compound-add-item:focus > a {
+ color: #ffffff;
+}
+ul.condition-set .condition-item.is-compound .compound-add-item:active {
+ background: #3498db;
+ border-color: #3498db;
+}
+ul.condition-set .condition-item.is-compound .compound-add-item:active > a {
+ color: #ffffff;
+}
+ul.condition-set .condition-item.is-compound.is-inner {
+ padding-right: 20px;
+}
+ul.condition-set .condition-item.is-compound.is-inner a.condition-delete-compound {
+ right: -7px;
+ top: 0;
+}
+.condition-multi-value .condition-filter-search {
+ background-position: right -81px !important;
+ border-top: none !important;
+ border-left: none !important;
+ border-right: none !important;
+ border-radius: 0;
+ padding-left: 20px;
+}
+.condition-multi-value .filter-list {
+ margin: 0 -20px;
+}
+.condition-multi-value .filter-list .list-footer {
+ padding-top: 0;
+ padding-bottom: 0;
+}
+.condition-multi-value .added-filter-list {
+ margin: 0 -20px;
+}
diff --git a/plugins/rainlab/notify/formwidgets/conditionbuilder/assets/js/conditions.js b/plugins/rainlab/notify/formwidgets/conditionbuilder/assets/js/conditions.js
new file mode 100644
index 00000000..e4a7379c
--- /dev/null
+++ b/plugins/rainlab/notify/formwidgets/conditionbuilder/assets/js/conditions.js
@@ -0,0 +1,184 @@
+/*
+ * Global helpers
+ */
+function showConditionSettings(id) {
+ var $control = $('[data-condition-id='+id+']').closest('[data-control="ruleconditions"]')
+
+ $control.ruleConditions('onShowNewConditionSettings', id)
+}
+
+/*
+ * Plugin definition
+ */
++function ($) { "use strict";
+ var Base = $.oc.foundation.base,
+ BaseProto = Base.prototype
+
+ var RuleConditions = function (element, options) {
+ this.$el = $(element)
+ this.options = options || {}
+
+ $.oc.foundation.controlUtils.markDisposable(element)
+ Base.call(this)
+ this.init()
+ }
+
+ RuleConditions.prototype = Object.create(BaseProto)
+ RuleConditions.prototype.constructor = RuleConditions
+
+ RuleConditions.prototype.init = function() {
+ this.$el.on('click', '[data-conditions-collapse]', this.proxy(this.onConditionsToggle))
+ this.$el.on('click', '[data-conditions-settings]', this.proxy(this.onShowSettings))
+ this.$el.on('click', '[data-conditions-create]', this.proxy(this.onCreateChildCondition))
+ this.$el.on('click', '[data-conditions-delete]', this.proxy(this.onDeleteCondition))
+ this.$el.one('dispose-control', this.proxy(this.dispose))
+ }
+
+ RuleConditions.prototype.dispose = function() {
+ this.$el.off('click', '[data-conditions-collapse]', this.proxy(this.onConditionsToggle))
+ this.$el.off('click', '[data-conditions-settings]', this.proxy(this.onShowSettings))
+ this.$el.off('click', '[data-conditions-create]', this.proxy(this.onCreateChildCondition))
+ this.$el.off('click', '[data-conditions-delete]', this.proxy(this.onDeleteCondition))
+ this.$el.off('dispose-control', this.proxy(this.dispose))
+ this.$el.removeData('oc.ruleConditions')
+
+ this.$el = null
+
+ // In some cases options could contain callbacks,
+ // so it's better to clean them up too.
+ this.options = null
+
+ BaseProto.dispose.call(this)
+ }
+
+ RuleConditions.prototype.onDeleteCondition = function(event) {
+ var $el = $(event.target),
+ conditionId = getConditionIdFromElement($el)
+
+ $el.request(this.options.deleteHandler, {
+ data: { current_condition_id: conditionId },
+ confirm: 'Do you really want to delete this condition?'
+ })
+ }
+
+ RuleConditions.prototype.onCreateChildCondition = function(event) {
+ var $el = $(event.target),
+ conditionId = getConditionIdFromElement($el)
+
+ $el.popup({
+ handler: this.options.createHandler,
+ extraData: { current_condition_id: conditionId },
+ size: 'large'
+ })
+
+ return false
+ }
+
+ RuleConditions.prototype.onConditionsToggle = function(event) {
+ var $el = $(event.target),
+ $item = $el.closest('li'),
+ newStatusValue = $item.hasClass('collapsed') ? 0 : 1,
+ conditionId = getConditionIdFromElement($el)
+
+ $el.request(this.options.collapseHandler, {
+ data: {
+ status: newStatusValue,
+ group: conditionId
+ }
+ })
+
+ if (newStatusValue) {
+ $el.parents('li:first').addClass('collapsed');
+ }
+ else {
+ $el.parents('li:first').removeClass('collapsed');
+ }
+
+ return false
+ }
+
+ RuleConditions.prototype.onShowNewConditionSettings = function(conditionId) {
+ var $el = $('[data-condition-id='+conditionId+']')
+
+ $el.popup({
+ handler: this.options.settingsHandler,
+ extraData: { current_condition_id: conditionId },
+ size: 'large'
+ })
+
+ // This will not fire on successful save because the target element
+ // is replaced by the time the popup loader has finished to call it
+ $el.one('hide.oc.popup', this.proxy(this.onCancelCondition))
+ }
+
+ RuleConditions.prototype.onCancelCondition = function(event) {
+ var $el = $(event.target),
+ conditionId = getConditionIdFromElement($el)
+
+ $el.request(this.options.cancelHandler, {
+ data: { new_condition_id: conditionId }
+ })
+
+ return false
+ }
+
+ RuleConditions.prototype.onShowSettings = function(event) {
+ var $el = $(event.target),
+ conditionId = getConditionIdFromElement($el)
+
+ $el.popup({
+ handler: this.options.settingsHandler,
+ extraData: { current_condition_id: conditionId },
+ size: 'large'
+ })
+
+ return false
+ }
+
+ function getConditionIdFromElement($el) {
+ var $item = $el.closest('li.condition-item')
+
+ return $item.data('condition-id')
+ }
+
+ RuleConditions.DEFAULTS = {
+ collapseHandler: null,
+ settingsHandler: null,
+ deleteHandler: null,
+ cancelHandler: null,
+ createHandler: null
+ }
+
+ // PLUGIN DEFINITION
+ // ============================
+
+ var old = $.fn.ruleConditions
+
+ $.fn.ruleConditions = function (option) {
+ var args = Array.prototype.slice.call(arguments, 1), items, result
+
+ items = this.each(function () {
+ var $this = $(this)
+ var data = $this.data('oc.ruleConditions')
+ var options = $.extend({}, RuleConditions.DEFAULTS, $this.data(), typeof option == 'object' && option)
+ if (!data) $this.data('oc.ruleConditions', (data = new RuleConditions(this, options)))
+ if (typeof option == 'string') result = data[option].apply(data, args)
+ if (typeof result != 'undefined') return false
+ })
+
+ return result ? result : items
+ }
+
+ $.fn.ruleConditions.Constructor = RuleConditions
+
+ $.fn.ruleConditions.noConflict = function () {
+ $.fn.ruleConditions = old
+ return this
+ }
+
+ // Add this only if required
+ $(document).render(function (){
+ $('[data-control="ruleconditions"]').ruleConditions()
+ })
+
+}(window.jQuery);
diff --git a/plugins/rainlab/notify/formwidgets/conditionbuilder/assets/js/conditions.multivalue.js b/plugins/rainlab/notify/formwidgets/conditionbuilder/assets/js/conditions.multivalue.js
new file mode 100644
index 00000000..f87e2f8c
--- /dev/null
+++ b/plugins/rainlab/notify/formwidgets/conditionbuilder/assets/js/conditions.multivalue.js
@@ -0,0 +1,139 @@
+/*
+ * Plugin definition
+ */
++function ($) { "use strict";
+ var Base = $.oc.foundation.base,
+ BaseProto = Base.prototype
+
+ var ConditionMultiValue = function (element, options) {
+ this.$el = $(element)
+ this.options = options || {}
+ this.$template = $('[data-record-template]', this.$el)
+ this.$emptyTemplate = $('[data-empty-record-template]', this.$el)
+ this.$addedRecords = $('[data-added-records]', this.$el)
+
+ $.oc.foundation.controlUtils.markDisposable(element)
+ Base.call(this)
+
+ this.init()
+ this.recompileData()
+ }
+
+ ConditionMultiValue.prototype = Object.create(BaseProto)
+ ConditionMultiValue.prototype.constructor = ConditionMultiValue
+
+ ConditionMultiValue.prototype.init = function() {
+ this.$el.on('click', '[data-multivalue-add-record]', this.proxy(this.onAddRecord))
+ this.$el.on('click', '[data-multivalue-remove-record]', this.proxy(this.onRemoveRecord))
+ this.$el.one('dispose-control', this.proxy(this.dispose))
+ }
+
+ ConditionMultiValue.prototype.dispose = function() {
+ this.$el.off('click', '[data-multivalue-add-record]', this.proxy(this.onAddRecord))
+ this.$el.off('click', '[data-multivalue-remove-record]', this.proxy(this.onRemoveRecord))
+ this.$el.off('dispose-control', this.proxy(this.dispose))
+ this.$el.removeData('oc.conditionMultiValue')
+
+ this.$el = null
+
+ // In some cases options could contain callbacks,
+ // so it's better to clean them up too.
+ this.options = null
+
+ BaseProto.dispose.call(this)
+ }
+
+ ConditionMultiValue.prototype.onAddRecord = function(event) {
+ var $el = $(event.target),
+ recordId = $el.closest('[data-record-key]').data('record-key'),
+ recordValue = $el.closest('a').data('record-value')
+
+ if (!!$('[data-record-key='+recordId+']', this.$addedRecords).length) {
+ return
+ }
+
+ this.$addedRecords.append(this.renderTemplate({
+ key: recordId,
+ value: recordValue
+ }))
+
+ this.recompileData()
+ }
+
+ ConditionMultiValue.prototype.onRemoveRecord = function(event) {
+ var $el = $(event.target),
+ recordId = $el.closest('[data-record-key]').data('record-key')
+
+ $('[data-record-key='+recordId+']', this.$addedRecords).remove()
+
+ this.recompileData()
+ }
+
+ ConditionMultiValue.prototype.recompileData = function(params) {
+ var $recordElements = $('[data-record-key]', this.$addedRecords),
+ hasData = !!$recordElements.length
+
+ if (hasData) {
+ $('[data-no-record-data]', this.$addedRecords).remove()
+ }
+ else {
+ this.$addedRecords.append(this.renderEmptyTemplate())
+ }
+
+ if (this.options.dataLocker) {
+ var $locker = $(this.options.dataLocker),
+ selectedIds = []
+
+ $recordElements.each(function(key, record) {
+ selectedIds.push($(record).data('record-key'))
+ })
+
+ $locker.val(selectedIds.join(','))
+ }
+ }
+
+ ConditionMultiValue.prototype.renderTemplate = function(params) {
+ return Mustache.render(this.$template.html(), params)
+ }
+
+ ConditionMultiValue.prototype.renderEmptyTemplate = function() {
+ return this.$emptyTemplate.html()
+ }
+
+ ConditionMultiValue.DEFAULTS = {
+ dataLocker: null
+ }
+
+ // PLUGIN DEFINITION
+ // ============================
+
+ var old = $.fn.conditionMultiValue
+
+ $.fn.conditionMultiValue = function (option) {
+ var args = Array.prototype.slice.call(arguments, 1), items, result
+
+ items = this.each(function () {
+ var $this = $(this)
+ var data = $this.data('oc.conditionMultiValue')
+ var options = $.extend({}, ConditionMultiValue.DEFAULTS, $this.data(), typeof option == 'object' && option)
+ if (!data) $this.data('oc.conditionMultiValue', (data = new ConditionMultiValue(this, options)))
+ if (typeof option == 'string') result = data[option].apply(data, args)
+ if (typeof result != 'undefined') return false
+ })
+
+ return result ? result : items
+ }
+
+ $.fn.conditionMultiValue.Constructor = ConditionMultiValue
+
+ $.fn.conditionMultiValue.noConflict = function () {
+ $.fn.conditionMultiValue = old
+ return this
+ }
+
+ // Add this only if required
+ $(document).render(function (){
+ $('[data-control="condition-multivalue"]').conditionMultiValue()
+ })
+
+}(window.jQuery);
diff --git a/plugins/rainlab/notify/formwidgets/conditionbuilder/assets/less/conditions.less b/plugins/rainlab/notify/formwidgets/conditionbuilder/assets/less/conditions.less
new file mode 100644
index 00000000..e406b208
--- /dev/null
+++ b/plugins/rainlab/notify/formwidgets/conditionbuilder/assets/less/conditions.less
@@ -0,0 +1,286 @@
+@import "../../../../../../../modules/backend/assets/less/core/boot.less";
+
+//
+// Conditions
+// --------------------------------------------------
+
+ul.condition-set {
+ padding: 5px 0 0;
+ margin: 0;
+ list-style: none;
+
+ ul {
+ padding: 0;
+ margin: 0;
+ list-style: none;
+ }
+
+ li {
+ margin: 0;
+ padding: 0;
+ }
+
+ .condition-item {
+ border-radius: 4px;
+
+ &.is-root {
+ border-radius: 0;
+ }
+
+ &.is-inner {}
+
+ &.collapsed {
+ .compound-content {
+ border-left-color: transparent !important;
+ }
+
+ .condition-set {
+ display: none;
+ }
+
+ &.is-inner .compound-add-item {
+ display: none;
+ }
+ }
+
+ .condition-delete {
+ position: absolute;
+ display: block;
+ width: 10px;
+ height: 10px;
+ right: 7px;
+ top: 7px;
+ }
+ }
+
+ //
+ // Collapse
+ //
+
+ a.condition-collapse {
+ text-decoration: none;
+ display: block;
+ position: absolute;
+ right: 8px;
+ top: 2px;
+
+ > i {
+ .transition(~'transform 0.3s');
+ width: 15px;
+ height: 15px;
+ color: #bdc3c7;
+ font-size: 12px;
+ display: block;
+ }
+
+ &:hover {
+ > i {
+ color: #999;
+ }
+ }
+ }
+
+ li.collapsed a.condition-collapse {
+ > i {
+ .transform(scale(1,-1));
+ }
+ }
+
+ //
+ // Joins
+ //
+
+ .compound-join {
+ text-align: center;
+ padding: 3px 0 2px 0;
+ a {
+ color: #333333;
+ text-decoration: none;
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+ }
+
+ //
+ // Single
+ //
+
+ .condition-item.is-single {
+ padding: 8px 20px 9px 24px;
+ background: #fff;
+ border: 1px solid #eee;
+ position: relative;
+
+ &:before {
+ .icon(@code-fork);
+ line-height: 100%;
+ font-size: 16px;
+ color: #bdc3c7;
+
+ position: absolute;
+ width: 15px;
+ height: 15px;
+ left: 12px;
+ top: 11px;
+ }
+
+ .condition-text {
+ padding-left: 10px;
+
+ a {
+ color: #333333;
+ text-decoration: none;
+ &:hover {
+ text-decoration: underline;
+ }
+
+ span.operator {
+ font-weight: 500;
+ }
+ }
+ }
+
+ a.condition-delete {
+ right: 12px;
+ top: 9px;
+ }
+ }
+
+ //
+ // Compound
+ //
+
+ .condition-item.is-compound {
+ .compound-content {
+ position: relative;
+ border-left: 1px solid #dbdee0;
+ margin-left: 5px;
+
+ &:before {
+ color: #bdc3c7;
+ .icon(@circle);
+ font-size: 8px;
+ position: absolute;
+ left: -4px;
+ top: -2px;
+ }
+
+ h4 {
+ padding: 0 30px 5px 5px;
+ margin: 0;
+ position: relative;
+ top: -5px;
+ font-size: 13px;
+ line-height: 130%;
+ font-weight: 400;
+ color: #666;
+ a.condition-text {
+ color: #333333;
+ padding-left: 10px;
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+ }
+
+ .condition-set {
+ padding-left: 15px;
+ }
+ }
+
+ .condition-item.is-compound {
+
+ }
+
+ .compound-add-item {
+ position: relative;
+ margin-top: 10px;
+ margin-left: 20px;
+ border: 2px dotted #e0e0e0;
+ border-radius: 5px;
+
+ &:before {
+ color: #bdc3c7;
+ .icon(@plus);
+ font-size: 16px;
+ position: absolute;
+ left: -23px;
+ top: -11px;
+ }
+
+ > a {
+ color: #bdc3c7;
+ text-align: center;
+ display: block;
+ text-decoration: none;
+ padding: 13px 15px;
+ text-transform: uppercase;
+ font-weight: 600;
+ font-size: @font-size-base - 2;
+ }
+
+ &:hover, &:focus {
+ background-color: @highlight-hover-bg;
+ border-color: @highlight-hover-bg;
+
+ &:before {
+ color: #999;
+ }
+
+ > a {
+ color: @highlight-hover-text;
+ }
+ }
+
+ &:active {
+ background: @highlight-active-bg;
+ border-color: @highlight-active-bg;
+ > a {
+ color: @highlight-active-text;
+ }
+ }
+ }
+ }
+
+ //
+ // Compound (Inner)
+ //
+
+ .condition-item.is-compound.is-inner {
+ padding-right: 20px;
+
+ a.condition-delete-compound {
+ right: -7px;
+ top: 0;
+ }
+ }
+}
+
+//
+// Multi value
+//
+
+.condition-multi-value {
+ .condition-filter-search {
+ background-position: right -81px !important;
+ border-top: none !important;
+ border-left: none !important;
+ border-right: none !important;
+ border-radius: 0;
+ padding-left: 20px;
+ }
+
+ .filter-list {
+ margin: 0 -20px;
+
+ .list-footer {
+ padding-top: 0;
+ padding-bottom: 0;
+ }
+ }
+
+ .added-filter-list {
+ margin: 0 -20px;
+ }
+}
diff --git a/plugins/rainlab/notify/formwidgets/conditionbuilder/partials/_condition.htm b/plugins/rainlab/notify/formwidgets/conditionbuilder/partials/_condition.htm
new file mode 100644
index 00000000..95bb61d2
--- /dev/null
+++ b/plugins/rainlab/notify/formwidgets/conditionbuilder/partials/_condition.htm
@@ -0,0 +1,99 @@
+isCompound();
+ $isRoot = $treeLevel == 0;
+ $collapsed = $this->getCollapseStatus($condition->id, false);
+?>
+
+
+
+
+
+
+
+ children()->withDeferred($this->sessionKey.'_'.$condition->id)->get();
+ $lastIndex = $children->count() - 1;
+ ?>
+ $childCondition): ?>
+ = $this->makePartial('condition', [
+ 'condition' => $childCondition,
+ 'treeLevel' => $treeLevel + 1,
+ 'parentCondition' => $condition
+ ]) ?>
+
+
+
+ = e($this->getCacheConditionJoinText($condition)) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ×
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/rainlab/notify/formwidgets/conditionbuilder/partials/_condition_settings_form.htm b/plugins/rainlab/notify/formwidgets/conditionbuilder/partials/_condition_settings_form.htm
new file mode 100644
index 00000000..77e0d843
--- /dev/null
+++ b/plugins/rainlab/notify/formwidgets/conditionbuilder/partials/_condition_settings_form.htm
@@ -0,0 +1,68 @@
+= Form::open(['id' => 'propertyForm']) ?>
+
+
+
+ fatalError): ?>
+
+
+
+
+
+
+ = $conditionFormWidget->render() ?>
+
+
+
+
+
+
+
= e(trans($this->fatalError)) ?>
+
+
+
+
+
+
diff --git a/plugins/rainlab/notify/formwidgets/conditionbuilder/partials/_conditions.htm b/plugins/rainlab/notify/formwidgets/conditionbuilder/partials/_conditions.htm
new file mode 100644
index 00000000..8cc8c4bb
--- /dev/null
+++ b/plugins/rainlab/notify/formwidgets/conditionbuilder/partials/_conditions.htm
@@ -0,0 +1,10 @@
+
+ = $this->makePartial('condition', [
+ 'condition' => $condition,
+ 'treeLevel' => 0
+ ]) ?>
+
+
+
+
+
diff --git a/plugins/rainlab/notify/formwidgets/conditionbuilder/partials/_conditions_container.htm b/plugins/rainlab/notify/formwidgets/conditionbuilder/partials/_conditions_container.htm
new file mode 100644
index 00000000..737b072a
--- /dev/null
+++ b/plugins/rainlab/notify/formwidgets/conditionbuilder/partials/_conditions_container.htm
@@ -0,0 +1,15 @@
+
+
+ = $this->makePartial('conditions', ['condition' => $rootCondition]) ?>
+
+
+
+
diff --git a/plugins/rainlab/notify/formwidgets/conditionbuilder/partials/_create_child_form.htm b/plugins/rainlab/notify/formwidgets/conditionbuilder/partials/_create_child_form.htm
new file mode 100644
index 00000000..3e9e8635
--- /dev/null
+++ b/plugins/rainlab/notify/formwidgets/conditionbuilder/partials/_create_child_form.htm
@@ -0,0 +1,67 @@
+= Form::open(['id' => 'propertyForm']) ?>
+
+
+
+ fatalError): ?>
+
+
+
+
+
+
+ = e(__('Condition')) ?>
+
+
+ $class): ?>
+
+ = e($name) ?>
+
+
+ $subClass): ?>
+ = e($subName) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
= e(trans($this->fatalError)) ?>
+
+
+
+
+
+= Form::close() ?>
diff --git a/plugins/rainlab/notify/interfaces/Action.php b/plugins/rainlab/notify/interfaces/Action.php
new file mode 100644
index 00000000..011cc13e
--- /dev/null
+++ b/plugins/rainlab/notify/interfaces/Action.php
@@ -0,0 +1,31 @@
+ [],
+ ];
+
+ /**
+ * Mark the notification as read.
+ *
+ * @return void
+ */
+ public function markAsRead()
+ {
+ if (is_null($this->read_at)) {
+ $this->forceFill(['read_at' => $this->freshTimestamp()])->save();
+ }
+ }
+
+ /**
+ * Determine if a notification has been read.
+ *
+ * @return bool
+ */
+ public function read()
+ {
+ return $this->read_at !== null;
+ }
+
+ /**
+ * Determine if a notification has not been read.
+ *
+ * @return bool
+ */
+ public function unread()
+ {
+ return $this->read_at === null;
+ }
+
+ /**
+ * Get the entity's unread notifications.
+ */
+ public function scopeApplyUnread($query)
+ {
+ return $query->whereNull('read_at');
+ }
+
+ /**
+ * Get the entity's read notifications.
+ */
+ public function scopeApplyRead($query)
+ {
+ return $query->whereNotNull('read_at');
+ }
+
+ /**
+ * Get the parsed body of the announcement.
+ *
+ * @return string
+ */
+ public function getParsedBodyAttribute()
+ {
+ return Markdown::parse($this->body);
+ }
+}
diff --git a/plugins/rainlab/notify/models/NotificationRule.php b/plugins/rainlab/notify/models/NotificationRule.php
new file mode 100644
index 00000000..8db57a4d
--- /dev/null
+++ b/plugins/rainlab/notify/models/NotificationRule.php
@@ -0,0 +1,289 @@
+ 'required'
+ ];
+
+ /**
+ * @var array Relations
+ */
+ public $hasMany = [
+ 'rule_conditions' => [
+ RuleCondition::class,
+ 'key' => 'rule_host_id',
+ 'conditions' => 'rule_parent_id is null',
+ 'delete' => true
+ ],
+ 'rule_actions' => [
+ RuleAction::class,
+ 'key' => 'rule_host_id',
+ 'delete' => true
+ ],
+ ];
+
+ /**
+ * Kicks off this notification rule, fires the event to obtain its parameters,
+ * checks the rule conditions evaluate as true, then spins over each action.
+ */
+ public function triggerRule()
+ {
+ $params = $this->getEventObject()->getParams();
+ $rootCondition = $this->rule_conditions->first();
+
+ if ($rootCondition && !$rootCondition->getConditionObject()->isTrue($params)) {
+ return false;
+ }
+
+ foreach ($this->rule_actions as $action) {
+ $action->setRelation('notification_rule', $this);
+ $action->triggerAction($params);
+ }
+ }
+
+ /**
+ * Returns extra conditions provided by the event.
+ * @return array
+ */
+ public function getExtraConditionRules()
+ {
+ $rules = [];
+
+ $classes = $this->getEventObject()->defineConditions();
+
+ foreach ($classes as $class) {
+ $rules[$class] = new $class;
+ }
+
+ return $rules;
+ }
+
+ /**
+ * Extends this class with the event class
+ * @param string $class Class name
+ * @return boolean
+ */
+ public function applyEventClass($class = null)
+ {
+ if (!$class) {
+ $class = $this->class_name;
+ }
+
+ if (!$class) {
+ return false;
+ }
+
+ if (!$this->isClassExtendedWith($class)) {
+ $this->extendClassWith($class);
+ }
+
+ $this->class_name = $class;
+ $this->event_name = Lang::get(array_get($this->eventDetails(), 'name', 'Unknown'));
+ return true;
+ }
+
+ /**
+ * Returns the event class extension object.
+ * @return \RainLab\Notify\Classes\NotificationEvent
+ */
+ public function getEventObject()
+ {
+ $this->applyEventClass();
+
+ return $this->asExtension($this->getEventClass());
+ }
+
+ public function getEventClass()
+ {
+ return $this->class_name;
+ }
+
+ //
+ // Events
+ //
+
+ public function afterFetch()
+ {
+ $this->applyEventClass();
+ }
+
+ public function beforeValidate()
+ {
+ if (!$this->applyEventClass()) {
+ return;
+ }
+ }
+
+ //
+ // Scopes
+ //
+
+ public function scopeApplyEnabled($query)
+ {
+ return $query->where('is_enabled', true);
+ }
+
+ public function scopeApplyClass($query, $class)
+ {
+ if (!is_string($class)) {
+ $class = get_class($class);
+ }
+
+ return $query->where('class_name', $class);
+ }
+
+ //
+ // Presets
+ //
+
+ /**
+ * Returns an array of rule codes and descriptions.
+ * @return array
+ */
+ public static function listRulesForEvent($eventClass)
+ {
+ $results = [];
+
+ $dbRules = self::applyClass($eventClass)->get();
+ $presets = (array) EventBase::findEventPresetsByClass($eventClass);
+
+ foreach ($dbRules as $dbRule) {
+ if ($dbRule->code) {
+ unset($presets[$dbRule->code]);
+ }
+
+ if ($dbRule->is_enabled) {
+ $results[] = $dbRule;
+ }
+ }
+
+ foreach ($presets as $code => $preset) {
+ if ($newPreset = self::createFromPreset($code, $preset)) {
+ $results[] = $newPreset;
+ }
+ }
+
+ return $results;
+ }
+
+ /**
+ * Syncronise all file-based presets to the database.
+ * @return void
+ */
+ public static function syncAll()
+ {
+ $presets = (array) EventBase::findEventPresets();
+ $dbRules = self::where('code', '!=', '')->whereNotNull('code')->lists('is_custom', 'code');
+ $newRules = array_diff_key($presets, $dbRules);
+
+ /*
+ * Clean up non-customized templates
+ */
+ foreach ($dbRules as $code => $isCustom) {
+ if ($isCustom) {
+ continue;
+ }
+
+ if (!array_key_exists($code, $presets) && ($record = self::whereCode($code)->first())) {
+ $record->delete();
+ }
+ }
+
+ /*
+ * Create new rules
+ */
+ foreach ($newRules as $code => $preset) {
+ self::createFromPreset($code, $preset);
+ }
+ }
+
+ public static function createFromPreset($code, $preset)
+ {
+ $actions = array_get($preset, 'items');
+ if (!$actions || !is_array($actions)) {
+ return;
+ }
+
+ $newRule = new self;
+ $newRule->code = $code;
+ $newRule->is_enabled = 1;
+ $newRule->is_custom = 0;
+ $newRule->name = array_get($preset, 'name');
+ $newRule->class_name = array_get($preset, 'event');
+ $newRule->forceSave();
+
+ // Add the actions
+ foreach ($actions as $action) {
+ $params = array_except($action, 'action');
+
+ $newAction = new RuleAction;
+ $newAction->class_name = array_get($action, 'action');
+ $newAction->notification_rule = $newRule;
+ $newAction->fill($params);
+ $newAction->forceSave();
+ }
+
+ // Add the conditions
+ $conditions = array_get($preset, 'conditions');
+ if (!$conditions || !is_array($conditions)) {
+ return $newRule;
+ }
+
+ // Create the root condition
+ $rootCondition = new RuleCondition();
+ $rootCondition->rule_host_type = ConditionBase::TYPE_ANY;
+ $rootCondition->class_name = $rootCondition->getRootConditionClass();
+ $rootCondition->notification_rule = $newRule;
+ $rootCondition->save();
+
+ // Add the sub conditions
+ foreach ($conditions as $condition) {
+ $params = array_except($condition, 'condition');
+ $newCondition = new RuleCondition();
+ $newCondition->class_name = array_get($condition, 'condition');
+ $newCondition->parent = $rootCondition;
+ $newCondition->fill($params);
+ $newCondition->forceSave();
+ }
+
+ return $newRule;
+ }
+}
diff --git a/plugins/rainlab/notify/models/RuleAction.php b/plugins/rainlab/notify/models/RuleAction.php
new file mode 100644
index 00000000..7d4b42c3
--- /dev/null
+++ b/plugins/rainlab/notify/models/RuleAction.php
@@ -0,0 +1,201 @@
+ [NotificationRule::class, 'key' => 'rule_host_id'],
+ ];
+
+ public function triggerAction($params, $scheduled = true)
+ {
+ try {
+ $actionObject = $this->getActionObject();
+
+ // Check if action is muted in which case we don't proceed sending a notification
+ if (method_exists($actionObject, 'isMuted') && $actionObject->isMuted()) {
+ return;
+ }
+
+ // Apply action schedule
+ if ($scheduled && $schedule = $this->getSchedule()) {
+ // We delay the execution using Queues. When dequeued
+ // ScheduledAction will call this triggerAction
+ // function with $scheduled=false
+ Queue::later($schedule, new ScheduledAction($this, $params));
+ }
+ else {
+ // We trigger the action
+ $actionObject->triggerAction($params);
+ }
+ }
+ catch (Exception $ex) {
+ // We could log the error here, for now we should suppress
+ // any exceptions to let other actions proceed as normal
+ traceLog('Error with ' . $this->getActionClass());
+ traceLog($ex);
+ }
+ }
+
+ /**
+ * Extends this model with the action class
+ * @param string $class Class name
+ * @return boolean
+ */
+ public function applyActionClass($class = null)
+ {
+ if (!$class) {
+ $class = $this->class_name;
+ }
+
+ if (!$class) {
+ return false;
+ }
+
+ if (!$this->isClassExtendedWith($class)) {
+ $this->extendClassWith($class);
+ }
+
+ $this->class_name = $class;
+ return true;
+ }
+
+ public function beforeSave()
+ {
+ $this->setCustomData();
+ }
+
+ public function afterSave()
+ {
+ // Make sure that this record is removed from the DB after being removed from a rule
+ $removedFromRule = $this->rule_host_id === null && $this->getOriginal('rule_host_id');
+ if ($removedFromRule && !$this->notification_rule()->withDeferred(post('_session_key'))->exists()) {
+ $this->delete();
+ }
+ }
+
+ public function applyCustomData()
+ {
+ $this->setCustomData();
+ $this->loadCustomData();
+ }
+
+ protected function loadCustomData()
+ {
+ $this->setRawAttributes((array) $this->getAttributes() + (array) $this->config_data, true);
+ }
+
+ protected function setCustomData()
+ {
+ if (!$actionObj = $this->getActionObject()) {
+ throw new SystemException(sprintf('Unable to find action object [%s]', $this->getActionClass()));
+ }
+
+ /*
+ * Spin over each field and add it to config_data
+ */
+
+ $metaAttributes = [
+ 'action_text',
+ 'schedule_type',
+ 'schedule_delay',
+ 'schedule_delay_factor',
+ ];
+
+ $formAttributes = [];
+ $config = $actionObj->getFieldConfig();
+ if (isset($config->fields)) {
+ $formAttributes = array_keys($config->fields);
+ }
+
+ $fieldAttributes = array_merge($formAttributes, $metaAttributes);
+
+ $dynamicAttributes = array_only($this->getAttributes(), $fieldAttributes);
+ $this->config_data = $dynamicAttributes;
+
+ $this->setRawAttributes(array_except($this->getAttributes(), $fieldAttributes));
+ }
+
+ public function afterFetch()
+ {
+ $this->applyActionClass();
+ $this->loadCustomData();
+ }
+
+ public function getText()
+ {
+ if (strlen($this->action_text)) {
+ return $this->action_text;
+ }
+
+ if ($actionObj = $this->getActionObject()) {
+ return $actionObj->getText();
+ }
+ }
+
+ public function getSchedule()
+ {
+ if ($this->schedule_type !== 'delayed') {
+ return false;
+ }
+
+ if (!is_numeric($this->schedule_delay) || !is_numeric($this->schedule_delay_factor)) {
+ return false;
+ }
+
+ $delay = (int) $this->schedule_delay;
+ $delay_factor = (int) $this->schedule_delay_factor;
+
+ return abs($delay * $delay_factor);
+ }
+
+ public function getActionObject()
+ {
+ $this->applyActionClass();
+
+ return $this->asExtension($this->getActionClass());
+ }
+
+ public function getActionClass()
+ {
+ return $this->class_name;
+ }
+}
diff --git a/plugins/rainlab/notify/models/RuleCondition.php b/plugins/rainlab/notify/models/RuleCondition.php
new file mode 100644
index 00000000..02827484
--- /dev/null
+++ b/plugins/rainlab/notify/models/RuleCondition.php
@@ -0,0 +1,172 @@
+ [self::class, 'key' => 'rule_parent_id', 'delete' => true],
+ ];
+
+ public $belongsTo = [
+ 'parent' => [self::class, 'key' => 'rule_parent_id'],
+ 'notification_rule' => [NotificationRule::class, 'key'=>'rule_host_id']
+ ];
+
+ public function filterFields($fields, $context)
+ {
+ /*
+ * Let the condition contribute
+ */
+ $this->getConditionObject()->setFormFields($fields);
+ }
+
+ /**
+ * Extends this model with the condition class
+ * @param string $class Class name
+ * @return boolean
+ */
+ public function applyConditionClass($class = null)
+ {
+ if (!$class) {
+ $class = $this->class_name;
+ }
+
+ if (!$class) {
+ return false;
+ }
+
+ if (!$this->isClassExtendedWith($class)) {
+ $this->extendClassWith($class);
+ }
+
+ $this->class_name = $class;
+ return true;
+ }
+
+ public function beforeSave()
+ {
+ $this->setCustomData();
+ }
+
+ public function applyCustomData()
+ {
+ $this->setCustomData();
+ $this->loadCustomData();
+ }
+
+ protected function loadCustomData()
+ {
+ $this->setRawAttributes((array) $this->getAttributes() + (array) $this->config_data, true);
+ }
+
+ protected function setCustomData()
+ {
+ /*
+ * Let the condition contribute
+ */
+ $this->getConditionObject()->setCustomData($this);
+
+ /*
+ * Spin over each field and add it to config_data
+ */
+ $config = $this->getFieldConfig();
+
+ if (!isset($config->fields)) {
+ throw new SystemException('Condition class has no fields.');
+ }
+
+ $staticAttributes = ['condition_text'];
+
+ $fieldAttributes = array_merge($staticAttributes, array_keys($config->fields));
+
+ $dynamicAttributes = array_only($this->getAttributes(), $fieldAttributes);
+
+ $this->config_data = $dynamicAttributes;
+
+ $this->setRawAttributes(array_except($this->getAttributes(), $fieldAttributes));
+ }
+
+ public function afterFetch()
+ {
+ $this->applyConditionClass();
+ $this->loadCustomData();
+ }
+
+ public function afterSave()
+ {
+ // Make sure that this record is removed from the DB after being removed from a rule
+ $removedFromRule = $this->rule_parent_id === null && $this->getOriginal('rule_parent_id');
+ if ($removedFromRule && !$this->notification_rule()->withDeferred(post('_session_key'))->exists()) {
+ $this->delete();
+ }
+ }
+
+ public function getText()
+ {
+ if (strlen($this->condition_text)) {
+ return $this->condition_text;
+ }
+
+ if ($conditionObj = $this->getConditionObject()) {
+ return $conditionObj->getText();
+ }
+ }
+
+ public function isCompound()
+ {
+ return $this->getConditionObject() instanceof CompoundConditionInterface;
+ }
+
+ public function getConditionObject()
+ {
+ $this->applyConditionClass();
+
+ return $this->asExtension($this->getConditionClass());
+ }
+
+ public function getConditionClass()
+ {
+ return $this->class_name;
+ }
+
+ public function getRootConditionClass()
+ {
+ return CompoundCondition::class;
+ }
+}
diff --git a/plugins/rainlab/notify/models/notificationrule/columns.yaml b/plugins/rainlab/notify/models/notificationrule/columns.yaml
new file mode 100644
index 00000000..2e298a31
--- /dev/null
+++ b/plugins/rainlab/notify/models/notificationrule/columns.yaml
@@ -0,0 +1,13 @@
+# ===================================
+# Column Definitions
+# ===================================
+
+columns:
+
+ name:
+ label: Name
+ searchable: true
+
+ code:
+ label: Code
+ searchable: true
diff --git a/plugins/rainlab/notify/models/notificationrule/fields.yaml b/plugins/rainlab/notify/models/notificationrule/fields.yaml
new file mode 100644
index 00000000..b0eec2ae
--- /dev/null
+++ b/plugins/rainlab/notify/models/notificationrule/fields.yaml
@@ -0,0 +1,47 @@
+# ===================================
+# Field Definitions
+# ===================================
+
+fields:
+ name:
+ label: Name
+ placeholder: New notification rule name
+ attributes:
+ autofocus: 1
+
+ toolbar:
+ type: partial
+ path: form_toolbar
+ cssClass: collapse-visible
+
+tabs:
+ stretch: true
+ cssClass: master-area
+ paneCssClass:
+ 'Actions': 'pane-compact'
+
+ fields:
+ rule_actions:
+ type: RainLab\Notify\FormWidgets\ActionBuilder
+ tab: Actions
+
+ rule_conditions:
+ type: RainLab\Notify\FormWidgets\ConditionBuilder
+ tab: Conditions
+
+ is_enabled:
+ label: Active
+ type: checkbox
+ tab: Settings
+ default: true
+
+ description:
+ label: Description
+ type: textarea
+ size: tiny
+ tab: Settings
+
+ code:
+ label: API Code
+ span: auto
+ tab: Settings
diff --git a/plugins/rainlab/notify/models/ruleaction/columns.yaml b/plugins/rainlab/notify/models/ruleaction/columns.yaml
new file mode 100644
index 00000000..b11160b4
--- /dev/null
+++ b/plugins/rainlab/notify/models/ruleaction/columns.yaml
@@ -0,0 +1,8 @@
+# ===================================
+# List Column Definitions
+# ===================================
+
+columns:
+ id:
+ label: ID
+ searchable: true
diff --git a/plugins/rainlab/notify/models/ruleaction/fields.yaml b/plugins/rainlab/notify/models/ruleaction/fields.yaml
new file mode 100644
index 00000000..c611f31c
--- /dev/null
+++ b/plugins/rainlab/notify/models/ruleaction/fields.yaml
@@ -0,0 +1,8 @@
+# ===================================
+# Form Field Definitions
+# ===================================
+
+fields:
+ id:
+ label: ID
+ disabled: true
diff --git a/plugins/rainlab/notify/models/ruleaction/fields_schedule.yaml b/plugins/rainlab/notify/models/ruleaction/fields_schedule.yaml
new file mode 100644
index 00000000..bd3d80cb
--- /dev/null
+++ b/plugins/rainlab/notify/models/ruleaction/fields_schedule.yaml
@@ -0,0 +1,30 @@
+# ===================================
+# Form Field Definitions
+# ===================================
+
+fields:
+ schedule_type:
+ type: dropdown
+ options:
+ disabled: None
+ delayed: Delay execution
+ schedule_delay:
+ type: number
+ span: left
+ trigger:
+ action: show
+ field: schedule_type
+ condition: value[delayed]
+ schedule_delay_factor:
+ type: dropdown
+ span: right
+ options:
+ 1: Seconds
+ 60: Minutes
+ 3600: Hours
+ 86400: Days
+ 2592000: Months
+ trigger:
+ action: show
+ field: schedule_type
+ condition: value[delayed]
diff --git a/plugins/rainlab/notify/notifyrules/ExecutionContextCondition.php b/plugins/rainlab/notify/notifyrules/ExecutionContextCondition.php
new file mode 100644
index 00000000..32369cab
--- /dev/null
+++ b/plugins/rainlab/notify/notifyrules/ExecutionContextCondition.php
@@ -0,0 +1,136 @@
+ 'is',
+ 'is_not' => 'is not',
+ ];
+
+ public function getName()
+ {
+ return 'Event is triggered from environment';
+ }
+
+ public function getTitle()
+ {
+ return 'Execution context';
+ }
+
+ public function getText()
+ {
+ $host = $this->host;
+ $value = $host->value;
+ $attribute = $host->subcondition;
+ $subconditions = $this->getSubconditionOptions();
+
+ $result = array_get($subconditions, $attribute, 'Execution context');
+ $result .= ' '.array_get($this->operators, $host->operator, $host->operator).' ';
+
+ if ($attribute == 'locale' || $attribute == 'environment') {
+ $result .= strtolower($value) ?: '?';
+ }
+ elseif ($value) {
+ $options = $this->getValueOptions();
+ $result .= strtolower(array_get($options, $value));
+ }
+ else {
+ $result .= '?';
+ }
+
+ return $result;
+ }
+
+ /**
+ * Returns a title to use for grouping subconditions
+ * in the Create Condition drop-down menu
+ */
+ public function getGroupingTitle()
+ {
+ return 'Execution context';
+ }
+
+ public function listSubconditions()
+ {
+ return array_flip($this->getSubconditionOptions());
+ }
+
+ public function initConfigData($host)
+ {
+ $host->operator = 'is';
+ }
+
+ public function setFormFields($fields)
+ {
+ $attribute = $fields->subcondition->value;
+
+ if ($attribute == 'locale' || $attribute == 'environment') {
+ $fields->value->type = 'text';
+ }
+ else {
+ $fields->value->type = 'dropdown';
+ }
+ }
+
+ public function getValueOptions()
+ {
+ $attribute = $this->host->subcondition;
+ $result = [];
+
+ if ($attribute == 'context') {
+ $result = [
+ 'backend' => 'Back-end area',
+ 'front' => 'Front-end website',
+ 'console' => 'Command line interface',
+ ];
+ }
+
+ if ($attribute == 'theme') {
+ foreach (Theme::all() as $theme) {
+ $result[$theme->getDirName()] = $theme->getDirName();
+ }
+ }
+
+ return $result;
+ }
+
+ public function getSubconditionOptions()
+ {
+ return [
+ 'environment' => 'Application environment',
+ 'context' => 'Request context',
+ 'theme' => 'Active theme',
+ 'locale' => 'Visitor locale',
+ ];
+ }
+
+ public function getOperatorOptions()
+ {
+ return $this->operators;
+ }
+
+ /**
+ * Checks whether the condition is TRUE for specified parameters
+ * @param array $params
+ * @return bool
+ */
+ public function isTrue(&$params)
+ {
+ $hostObj = $this->host;
+ $attribute = $hostObj->subcondition;
+
+ $conditionValue = $hostObj->value;
+ $conditionValue = trim(mb_strtolower($conditionValue));
+
+ if ($attribute == 'locale') {
+ return array_get($params, 'appLocale') == $conditionValue;
+ } else if ($attribute === 'environment') {
+ return $conditionValue === \App::environment();
+ }
+
+ return false;
+ }
+}
diff --git a/plugins/rainlab/notify/notifyrules/SaveDatabaseAction.php b/plugins/rainlab/notify/notifyrules/SaveDatabaseAction.php
new file mode 100644
index 00000000..61921265
--- /dev/null
+++ b/plugins/rainlab/notify/notifyrules/SaveDatabaseAction.php
@@ -0,0 +1,119 @@
+ 'Store in database',
+ 'description' => 'Log event data in the notifications activity log',
+ 'icon' => 'icon-database'
+ ];
+ }
+
+ public function defineFormFields()
+ {
+ return 'fields.yaml';
+ }
+
+ public function getText()
+ {
+ if ($this->host->related_object) {
+ $label = array_get($this->getRelatedObjectOptions(), $this->host->related_object);
+
+ return 'Log event in the '.$label.' log';
+ }
+
+ return parent::getText();
+ }
+
+ /**
+ * Triggers this action.
+ * @param array $params
+ * @return void
+ */
+ public function triggerAction($params)
+ {
+ if (
+ (!$definition = array_get($this->tableDefinitions, $this->host->related_object)) ||
+ (!$param = array_get($definition, 'param')) ||
+ (!$value = array_get($params, $param))
+ ) {
+ throw new ApplicationException('Error evaluating the save database action: the related object is not found in the action parameters.');
+ }
+
+ if (!$value instanceof EloquentModel) {
+ // @todo Perhaps value is an ID or a model array,
+ // look up model $definition[class] from ID ...
+ }
+
+ $rule = $this->host->notification_rule;
+ $relation = array_get($definition, 'relation');
+
+ $value->$relation()->create([
+ 'id' => Uuid::uuid4()->toString(),
+ 'event_type' => $rule->getEventClass(),
+ 'icon' => $this->host->icon,
+ 'type' => $this->host->type,
+ 'body' => $this->host->body,
+ 'data' => $this->getData($params),
+ 'read_at' => null,
+ ]);
+ }
+
+ /**
+ * Get the data for the notification.
+ *
+ * @param array $notifiable
+ * @return array
+ */
+ protected function getData($params)
+ {
+ // This should check for params that cannot be jsonable.
+ return $params;
+ }
+
+ public function getRelatedObjectOptions()
+ {
+ $result = [];
+
+ foreach ($this->tableDefinitions as $key => $definition) {
+ $result[$key] = array_get($definition, 'label');
+ }
+
+ return $result;
+ }
+
+ public function getTableDefinitions()
+ {
+ return $this->tableDefinitions;
+ }
+
+ public function addTableDefinition($options)
+ {
+ if (!$className = array_get($options, 'class')) {
+ throw new ApplicationException('Missing class name from table definition.');
+ }
+
+ $options = array_merge([
+ 'label' => 'Undefined table',
+ 'class' => null,
+ 'param' => null,
+ 'relation' => 'notifications',
+ ], $options);
+
+ $keyName = $className . '@' . array_get($options, 'relation');
+
+ $this->tableDefinitions[$keyName] = $options;
+ }
+}
diff --git a/plugins/rainlab/notify/notifyrules/SendMailTemplateAction.php b/plugins/rainlab/notify/notifyrules/SendMailTemplateAction.php
new file mode 100644
index 00000000..e7abf4bc
--- /dev/null
+++ b/plugins/rainlab/notify/notifyrules/SendMailTemplateAction.php
@@ -0,0 +1,225 @@
+ 'System default',
+ 'user' => 'User email address (if applicable)',
+ 'sender' => 'Sender user email address (if applicable)',
+ 'admin' => 'Back-end administrators',
+ 'custom' => 'Specific email address',
+ ];
+
+ /**
+ * Returns information about this event, including name and description.
+ */
+ public function actionDetails()
+ {
+ return [
+ 'name' => 'Compose a mail message',
+ 'description' => 'Send a message to a recipient',
+ 'icon' => 'icon-envelope'
+ ];
+ }
+
+ /**
+ * Triggers this action.
+ * @param array $params
+ * @return void
+ */
+ public function triggerAction($params)
+ {
+ $template = $this->host->mail_template;
+
+ $recipient = $this->getRecipientAddress($params);
+
+ $replyTo = $this->getReplyToAddress($params);
+
+ if (!$recipient || !$template) {
+ throw new ApplicationException('Missing valid recipient or mail template');
+ }
+
+ Mail::sendTo($recipient, $template, $params, function($message) use ($replyTo) {
+ if ($replyTo) {
+ $message->replyTo($replyTo);
+ }
+ });
+ }
+
+ /**
+ * Field configuration for the action.
+ */
+ public function defineFormFields()
+ {
+ return 'fields.yaml';
+ }
+
+ /**
+ * Defines validation rules for the custom fields.
+ * @return array
+ */
+ public function defineValidationRules()
+ {
+ return [
+ 'mail_template' => 'required',
+ 'send_to_mode' => 'required',
+ ];
+ }
+
+ public function getTitle()
+ {
+ if ($this->isAdminMode()) {
+ return 'Compose mail to administrators';
+ }
+
+ return parent::getTitle();
+ }
+
+ public function getActionIcon()
+ {
+ if ($this->isAdminMode()) {
+ return 'icon-envelope-square';
+ }
+
+ return parent::getActionIcon();
+ }
+
+ public function getText()
+ {
+ $hostObj = $this->host;
+
+ $recipient = array_get($this->recipientModes, $hostObj->send_to_mode);
+
+ if ($this->isAdminMode()) {
+ if ($groupId = $this->host->send_to_admin) {
+ if ($group = AdminGroupModel::find($groupId)) {
+ $adminText = $group->name;
+ }
+ else {
+ $adminText = '?';
+ }
+
+ $adminText .= ' admin group';
+ }
+ else {
+ $adminText = 'all admins';
+ }
+ return sprintf(
+ 'Send a message to %s using template %s',
+ $adminText,
+ $hostObj->mail_template
+ );
+ }
+
+ if ($hostObj->mail_template) {
+ return sprintf(
+ 'Send a message to %s using template %s',
+ mb_strtolower($recipient),
+ $hostObj->mail_template
+ );
+ }
+
+ return parent::getText();
+ }
+
+ public function getSendToAdminOptions()
+ {
+ $options = ['' => '- All administrators -'];
+
+ $groups = AdminGroupModel::lists('name', 'id');
+
+ return $options + $groups;
+ }
+
+ public function getSendToModeOptions()
+ {
+ $modes = $this->recipientModes;
+
+ unset($modes['system']);
+
+ return $modes;
+ }
+
+ public function getReplyToModeOptions()
+ {
+ $modes = $this->recipientModes;
+
+ unset($modes['admin']);
+
+ return $modes;
+ }
+
+ public function getMailTemplateOptions()
+ {
+ $codes = array_keys(MailTemplate::listAllTemplates());
+
+ $result = array_combine($codes, $codes);
+
+ // Wrap to prevent collisions with language keys
+ array_walk($result, function(&$value, $key) {
+ $value = "[$value]";
+ });
+
+ return $result;
+ }
+
+ protected function getReplyToAddress($params)
+ {
+ $mode = $this->host->reply_to_mode;
+
+ if ($mode == 'custom') {
+ return $this->host->reply_to_custom;
+ }
+
+ if ($mode == 'user' || $mode == 'sender') {
+ $obj = array_get($params, $mode);
+ return $obj->email;
+ }
+ }
+
+ protected function getRecipientAddress($params)
+ {
+ $mode = $this->host->send_to_mode;
+
+ if ($mode == 'custom') {
+ return $this->host->send_to_custom;
+ }
+
+ if ($mode == 'system') {
+ $name = Config::get('mail.from.name', 'Your Site');
+ $address = Config::get('mail.from.address', 'admin@domain.tld');
+ return [$address => $name];
+ }
+
+ if ($mode == 'admin') {
+ if ($groupId = $this->host->send_to_admin) {
+ if (!$group = AdminGroupModel::find($groupId)) {
+ throw new ApplicationException('Unable to find admin group with ID: '.$groupId);
+ }
+
+ return $group->users->lists('full_name', 'email');
+ }
+ else {
+ return AdminUserModel::all()->lists('full_name', 'email');
+ }
+ }
+
+ if ($mode == 'user' || $mode == 'sender') {
+ return array_get($params, $mode);
+ }
+ }
+
+ protected function isAdminMode()
+ {
+ return $this->host->send_to_mode == 'admin';
+ }
+}
diff --git a/plugins/rainlab/notify/notifyrules/executioncontextcondition/fields.yaml b/plugins/rainlab/notify/notifyrules/executioncontextcondition/fields.yaml
new file mode 100644
index 00000000..9981f871
--- /dev/null
+++ b/plugins/rainlab/notify/notifyrules/executioncontextcondition/fields.yaml
@@ -0,0 +1,21 @@
+# ===================================
+# Field Definitions
+# ===================================
+
+fields:
+
+ subcondition:
+ label: Attribute
+ span: auto
+ type: dropdown
+
+ operator:
+ label: Operator
+ span: auto
+ type: dropdown
+ dependsOn: subcondition
+
+ value:
+ label: Value
+ dependsOn: [subcondition, operator]
+ type: dropdown
diff --git a/plugins/rainlab/notify/notifyrules/savedatabaseaction/fields.yaml b/plugins/rainlab/notify/notifyrules/savedatabaseaction/fields.yaml
new file mode 100644
index 00000000..77c144f2
--- /dev/null
+++ b/plugins/rainlab/notify/notifyrules/savedatabaseaction/fields.yaml
@@ -0,0 +1,8 @@
+# ===================================
+# Field Definitions
+# ===================================
+
+fields:
+ related_object:
+ label: Related object
+ type: dropdown
diff --git a/plugins/rainlab/notify/notifyrules/sendmailtemplateaction/fields.yaml b/plugins/rainlab/notify/notifyrules/sendmailtemplateaction/fields.yaml
new file mode 100644
index 00000000..13aa5480
--- /dev/null
+++ b/plugins/rainlab/notify/notifyrules/sendmailtemplateaction/fields.yaml
@@ -0,0 +1,42 @@
+# ===================================
+# Field Definitions
+# ===================================
+
+fields:
+ mail_template:
+ label: Mail template
+ type: dropdown
+ placeholder: Select template
+
+ send_to_mode:
+ label: Send to
+ type: radio
+ span: left
+
+ send_to_custom:
+ cssClass: radio-align
+ trigger:
+ action: show
+ field: send_to_mode
+ condition: value[custom]
+
+ send_to_admin:
+ label: Send to admin group
+ span: right
+ type: dropdown
+ trigger:
+ action: show
+ field: send_to_mode
+ condition: value[admin]
+
+ reply_to_mode:
+ label: Reply-to address
+ type: radio
+ span: left
+
+ reply_to_custom:
+ cssClass: radio-align
+ trigger:
+ action: show
+ field: reply_to_mode
+ condition: value[custom]
diff --git a/plugins/rainlab/notify/updates/create_notification_rules_table.php b/plugins/rainlab/notify/updates/create_notification_rules_table.php
new file mode 100644
index 00000000..2bcc6b07
--- /dev/null
+++ b/plugins/rainlab/notify/updates/create_notification_rules_table.php
@@ -0,0 +1,29 @@
+engine = 'InnoDB';
+ $table->increments('id');
+ $table->string('name')->nullable();
+ $table->string('code')->index()->nullable();
+ $table->string('class_name')->nullable();
+ $table->text('description')->nullable();
+ $table->mediumText('config_data')->nullable();
+ $table->mediumText('condition_data')->nullable();
+ $table->boolean('is_enabled')->default(0);
+ $table->boolean('is_custom')->default(1);
+ $table->timestamps();
+ });
+ }
+
+ public function down()
+ {
+ Schema::dropIfExists('rainlab_notify_notification_rules');
+ }
+}
diff --git a/plugins/rainlab/notify/updates/create_notifications_table.php b/plugins/rainlab/notify/updates/create_notifications_table.php
new file mode 100644
index 00000000..4c73bb4c
--- /dev/null
+++ b/plugins/rainlab/notify/updates/create_notifications_table.php
@@ -0,0 +1,28 @@
+uuid('id')->primary();
+ $table->string('event_type');
+ $table->morphs('notifiable');
+ $table->string('icon')->nullable();
+ $table->string('type')->nullable();
+ $table->text('body')->nullable();
+ $table->mediumText('data');
+ $table->timestamp('read_at')->nullable();
+ $table->timestamps();
+ });
+ }
+
+ public function down()
+ {
+ Schema::dropIfExists('rainlab_notify_notifications');
+ }
+}
diff --git a/plugins/rainlab/notify/updates/create_rule_actions_table.php b/plugins/rainlab/notify/updates/create_rule_actions_table.php
new file mode 100644
index 00000000..af89e412
--- /dev/null
+++ b/plugins/rainlab/notify/updates/create_rule_actions_table.php
@@ -0,0 +1,25 @@
+engine = 'InnoDB';
+ $table->increments('id');
+ $table->string('class_name')->nullable();
+ $table->mediumText('config_data')->nullable();
+ $table->integer('rule_host_id')->unsigned()->nullable()->index();
+ $table->timestamps();
+ });
+ }
+
+ public function down()
+ {
+ Schema::dropIfExists('rainlab_notify_rule_actions');
+ }
+}
diff --git a/plugins/rainlab/notify/updates/create_rule_conditions_table.php b/plugins/rainlab/notify/updates/create_rule_conditions_table.php
new file mode 100644
index 00000000..4a0211e6
--- /dev/null
+++ b/plugins/rainlab/notify/updates/create_rule_conditions_table.php
@@ -0,0 +1,29 @@
+engine = 'InnoDB';
+ $table->increments('id');
+ $table->string('class_name')->nullable();
+ $table->mediumText('config_data')->nullable();
+ $table->string('condition_control_type', 100)->nullable();
+ $table->string('rule_host_type', 100)->nullable();
+ $table->integer('rule_host_id')->unsigned()->nullable()->index();
+ $table->integer('rule_parent_id')->unsigned()->nullable()->index();
+ $table->index(['rule_host_id', 'rule_host_type'], 'host_rule_id_type');
+ $table->timestamps();
+ });
+ }
+
+ public function down()
+ {
+ Schema::dropIfExists('rainlab_notify_rule_conditions');
+ }
+}
diff --git a/plugins/rainlab/notify/updates/version.yaml b/plugins/rainlab/notify/updates/version.yaml
new file mode 100644
index 00000000..af1dae03
--- /dev/null
+++ b/plugins/rainlab/notify/updates/version.yaml
@@ -0,0 +1,13 @@
+v1.0.1:
+ - First version of Notify
+ - create_notifications_table.php
+ - create_notification_rules_table.php
+ - create_rule_conditions_table.php
+ - create_rule_actions_table.php
+v1.0.2: Fixes crashing bug.
+v1.0.3: Added Turkish & Russian translations, various bug fixes.
+v1.1.0: Fixes support for October CMS 2.0
+v1.1.1: Fixes missing template bug when saving notification rule
+v1.1.2: Fixes collisions with language keys in compose mail action
+v1.2.0: Adds delayed execution to actions
+v1.2.1: Improve support with October v3
diff --git a/plugins/rainlab/translate/.gitignore b/plugins/rainlab/translate/.gitignore
new file mode 100644
index 00000000..4b53aba1
--- /dev/null
+++ b/plugins/rainlab/translate/.gitignore
@@ -0,0 +1,4 @@
+/vendor
+composer.lock
+.DS_Store
+.phpunit.result.cache
diff --git a/plugins/rainlab/translate/LICENCE.md b/plugins/rainlab/translate/LICENCE.md
new file mode 100644
index 00000000..e49b459a
--- /dev/null
+++ b/plugins/rainlab/translate/LICENCE.md
@@ -0,0 +1,21 @@
+# MIT license
+
+Copyright (c) 2014-2022 Responsiv Pty Ltd, October CMS
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/plugins/rainlab/translate/Plugin.php b/plugins/rainlab/translate/Plugin.php
new file mode 100644
index 00000000..ec578833
--- /dev/null
+++ b/plugins/rainlab/translate/Plugin.php
@@ -0,0 +1,381 @@
+ 'rainlab.translate::lang.plugin.name',
+ 'description' => 'rainlab.translate::lang.plugin.description',
+ 'author' => 'Alexey Bobkov, Samuel Georges',
+ 'icon' => 'icon-language',
+ 'homepage' => 'https://github.com/rainlab/translate-plugin'
+ ];
+ }
+
+ /**
+ * register the plugin
+ */
+ public function register()
+ {
+ // Load localized version of mail templates (akin to localized CMS content files)
+ Event::listen('mailer.beforeAddContent', function ($mailer, $message, $view, $data, $raw, $plain) {
+ return EventRegistry::instance()->findLocalizedMailViewContent($mailer, $message, $view, $data, $raw, $plain);
+ }, 1);
+
+ // Defer event with low priority to let others contribute before this registers.
+ Event::listen('backend.form.extendFieldsBefore', function($widget) {
+ EventRegistry::instance()->registerFormFieldReplacements($widget);
+ }, -1);
+
+ // Handle translated page URLs
+ Page::extend(function($model) {
+ if (!$model->propertyExists('translatable')) {
+ $model->addDynamicProperty('translatable', []);
+ }
+ $model->translatable = array_merge($model->translatable, ['title', 'description', 'meta_title', 'meta_description']);
+ if (!$model->isClassExtendedWith(\RainLab\Translate\Behaviors\TranslatablePageUrl::class)) {
+ $model->extendClassWith(\RainLab\Translate\Behaviors\TranslatablePageUrl::class);
+ }
+ if (!$model->isClassExtendedWith(\RainLab\Translate\Behaviors\TranslatablePage::class)) {
+ $model->extendClassWith(\RainLab\Translate\Behaviors\TranslatablePage::class);
+ }
+ });
+
+ // Extension logic for October CMS v1.0
+ if (!class_exists('System')) {
+ $this->extendLegacyPlatform();
+ }
+ // Extension logic for October CMS v2.0
+ else {
+ Event::listen('cms.theme.createThemeDataModel', function($attributes) {
+ return new \RainLab\Translate\Models\MLThemeData($attributes);
+ });
+
+ Event::listen('cms.template.getTemplateToolbarSettingsButtons', function($extension, $dataHolder) {
+ if ($dataHolder->templateType === 'page') {
+ EventRegistry::instance()->extendEditorPageToolbar($dataHolder);
+ }
+ });
+ }
+
+ // Register console commands
+ $this->registerConsoleCommand('translate.scan', \Rainlab\Translate\Console\ScanCommand::class);
+
+ // Register asset bundles
+ $this->registerAssetBundles();
+ }
+
+ /**
+ * extendLegacyPlatform will add the legacy features expected in v1.0
+ */
+ protected function extendLegacyPlatform()
+ {
+ // Adds translation support to file models
+ File::extend(function ($model) {
+ if (!$model->propertyExists('translatable')) {
+ $model->addDynamicProperty('translatable', []);
+ }
+ $model->translatable = array_merge($model->translatable, ['title', 'description']);
+ if (!$model->isClassExtendedWith(\October\Rain\Database\Behaviors\Purgeable::class)) {
+ $model->extendClassWith(\October\Rain\Database\Behaviors\Purgeable::class);
+ }
+ if (!$model->isClassExtendedWith(\RainLab\Translate\Behaviors\TranslatableModel::class)) {
+ $model->extendClassWith(\RainLab\Translate\Behaviors\TranslatableModel::class);
+ }
+ });
+
+ // Adds translation support to theme settings
+ ThemeData::extend(static function ($model) {
+ if (!$model->propertyExists('translatable')) {
+ $model->addDynamicProperty('translatable', []);
+ }
+
+ if (!$model->isClassExtendedWith(\October\Rain\Database\Behaviors\Purgeable::class)) {
+ $model->extendClassWith(\October\Rain\Database\Behaviors\Purgeable::class);
+ }
+ if (!$model->isClassExtendedWith(\RainLab\Translate\Behaviors\TranslatableModel::class)) {
+ $model->extendClassWith(\RainLab\Translate\Behaviors\TranslatableModel::class);
+ }
+
+ $model->bindEvent('model.afterFetch', static function() use ($model) {
+ foreach ($model->getFormFields() as $id => $field) {
+ if (!empty($field['translatable'])) {
+ $model->translatable[] = $id;
+ }
+ }
+ });
+ });
+ }
+
+ /**
+ * boot the plugin
+ */
+ public function boot()
+ {
+ // Set the page context for translation caching with high priority.
+ Event::listen('cms.page.init', function($controller, $page) {
+ EventRegistry::instance()->setMessageContext($page);
+ }, 100);
+
+ // Populate MenuItem properties with localized values if available
+ Event::listen('pages.menu.referencesGenerated', function (&$items) {
+ $locale = App::getLocale();
+ $iterator = function ($menuItems) use (&$iterator, $locale) {
+ $result = [];
+ foreach ($menuItems as $item) {
+ $localeFields = array_get($item->viewBag, "locale.$locale", []);
+ foreach ($localeFields as $fieldName => $fieldValue) {
+ if ($fieldValue) {
+ $item->$fieldName = $fieldValue;
+ }
+ }
+ if ($item->items) {
+ $item->items = $iterator($item->items);
+ }
+ $result[] = $item;
+ }
+ return $result;
+ };
+ $items = $iterator($items);
+ });
+
+ // Import messages defined by the theme
+ Event::listen('cms.theme.setActiveTheme', function($code) {
+ EventRegistry::instance()->importMessagesFromTheme($code);
+ });
+
+ // Adds language suffixes to content files.
+ Event::listen('cms.page.beforeRenderContent', function($controller, $fileName) {
+ return EventRegistry::instance()
+ ->findTranslatedContentFile($controller, $fileName)
+ ;
+ });
+
+ // Prune localized content files from template list
+ Event::listen('pages.content.templateList', function($widget, $templates) {
+ return EventRegistry::instance()
+ ->pruneTranslatedContentTemplates($templates)
+ ;
+ });
+
+ // Look at session for locale using middleware
+ \Cms\Classes\CmsController::extend(function($controller) {
+ $controller->middleware(\RainLab\Translate\Classes\LocaleMiddleware::class);
+ });
+
+ // Append current locale to static page's cache keys
+ $modifyKey = function (&$key) {
+ $key = $key . '-' . Lang::getLocale();
+ };
+ Event::listen('pages.router.getCacheKey', $modifyKey);
+ Event::listen('pages.page.getMenuCacheKey', $modifyKey);
+ Event::listen('pages.snippet.getMapCacheKey', $modifyKey);
+ Event::listen('pages.snippet.getPartialMapCacheKey', $modifyKey);
+
+ if (class_exists('\RainLab\Pages\Classes\SnippetManager')) {
+ $handler = function ($controller, $template, $type) {
+ if (!$template->methodExists('getDirtyLocales')) {
+ return;
+ }
+
+ // Get the locales that have changed
+ $dirtyLocales = $template->getDirtyLocales();
+
+ if (!empty($dirtyLocales)) {
+ $currentLocale = Lang::getLocale();
+
+ foreach ($dirtyLocales as $locale) {
+ if (!$template->isTranslateDirty(null, $locale)) {
+ continue;
+ }
+
+ // Clear the RainLab.Pages caches for each dirty locale
+ App::setLocale($locale);
+ \RainLab\Pages\Plugin::clearCache();
+ }
+
+ // Restore the original locale for this request
+ App::setLocale($currentLocale);
+ }
+ };
+
+ Event::listen('cms.template.save', $handler);
+ Event::listen('pages.object.save', $handler);
+ }
+ }
+
+ /**
+ * registerComponents
+ */
+ public function registerComponents()
+ {
+ return [
+ \RainLab\Translate\Components\LocalePicker::class => 'localePicker',
+ \RainLab\Translate\Components\AlternateHrefLangElements::class => 'alternateHrefLangElements'
+ ];
+ }
+
+ /**
+ * registerPermissions
+ */
+ public function registerPermissions()
+ {
+ return [
+ 'rainlab.translate.manage_locales' => [
+ 'tab' => 'rainlab.translate::lang.plugin.tab',
+ 'label' => 'rainlab.translate::lang.plugin.manage_locales'
+ ],
+ 'rainlab.translate.manage_messages' => [
+ 'tab' => 'rainlab.translate::lang.plugin.tab',
+ 'label' => 'rainlab.translate::lang.plugin.manage_messages'
+ ]
+ ];
+ }
+
+ /**
+ * registerSettings
+ */
+ public function registerSettings()
+ {
+ return [
+ 'locales' => [
+ 'label' => 'rainlab.translate::lang.locale.title',
+ 'description' => 'rainlab.translate::lang.plugin.description',
+ 'icon' => 'icon-language',
+ 'url' => Backend::url('rainlab/translate/locales'),
+ 'order' => 550,
+ 'category' => 'rainlab.translate::lang.plugin.name',
+ 'permissions' => ['rainlab.translate.manage_locales'],
+ 'keywords' => 'translate',
+ ],
+ 'messages' => [
+ 'label' => 'rainlab.translate::lang.messages.title',
+ 'description' => 'rainlab.translate::lang.messages.description',
+ 'icon' => 'icon-list-alt',
+ 'url' => Backend::url('rainlab/translate/messages'),
+ 'order' => 551,
+ 'category' => 'rainlab.translate::lang.plugin.name',
+ 'permissions' => ['rainlab.translate.manage_messages'],
+ 'keywords' => 'translate',
+ ]
+ ];
+ }
+
+ /**
+ * registerMarkupTags for Twig
+ * @return array
+ */
+ public function registerMarkupTags()
+ {
+ return [
+ 'filters' => [
+ '_' => [$this, 'translateString'],
+ '__' => [$this, 'translatePlural'],
+ 'transRaw' => [$this, 'translateRawString'],
+ 'transRawPlural' => [$this, 'translateRawPlural'],
+ 'localeUrl' => [$this, 'localeUrl'],
+ ]
+ ];
+ }
+
+ /**
+ * registerFormWidgets for multi-lingual
+ */
+ public function registerFormWidgets()
+ {
+ $mediaFinderClass = class_exists('System')
+ ? \RainLab\Translate\FormWidgets\MLMediaFinderv2::class
+ : \RainLab\Translate\FormWidgets\MLMediaFinder::class;
+
+ return [
+ \RainLab\Translate\FormWidgets\MLText::class => 'mltext',
+ \RainLab\Translate\FormWidgets\MLTextarea::class => 'mltextarea',
+ \RainLab\Translate\FormWidgets\MLRichEditor::class => 'mlricheditor',
+ \RainLab\Translate\FormWidgets\MLMarkdownEditor::class => 'mlmarkdowneditor',
+ \RainLab\Translate\FormWidgets\MLRepeater::class => 'mlrepeater',
+ \RainLab\Translate\FormWidgets\MLNestedForm::class => 'mlnestedform',
+ $mediaFinderClass => 'mlmediafinder',
+ ];
+ }
+
+ /**
+ * registerAssetBundles for compilation
+ */
+ protected function registerAssetBundles()
+ {
+ CombineAssets::registerCallback(function ($combiner) {
+ $combiner->registerBundle('$/rainlab/translate/assets/less/messages.less');
+ $combiner->registerBundle('$/rainlab/translate/assets/less/multilingual.less');
+ });
+ }
+
+ /**
+ * localeUrl builds a localized URL
+ */
+ public function localeUrl($url, $locale)
+ {
+ $translator = Translator::instance();
+
+ $parts = parse_url($url);
+
+ $path = array_get($parts, 'path');
+
+ return http_build_url($parts, [
+ 'path' => '/' . $translator->getPathInLocale($path, $locale)
+ ]);
+ }
+
+ /**
+ * translateString
+ */
+ public function translateString($string, $params = [], $locale = null)
+ {
+ return Message::trans($string, $params, $locale);
+ }
+
+ /**
+ * translatePlural
+ */
+ public function translatePlural($string, $count = 0, $params = [], $locale = null)
+ {
+ return Lang::choice(Message::trans($string, $params, $locale), $count, $params);
+ }
+
+ /**
+ * translateRawString
+ */
+ public function translateRawString($string, $params = [], $locale = null)
+ {
+ return Message::transRaw($string, $params, $locale);
+ }
+
+ /**
+ * translateRawPlural
+ */
+ public function translateRawPlural($string, $count = 0, $params = [], $locale = null)
+ {
+ return Lang::choice(Message::transRaw($string, $params, $locale), $count, $params);
+ }
+}
diff --git a/plugins/rainlab/translate/README.md b/plugins/rainlab/translate/README.md
new file mode 100644
index 00000000..65227172
--- /dev/null
+++ b/plugins/rainlab/translate/README.md
@@ -0,0 +1,394 @@
+# Translation plugin
+
+Enables multi-lingual sites.
+
+## Selecting a language
+
+Different languages can be set up in the back-end area, with a single default language selected. This activates the use of the language on the front-end and in the back-end UI.
+
+A visitor can select a language by prefixing the language code to the URL, this is then stored in the user's session as their chosen language. For example:
+
+* `http://website/ru/` will display the site in Russian
+* `http://website/fr/` will display the site in French
+* `http://website/` will display the site in the default language or the user's chosen language.
+
+## Language Picker Component
+
+A visitor can select their chosen language using the `LocalePicker` component. This component will display a simple dropdown that changes the page language depending on the selection.
+
+ title = "Home"
+ url = "/"
+
+ [localePicker]
+ ==
+
+ {{ 'Please select your language:'|_ }}
+ {% component 'localePicker' %}
+
+If translated, the text above will appear as whatever language is selected by the user. The dropdown is very basic and is intended to be restyled. A simpler example might be:
+
+ [...]
+ ==
+
+
+ Switch language to:
+ English ,
+ Russian
+
+
+## Message translation
+
+Message or string translation is the conversion of adhoc strings used throughout the site. A message can be translated with parameters.
+
+ {{ 'site.name'|_ }}
+
+ {{ 'Welcome to our website!'|_ }}
+
+ {{ 'Hello :name!'|_({ name: 'Friend' }) }}
+
+A message can also be translated for a choice usage.
+
+ {{ 'There are no apples|There are :number applies!'|__(2, { number: 'two' }) }}
+
+Or you set a locale manually by passing a second argument.
+
+ {{ 'this is always english'|_({}, 'en') }}
+
+Themes can provide default values for these messages by defining a `translate` key in the `theme.yaml` file, located in the theme directory.
+
+ name: My Theme
+ # [...]
+
+ translate:
+ en:
+ site.name: 'My Website'
+ nav.home: 'Home'
+ nav.video: 'Video'
+ title.home: 'Welcome Home'
+ title.video: 'Screencast Video'
+
+You may also define the translations in a separate file, where the path is relative to the theme. The following definition will source the default messages from the file **config/lang.yaml** inside the theme.
+
+ name: My Theme
+ # [...]
+
+ translate: config/lang.yaml
+
+This is an example of **config/lang.yaml** file with two languages:
+
+ en:
+ site.name: 'My Website'
+ nav.home: 'Home'
+ nav.video: 'Video'
+ title.home: 'Welcome Home'
+ hr:
+ site.name: 'Moje web stranice'
+ nav.home: 'Početna'
+ nav.video: 'Video'
+ title.home: 'Dobrodošli'
+
+You may also define the translations in a separate file per locale, where the path is relative to the theme. The following definition will source the default messages from the file **config/lang-en.yaml** inside the theme for the english locale and from the file **config/lang-fr.yaml** for the french locale.
+
+ name: My Theme
+ # [...]
+
+ translate:
+ en: config/lang-en.yaml
+ fr: config/lang-fr.yaml
+
+This is an example for the **config/lang-en.yaml** file:
+
+ site.name: 'My Website'
+ nav.home: 'Home'
+ nav.video: 'Video'
+ title.home: 'Welcome Home'
+
+In order to make these default values reflected to your frontend site, go to **Settings -> Translate messages** in the backend and hit **Scan for messages**. They will also be loaded automatically when the theme is activated.
+
+The same operation can be performed with the `translate:scan` artisan command. It may be worth including it in a deployment script to automatically fetch updated messages:
+
+ php artisan translate:scan
+
+Add the `--purge` option to clear old messages first:
+
+ php artisan translate:scan --purge
+
+## Content & mail template translation
+
+This plugin activates a feature in the CMS that allows content & mail template files to use language suffixes, for example:
+
+* **welcome.htm** will contain the content or mail template in the default language.
+* **welcome-ru.htm** will contain the content or mail template in Russian.
+* **welcome-fr.htm** will contain the content or mail template in French.
+
+## Model translation
+
+Models can have their attributes translated by using the `RainLab.Translate.Behaviors.TranslatableModel` behavior and specifying which attributes to translate in the class.
+
+ class User
+ {
+ public $implement = ['RainLab.Translate.Behaviors.TranslatableModel'];
+
+ public $translatable = ['name'];
+ }
+
+The attribute will then contain the default language value and other language code values can be created by using the `translateContext()` method.
+
+ $user = User::first();
+
+ // Outputs the name in the default language
+ echo $user->name;
+
+ $user->translateContext('fr');
+
+ // Outputs the name in French
+ echo $user->name;
+
+You may use the same process for setting values.
+
+ $user = User::first();
+
+ // Sets the name in the default language
+ $user->name = 'English';
+
+ $user->translateContext('fr');
+
+ // Sets the name in French
+ $user->name = 'Anglais';
+
+The `lang()` method is a shorthand version of `translateContext()` and is also chainable.
+
+ // Outputs the name in French
+ echo $user->lang('fr')->name;
+
+This can be useful inside a Twig template.
+
+ {{ user.lang('fr').name }}
+
+There are ways to get and set attributes without changing the context.
+
+ // Gets a single translated attribute for a language
+ $user->getAttributeTranslated('name', 'fr');
+
+ // Sets a single translated attribute for a language
+ $user->setAttributeTranslated('name', 'Jean-Claude', 'fr');
+
+## Theme data translation
+
+It is also possible to translate theme customisation options. Just mark your form fields with `translatable` property and the plugin will take care about everything else:
+
+ tabs:
+ fields:
+ website_name:
+ tab: Info
+ label: Website Name
+ type: text
+ default: Your website name
+ translatable: true
+
+## Fallback attribute values
+
+By default, untranslated attributes will fall back to the default locale. This behavior can be disabled by calling the `noFallbackLocale` method.
+
+ $user = User::first();
+
+ $user->noFallbackLocale()->lang('fr');
+
+ // Returns NULL if there is no French translation
+ $user->name;
+
+## Indexed attributes
+
+Translatable model attributes can also be declared as an index by passing the `$transatable` attribute value as an array. The first value is the attribute name, the other values represent options, in this case setting the option `index` to `true`.
+
+ public $translatable = [
+ 'name',
+ ['slug', 'index' => true]
+ ];
+
+Once an attribute is indexed, you may use the `transWhere` method to apply a basic query to the model.
+
+ Post::transWhere('slug', 'hello-world')->first();
+
+The `transWhere` method accepts a third argument to explicitly pass a locale value, otherwise it will be detected from the environment.
+
+ Post::transWhere('slug', 'hello-world', 'en')->first();
+
+## URL translation
+
+Pages in the CMS support translating the URL property. Assuming you have 3 languages set up:
+
+- en: English
+- fr: French
+- ru: Russian
+
+There is a page with the following content:
+
+ url = "/contact"
+
+ [viewBag]
+ localeUrl[ru] = "/контакт"
+ ==
+ Page content
+
+The word "Contact" in French is the same so a translated URL is not given, or needed. If the page has no URL override specified, then the default URL will be used. Pages will not be duplicated for a given language.
+
+- /fr/contact - Page in French
+- /en/contact - Page in English
+- /ru/контакт - Page in Russian
+- /ru/contact - 404
+
+## URL parameter translation
+
+It's possible to translate URL parameters by listening to the `translate.localePicker.translateParams` event, which is fired when switching languages.
+
+ Event::listen('translate.localePicker.translateParams', function($page, $params, $oldLocale, $newLocale) {
+ if ($page->baseFileName == 'your-page-filename') {
+ return YourModel::translateParams($params, $oldLocale, $newLocale);
+ }
+ });
+
+In YourModel, one possible implementation might look like this:
+
+ public static function translateParams($params, $oldLocale, $newLocale) {
+ $newParams = $params;
+ foreach ($params as $paramName => $paramValue) {
+ $records = self::transWhere($paramName, $paramValue, $oldLocale)->first();
+ if ($records) {
+ $records->translateContext($newLocale);
+ $newParams[$paramName] = $records->$paramName;
+ }
+ }
+ return $newParams;
+ }
+
+## Query string translation
+
+It's possible to translate query string parameters by listening to the `translate.localePicker.translateQuery` event, which is fired when switching languages.
+
+ Event::listen('translate.localePicker.translateQuery', function($page, $params, $oldLocale, $newLocale) {
+ if ($page->baseFileName == 'your-page-filename') {
+ return YourModel::translateParams($params, $oldLocale, $newLocale);
+ }
+ });
+
+For a possible implementation of the `YourModel::translateParams` method look at the example under `URL parameter translation` from above.
+
+## Extend theme scan
+
+ Event::listen('rainlab.translate.themeScanner.afterScan', function (ThemeScanner $scanner) {
+ ...
+ });
+
+## Settings model translation
+
+It's possible to translate your settings model like any other model. To retrieve translated values use:
+
+ Settings::instance()->getAttributeTranslated('your_attribute_name');
+
+## Conditionally extending plugins
+
+#### Models
+
+It is possible to conditionally extend a plugin's models to support translation by placing an `@` symbol before the behavior definition. This is a soft implement will only use `TranslatableModel` if the Translate plugin is installed, otherwise it will not cause any errors.
+
+ /**
+ * Blog Post Model
+ */
+ class Post extends Model
+ {
+
+ [...]
+
+ /**
+ * Softly implement the TranslatableModel behavior.
+ */
+ public $implement = ['@RainLab.Translate.Behaviors.TranslatableModel'];
+
+ /**
+ * @var array Attributes that support translation, if available.
+ */
+ public $translatable = ['title'];
+
+ [...]
+
+ }
+
+The back-end forms will automatically detect the presence of translatable fields and replace their controls for multilingual equivalents.
+
+#### Messages
+
+Since the Twig filter will not be available all the time, we can pipe them to the native Laravel translation methods instead. This ensures translated messages will always work on the front end.
+
+ /**
+ * Register new Twig variables
+ * @return array
+ */
+ public function registerMarkupTags()
+ {
+ // Check the translate plugin is installed
+ if (!class_exists('RainLab\Translate\Behaviors\TranslatableModel'))
+ return;
+
+ return [
+ 'filters' => [
+ '_' => ['Lang', 'get'],
+ '__' => ['Lang', 'choice'],
+ ]
+ ];
+ }
+
+# User Interface
+
+#### Switching locales
+
+Users can switch between locales by clicking on the locale indicator on the right hand side of the Multi-language input. By holding the CMD / CTRL key all Multi-language Input fields will switch to the selected locale.
+
+## Integration without jQuery and October Framework files
+
+It is possible to use the front-end language switcher without using jQuery or the OctoberCMS AJAX Framework by making the AJAX API request yourself manually. The following is an example of how to do that.
+
+ document.querySelector('#languageSelect').addEventListener('change', function () {
+ const details = {
+ _session_key: document.querySelector('input[name="_session_key"]').value,
+ _token: document.querySelector('input[name="_token"]').value,
+ locale: this.value
+ }
+
+ let formBody = []
+
+ for (var property in details) {
+ let encodedKey = encodeURIComponent(property)
+ let encodedValue = encodeURIComponent(details[property])
+ formBody.push(encodedKey + '=' + encodedValue)
+ }
+
+ formBody = formBody.join('&')
+
+ fetch(location.href + '/', {
+ method: 'POST',
+ body: formBody,
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
+ 'X-OCTOBER-REQUEST-HANDLER': 'onSwitchLocale',
+ 'X-OCTOBER-REQUEST-PARTIALS': '',
+ 'X-Requested-With': 'XMLHttpRequest'
+ }
+ })
+ .then(res => res.json())
+ .then(res => window.location.replace(res.X_OCTOBER_REDIRECT))
+ .catch(err => console.log(err))
+ })
+
+The HTML:
+
+ {{ form_open() }}
+
+
+ {% for code, name in locales %}
+ {% if code != activeLocale %}
+ {{code|upper}}
+ {% endif %}
+ {% endfor %}
+
+ {{ form_close() }}
diff --git a/plugins/rainlab/translate/assets/css/messages.css b/plugins/rainlab/translate/assets/css/messages.css
new file mode 100644
index 00000000..13b13fab
--- /dev/null
+++ b/plugins/rainlab/translate/assets/css/messages.css
@@ -0,0 +1,17 @@
+#messagesContainer{padding-top:20px}
+.translate-messages{position:relative;margin-top:-20px}
+.translate-messages .dropdown-button-placeholder{display:block;width:1px;height:20px}
+.translate-messages .dropdown-to,
+.translate-messages .dropdown-from{position:absolute;top:57px}
+.translate-messages .dropdown-to{left:50%}
+.translate-messages .dropdown-from{left:0}
+.translate-messages .no-other-languages{text-align:center;padding:5px}
+.translate-messages .header-language,
+.translate-messages .header-swap-languages,
+.translate-messages .header-hide-translated{line-height:27px}
+.translate-messages .header-language{float:left;cursor:pointer}
+.translate-messages .header-language span.is-default{text-transform:none}
+.translate-messages .header-swap-languages{margin-right:10px;float:right;font-size:16px;cursor:pointer}
+.translate-messages .header-hide-translated{float:right;text-align:right}
+.translate-messages .header-hide-translated label{font-weight:normal;margin-bottom:0;margin-right:10px;font-size:12px;white-space:normal}
+.translate-messages .header-hide-translated label:before{top:5px;left:0}
\ No newline at end of file
diff --git a/plugins/rainlab/translate/assets/css/multilingual-v1.css b/plugins/rainlab/translate/assets/css/multilingual-v1.css
new file mode 100644
index 00000000..aa2ed8be
--- /dev/null
+++ b/plugins/rainlab/translate/assets/css/multilingual-v1.css
@@ -0,0 +1,13 @@
+/*
+ * Legacy styles for October v1.0
+ */
+
+/* Fancy layout */
+
+.fancy-layout .form-tabless-fields .field-multilingual .ml-btn {
+ color: rgba(255,255,255,0.8);
+}
+
+.fancy-layout .form-tabless-fields .field-multilingual .ml-btn:hover {
+ color: #fff;
+}
diff --git a/plugins/rainlab/translate/assets/css/multilingual.css b/plugins/rainlab/translate/assets/css/multilingual.css
new file mode 100644
index 00000000..6ecaba1c
--- /dev/null
+++ b/plugins/rainlab/translate/assets/css/multilingual.css
@@ -0,0 +1,23 @@
+.field-multilingual{position:relative}
+.field-multilingual .ml-btn{background:transparent;position:absolute;color:#7b7b7b;text-transform:uppercase;font-size:11px;letter-spacing:1px;width:44px;padding-right:0;-webkit-box-shadow:none;box-shadow:none;text-shadow:none;z-index:2}
+.field-multilingual .ml-btn:hover{color:#555}
+.field-multilingual.field-multilingual-text .form-control{padding-right:44px}
+.field-multilingual.field-multilingual-text .ml-btn{right:4px;top:50%;margin-top:-44px;height:88px}
+.field-multilingual.field-multilingual-textarea textarea{padding-right:30px}
+.field-multilingual.field-multilingual-textarea .ml-btn{right:25px;top:5px;width:24px;text-align:right;padding-left:4px}
+.field-multilingual.field-multilingual-textarea .ml-dropdown-menu{top:39px;right:1px}
+.field-multilingual.field-multilingual-richeditor .ml-btn{top:43px;right:25px;text-align:right}
+.field-multilingual.field-multilingual-richeditor .ml-dropdown-menu{top:74px;right:12px}
+.field-multilingual.field-multilingual-markdowneditor .ml-btn{top:43px;right:25px;text-align:right}
+.field-multilingual.field-multilingual-markdowneditor .ml-dropdown-menu{top:74px !important;right:12px}
+.field-multilingual.field-multilingual-repeater .ml-btn{top:-27px;right:11px;text-align:right}
+.field-multilingual.field-multilingual-repeater .ml-dropdown-menu{top:0;right:0}
+.field-multilingual.field-multilingual-repeater.is-empty{padding-top:5px}
+.field-multilingual.field-multilingual-repeater.is-empty .ml-btn{top:-10px;right:5px;text-align:right}
+.field-multilingual.field-multilingual-repeater.is-empty .ml-dropdown-menu{top:15px;right:-7px}
+.field-multilingual.field-multilingual-nestedform .ml-btn{top:-3px;right:11px;text-align:right}
+.field-multilingual.field-multilingual-nestedform .ml-dropdown-menu{top:24px;right:-2px}
+.field-multilingual.field-multilingual-nestedform.is-paneled .ml-btn{top:7px;right:15px}
+.field-multilingual.field-multilingual-nestedform.is-paneled .ml-dropdown-menu{top:34px;right:2px}
+.fancy-layout .field-multilingual-text input.form-control{padding-right:44px}
+.help-block.before-field + .field-multilingual.field-multilingual-textarea .ml-btn{top:-41px}
\ No newline at end of file
diff --git a/plugins/rainlab/translate/assets/js/locales.js b/plugins/rainlab/translate/assets/js/locales.js
new file mode 100644
index 00000000..10e652a2
--- /dev/null
+++ b/plugins/rainlab/translate/assets/js/locales.js
@@ -0,0 +1,28 @@
+/*
+ * Scripts for the Locales controller.
+ */
++function ($) { "use strict";
+
+ var TranslateLocales = function() {
+
+ this.clickRecord = function(recordId) {
+ var newPopup = $(' ')
+
+ newPopup.popup({
+ handler: 'onUpdateForm',
+ extraData: {
+ 'record_id': recordId,
+ }
+ })
+ }
+
+ this.createRecord = function() {
+ var newPopup = $(' ')
+ newPopup.popup({ handler: 'onCreateForm' })
+ }
+
+ }
+
+ $.translateLocales = new TranslateLocales;
+
+}(window.jQuery);
\ No newline at end of file
diff --git a/plugins/rainlab/translate/assets/js/messages.js b/plugins/rainlab/translate/assets/js/messages.js
new file mode 100644
index 00000000..74648fd8
--- /dev/null
+++ b/plugins/rainlab/translate/assets/js/messages.js
@@ -0,0 +1,165 @@
+/*
+ * Scripts for the Messages controller.
+ */
++function ($) { "use strict";
+
+ var TranslateMessages = function() {
+ var self = this
+
+ this.$form = null
+
+ /*
+ * Table toolbar
+ */
+ this.tableToolbar = null
+
+ /*
+ * Input with the "from" locale value
+ */
+ this.fromInput = null
+
+ /*
+ * Template for the "from" header (title)
+ */
+ this.fromHeader = null
+
+ /*
+ * Input with the "to" locale value
+ */
+ this.toInput = null
+
+ /*
+ * Template for the "to" header (title)
+ */
+ this.toHeader = null
+
+ /*
+ * Template for the "found" header (title)
+ */
+ this.foundHeader = null
+
+ /*
+ * The table widget element
+ */
+ this.tableElement = null
+
+ /*
+ * Hide translated strings (show only from the empty data set)
+ */
+ this.hideTranslated = false
+
+ /*
+ * Data sets, complete and untranslated (empty)
+ */
+ this.emptyDataSet = null
+ this.dataSet = null
+
+ $(document).on('change', '#hideTranslated', function(){
+ self.toggleTranslated($(this).is(':checked'))
+ self.refreshTable()
+ });
+
+ $(document).on('keyup', '.control-table input.string-input', function(ev) {
+ self.onApplyValue(ev)
+ });
+
+ this.toggleTranslated = function(isHide) {
+ this.hideTranslated = isHide
+ this.setTitleContents()
+ }
+
+ this.setToolbarContents = function(tableToolbar) {
+ if (tableToolbar) this.tableToolbar = $(tableToolbar)
+ if (!this.tableElement) return
+
+ var $toolbar = $('.toolbar', this.tableElement)
+ if ($toolbar.hasClass('message-buttons-added')) {
+ return
+ }
+
+ $toolbar.addClass('message-buttons-added')
+ $toolbar.prepend(Mustache.render(this.tableToolbar.html()))
+ }
+
+ this.setTitleContents = function(fromEl, toEl, foundEl) {
+ if (fromEl) this.fromHeader = $(fromEl)
+ if (toEl) this.toHeader = $(toEl)
+ if (foundEl) this.foundHeader = $(foundEl)
+ if (!this.tableElement) return
+
+ var $headers = $('table.headers th', this.tableElement)
+ $headers.eq(0).html(this.fromHeader.html())
+ $headers.eq(1).html(Mustache.render(this.toHeader.html(), { hideTranslated: this.hideTranslated } ))
+ $headers.eq(2).html(this.foundHeader.html())
+ }
+
+ this.setTableElement = function(el) {
+ this.tableElement = $(el)
+ this.$form = $('#messagesForm')
+ this.fromInput = this.$form.find('input[name=locale_from]')
+ this.toInput = this.$form.find('input[name=locale_to]')
+
+ this.tableElement.one('oc.tableUpdateData', $.proxy(this.updateTableData, this))
+ }
+
+ this.onApplyValue = function(ev) {
+ if (ev.keyCode == 13) {
+ var $table = $(ev.currentTarget).closest('[data-control=table]')
+
+ if (!$table.length) {
+ return
+ }
+
+ var tableObj = $table.data('oc.table')
+ if (tableObj) {
+ tableObj.setCellValue($(ev.currentTarget).closest('td').get(0), ev.currentTarget.value)
+ tableObj.commitEditedRow()
+ }
+ }
+ }
+
+ this.updateTableData = function(event, records) {
+ if (this.hideTranslated && !records.length) {
+ self.toggleTranslated($(this).is(':checked'))
+ self.refreshTable()
+ }
+ }
+
+ this.toggleDropdown = function(el) {
+ setTimeout(function(){ $(el).dropdown('toggle') }, 1)
+ return false
+ }
+
+ this.setLanguage = function(type, code) {
+ if (type == 'to')
+ this.toInput.val(code)
+ else if (type == 'from')
+ this.fromInput.val(code)
+
+ this.refreshGrid()
+ return false
+ }
+
+ this.swapLanguages = function() {
+ var from = this.fromInput.val(),
+ to = this.toInput.val()
+
+ this.toggleTranslated(false)
+ this.fromInput.val(to)
+ this.toInput.val(from)
+ this.refreshGrid()
+ }
+
+ this.refreshGrid = function() {
+ this.$form.request('onRefresh')
+ }
+
+ this.refreshTable = function() {
+ this.tableElement.table('updateDataTable')
+ }
+
+ }
+
+ $.translateMessages = new TranslateMessages;
+
+}(window.jQuery);
diff --git a/plugins/rainlab/translate/assets/js/multilingual.js b/plugins/rainlab/translate/assets/js/multilingual.js
new file mode 100644
index 00000000..2fd6bc33
--- /dev/null
+++ b/plugins/rainlab/translate/assets/js/multilingual.js
@@ -0,0 +1,145 @@
+/*
+ * Multi lingual control plugin
+ *
+ * Data attributes:
+ * - data-control="multilingual" - enables the plugin on an element
+ * - data-default-locale="en" - default locale code
+ * - data-placeholder-field="#placeholderField" - an element that contains the placeholder value
+ *
+ * JavaScript API:
+ * $('a#someElement').multiLingual({ option: 'value' })
+ *
+ * Dependences:
+ * - Nil
+ */
+
++function ($) { "use strict";
+
+ // MULTILINGUAL CLASS DEFINITION
+ // ============================
+
+ var MultiLingual = function(element, options) {
+ var self = this
+ this.options = options
+ this.$el = $(element)
+
+ this.$activeField = null
+ this.$activeButton = $('[data-active-locale]', this.$el)
+ this.$dropdown = $('ul.ml-dropdown-menu', this.$el)
+ this.$placeholder = $(this.options.placeholderField)
+
+ /*
+ * Init locale
+ */
+ this.activeLocale = this.options.defaultLocale
+ this.$activeField = this.getLocaleElement(this.activeLocale)
+ this.$activeButton.text(this.activeLocale)
+
+ this.$dropdown.on('click', '[data-switch-locale]', this.$activeButton, function(event){
+ var currentLocale = event.data.text();
+ var selectedLocale = $(this).data('switch-locale')
+
+ // only call setLocale() if locale has changed
+ if (selectedLocale != currentLocale) {
+ self.setLocale(selectedLocale)
+ }
+
+ /*
+ * If Ctrl/Cmd key is pressed, find other instances and switch
+ */
+ if (event.ctrlKey || event.metaKey) {
+ event.preventDefault();
+ $('[data-switch-locale="'+selectedLocale+'"]').click()
+ }
+ })
+
+ this.$placeholder.on('input', function(){
+ self.$activeField.val(this.value)
+ })
+
+ /*
+ * Handle oc.inputPreset.beforeUpdate event
+ */
+ $('[data-input-preset]', this.$el).on('oc.inputPreset.beforeUpdate', function(event, src) {
+ var sourceLocale = src.siblings('.ml-btn[data-active-locale]').text()
+ var targetLocale = $(this).data('locale-value')
+ var targetActiveLocale = $(this).siblings('.ml-btn[data-active-locale]').text()
+
+ if (sourceLocale && targetLocale && targetActiveLocale) {
+ if (targetActiveLocale !== sourceLocale)
+ self.setLocale(sourceLocale)
+ $(this).data('update', sourceLocale === targetLocale)
+ }
+ })
+ }
+
+ MultiLingual.DEFAULTS = {
+ defaultLocale: 'en',
+ defaultField: null,
+ placeholderField: null
+ }
+
+ MultiLingual.prototype.getLocaleElement = function(locale) {
+ var el = this.$el.find('[data-locale-value="'+locale+'"]')
+ return el.length ? el : null
+ }
+
+ MultiLingual.prototype.getLocaleValue = function(locale) {
+ var value = this.getLocaleElement(locale)
+ return value ? value.val() : null
+ }
+
+ MultiLingual.prototype.setLocaleValue = function(value, locale) {
+ if (locale) {
+ this.getLocaleElement(locale).val(value)
+ }
+ else {
+ this.$activeField.val(value)
+ }
+ }
+
+ MultiLingual.prototype.setLocale = function(locale) {
+ this.activeLocale = locale
+ this.$activeField = this.getLocaleElement(locale)
+ this.$activeButton.text(locale)
+
+ this.$placeholder.val(this.getLocaleValue(locale))
+ this.$el.trigger('setLocale.oc.multilingual', [locale, this.getLocaleValue(locale)])
+ }
+
+ // MULTILINGUAL PLUGIN DEFINITION
+ // ============================
+
+ var old = $.fn.multiLingual
+
+ $.fn.multiLingual = function (option) {
+ var args = Array.prototype.slice.call(arguments, 1), result
+ this.each(function () {
+ var $this = $(this)
+ var data = $this.data('oc.multilingual')
+ var options = $.extend({}, MultiLingual.DEFAULTS, $this.data(), typeof option == 'object' && option)
+ if (!data) $this.data('oc.multilingual', (data = new MultiLingual(this, options)))
+ if (typeof option == 'string') result = data[option].apply(data, args)
+ if (typeof result != 'undefined') return false
+ })
+
+ return result ? result : this
+ }
+
+ $.fn.multiLingual.Constructor = MultiLingual
+
+ // MULTILINGUAL NO CONFLICT
+ // =================
+
+ $.fn.multiLingual.noConflict = function () {
+ $.fn.multiLingual = old
+ return this
+ }
+
+ // MULTILINGUAL DATA-API
+ // ===============
+ $(document).render(function () {
+ $('[data-control="multilingual"]').multiLingual()
+ })
+
+}(window.jQuery);
diff --git a/plugins/rainlab/translate/assets/less/messages.less b/plugins/rainlab/translate/assets/less/messages.less
new file mode 100644
index 00000000..7a76a046
--- /dev/null
+++ b/plugins/rainlab/translate/assets/less/messages.less
@@ -0,0 +1,72 @@
+#messagesContainer {
+ padding-top: 20px;
+}
+
+.translate-messages {
+ position: relative;
+ margin-top: -20px;
+
+ .dropdown-button-placeholder {
+ display:block;
+ width: 1px;
+ height: 20px
+ }
+
+ .dropdown-to, .dropdown-from {
+ position: absolute;
+ top: 57px;
+ }
+
+ .dropdown-to {
+ left: 50%;
+ }
+
+ .dropdown-from {
+ left: 0;
+ }
+
+ .no-other-languages {
+ text-align: center;
+ padding: 5px;
+ }
+
+ .header-language,
+ .header-swap-languages,
+ .header-hide-translated {
+ line-height: 27px;
+ }
+
+ .header-language {
+ float: left;
+ cursor: pointer;
+
+ span.is-default {
+ text-transform: none;
+ }
+ }
+
+ .header-swap-languages {
+ margin-right: 10px;
+ float: right;
+ font-size: 16px;
+ cursor: pointer;
+ }
+
+ .header-hide-translated {
+ float: right;
+ text-align: right;
+
+ label {
+ font-weight: normal;
+ margin-bottom: 0;
+ margin-right: 10px;
+ font-size: 12px;
+ white-space: normal;
+
+ &:before {
+ top: 5px;
+ left: 0;
+ }
+ }
+ }
+}
diff --git a/plugins/rainlab/translate/assets/less/multilingual.less b/plugins/rainlab/translate/assets/less/multilingual.less
new file mode 100644
index 00000000..c4b9e798
--- /dev/null
+++ b/plugins/rainlab/translate/assets/less/multilingual.less
@@ -0,0 +1,148 @@
+@import "../../../../../modules/backend/assets/less/core/boot.less";
+
+@multilingual-btn-color: #555555;
+
+.field-multilingual {
+ position: relative;
+
+ .ml-btn {
+ background: transparent;
+ position: absolute;
+ color: lighten(@multilingual-btn-color, 15%);
+ text-transform: uppercase;
+ font-size: 11px;
+ letter-spacing: 1px;
+ width: 44px;
+ padding-right: 0;
+ .box-shadow(none);
+ text-shadow: none;
+ z-index: 2;
+
+ &:hover {
+ color: @multilingual-btn-color;
+ }
+ }
+
+ &.field-multilingual-text {
+ .form-control {
+ padding-right: 44px;
+ }
+ .ml-btn {
+ right: 4px;
+ top: 50%;
+ margin-top: -44px;
+ height: 88px;
+ }
+ }
+
+ &.field-multilingual-textarea {
+ textarea {
+ // increase padding on the right so that the textarea content does not overlap with the language button
+ padding-right: 30px;
+ }
+
+ .ml-btn {
+ right: 25px;
+ top: 5px;
+ width: 24px;
+ text-align: right;
+ padding-left: 4px;
+ }
+
+ .ml-dropdown-menu {
+ top: 39px;
+ right: 1px;
+ }
+ }
+
+ &.field-multilingual-richeditor {
+ .ml-btn {
+ top: 43px;
+ right: 25px;
+ text-align: right;
+ }
+
+ .ml-dropdown-menu {
+ top: 74px;
+ right: 12px;
+ }
+ }
+
+ &.field-multilingual-markdowneditor {
+ .ml-btn {
+ top: 43px;
+ right: 25px;
+ text-align: right;
+ }
+
+ .ml-dropdown-menu {
+ top: 74px !important;
+ right: 12px;
+ }
+ }
+
+ &.field-multilingual-repeater {
+ .ml-btn {
+ top: -27px;
+ right: 11px;
+ text-align: right;
+ }
+
+ .ml-dropdown-menu {
+ top: 0;
+ right: 0;
+ }
+
+ &.is-empty {
+ padding-top: 5px;
+
+ .ml-btn {
+ top: -10px;
+ right: 5px;
+ text-align: right;
+ }
+
+ .ml-dropdown-menu {
+ top: 15px;
+ right: -7px;
+ }
+ }
+ }
+
+ &.field-multilingual-nestedform {
+ .ml-btn {
+ top: -3px;
+ right: 11px;
+ text-align: right;
+ }
+
+ .ml-dropdown-menu {
+ top: 24px;
+ right: -2px;
+ }
+
+ &.is-paneled {
+ .ml-btn {
+ top: 7px;
+ right: 15px;
+ }
+
+ .ml-dropdown-menu {
+ top: 34px;
+ right: 2px;
+ }
+ }
+ }
+}
+
+.fancy-layout {
+ .field-multilingual-text input.form-control {
+ padding-right: 44px;
+ }
+}
+
+.help-block.before-field + .field-multilingual.field-multilingual-textarea {
+ .ml-btn {
+ top: -41px;
+ }
+}
diff --git a/plugins/rainlab/translate/behaviors/TranslatableCmsObject.php b/plugins/rainlab/translate/behaviors/TranslatableCmsObject.php
new file mode 100644
index 00000000..f327f2a0
--- /dev/null
+++ b/plugins/rainlab/translate/behaviors/TranslatableCmsObject.php
@@ -0,0 +1,230 @@
+model->bindEvent('cmsObject.fillViewBagArray', function() {
+ $this->mergeViewBagAttributes();
+ });
+
+ $this->model->bindEvent('cmsObject.getTwigCacheKey', function($key) {
+ return $this->overrideTwigCacheKey($key);
+ });
+
+ // delete all translation files associated with the default language static page
+ $this->model->bindEvent('model.afterDelete', function() use ($model) {
+ foreach (Locale::listEnabled() as $locale => $label) {
+ if ($locale == $this->translatableDefault) {
+ continue;
+ }
+ if ($obj = $this->getCmsObjectForLocale($locale)) {
+ $obj->delete();
+ }
+ }
+ });
+ }
+
+ /**
+ * Merge the viewBag array for the base and translated objects.
+ * @return void
+ */
+ protected function mergeViewBagAttributes()
+ {
+ $locale = $this->translatableContext;
+
+ if (!array_key_exists($locale, $this->translatableAttributes)) {
+ $this->loadTranslatableData($locale);
+ }
+
+ if (isset($this->translatableViewBag[$locale])) {
+ $this->model->viewBag = array_merge(
+ $this->model->viewBag,
+ $this->translatableViewBag[$locale]
+ );
+ }
+ }
+
+ /**
+ * Translated CMS objects need their own unique cache key in twig.
+ * @return string|null
+ */
+ protected function overrideTwigCacheKey($key)
+ {
+ if (!$locale = $this->translatableContext) {
+ return null;
+ }
+
+ return $key . '-' . $locale;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function syncTranslatableAttributes()
+ {
+ parent::syncTranslatableAttributes();
+
+ if ($this->model->isDirty('fileName')) {
+ $this->syncTranslatableFileNames();
+ }
+ }
+
+ /**
+ * If the parent model file name is changed, this should
+ * be reflected in the translated models also.
+ */
+ protected function syncTranslatableFileNames()
+ {
+ $knownLocales = array_keys($this->translatableAttributes);
+ foreach ($knownLocales as $locale) {
+ if ($locale == $this->translatableDefault) {
+ continue;
+ }
+
+ if ($obj = $this->getCmsObjectForLocale($locale)) {
+ $obj->fileName = $this->model->fileName;
+ $obj->forceSave();
+ }
+ }
+ }
+
+ /**
+ * Saves the translation data in the join table.
+ * @param string $locale
+ * @return void
+ */
+ protected function storeTranslatableData($locale = null)
+ {
+ if (!$locale) {
+ $locale = $this->translatableContext;
+ }
+
+ /*
+ * Model doesn't exist yet, defer this logic in memory
+ */
+ if (!$this->model->exists) {
+ $this->model->bindEventOnce('model.afterCreate', function() use ($locale) {
+ $this->storeTranslatableData($locale);
+ });
+
+ return;
+ }
+
+ $data = $this->translatableAttributes[$locale];
+
+ if (!$obj = $this->getCmsObjectForLocale($locale)) {
+ $model = $this->createModel();
+ $obj = $model::forLocale($locale, $this->model);
+ $obj->fileName = $this->model->fileName;
+ }
+
+ if (!$this->isEmptyDataSet($data)) {
+ $obj->fill($data);
+ $obj->forceSave();
+ }
+ }
+
+ /**
+ * Returns true if all attributes are empty (false when converted to booleans).
+ * @param array $data
+ * @return bool
+ */
+ protected function isEmptyDataSet($data)
+ {
+ return !array_get($data, 'markup') &&
+ !count(array_filter(array_get($data, 'viewBag', []))) &&
+ !count(array_filter(array_get($data, 'placeholders', [])));
+ }
+
+ /**
+ * Loads the translation data from the join table.
+ * @param string $locale
+ * @return array
+ */
+ protected function loadTranslatableData($locale = null)
+ {
+ if (!$locale) {
+ $locale = $this->translatableContext;
+ }
+
+ if (!$this->model->exists) {
+ return $this->translatableAttributes[$locale] = [];
+ }
+
+ $obj = $this->getCmsObjectForLocale($locale);
+
+ $result = $obj ? $obj->getAttributes() : [];
+
+ $this->translatableViewBag[$locale] = $obj ? $obj->viewBag : [];
+
+ return $this->translatableOriginals[$locale] = $this->translatableAttributes[$locale] = $result;
+ }
+
+ protected function getCmsObjectForLocale($locale)
+ {
+ if ($locale == $this->translatableDefault) {
+ return $this->model;
+ }
+
+ $model = $this->createModel();
+ return $model::findLocale($locale, $this->model);
+ }
+
+ /**
+ * Internal method, prepare the form model object
+ * @return Model
+ */
+ protected function createModel()
+ {
+ $class = $this->getTranslatableModelClass();
+ $model = new $class;
+ return $model;
+ }
+
+ /**
+ * Returns a collection of fields that will be hashed.
+ * @return array
+ */
+ public function getTranslatableModelClass()
+ {
+ if (property_exists($this->model, 'translatableModel')) {
+ return $this->model->translatableModel;
+ }
+
+ return 'RainLab\Translate\Classes\MLCmsObject';
+ }
+}
diff --git a/plugins/rainlab/translate/behaviors/TranslatableModel.php b/plugins/rainlab/translate/behaviors/TranslatableModel.php
new file mode 100644
index 00000000..36091b2a
--- /dev/null
+++ b/plugins/rainlab/translate/behaviors/TranslatableModel.php
@@ -0,0 +1,319 @@
+morphMany['translations'] = [
+ 'RainLab\Translate\Models\Attribute',
+ 'name' => 'model'
+ ];
+
+ // October v2.0
+ if (class_exists('System')) {
+ $this->extendFileModels('attachOne');
+ $this->extendFileModels('attachMany');
+ }
+
+ // Clean up indexes when this model is deleted
+ $model->bindEvent('model.afterDelete', function() use ($model) {
+ Db::table('rainlab_translate_attributes')
+ ->where('model_id', $model->getKey())
+ ->where('model_type', get_class($model))
+ ->delete();
+
+ Db::table('rainlab_translate_indexes')
+ ->where('model_id', $model->getKey())
+ ->where('model_type', get_class($model))
+ ->delete();
+ });
+ }
+
+ /**
+ * extendFileModels will swap the standard File model with MLFile instead
+ */
+ protected function extendFileModels(string $relationGroup)
+ {
+ foreach ($this->model->$relationGroup as $relationName => $relationObj) {
+ $relationClass = is_array($relationObj) ? $relationObj[0] : $relationObj;
+ if ($relationClass === \System\Models\File::class) {
+ if (is_array($relationObj)) {
+ $this->model->$relationGroup[$relationName][0] = \RainLab\Translate\Models\MLFile::class;
+ }
+ else {
+ $this->model->$relationGroup[$relationName] = \RainLab\Translate\Models\MLFile::class;
+ }
+ }
+ }
+ }
+
+ /**
+ * Applies a translatable index to a basic query. This scope will join the index
+ * table and can be executed neither more than once, nor with scopeTransOrder.
+ * @param Builder $query
+ * @param string $index
+ * @param string $value
+ * @param string $locale
+ * @return Builder
+ */
+ public function scopeTransWhere($query, $index, $value, $locale = null, $operator = '=')
+ {
+ if (!$locale) {
+ $locale = $this->translatableContext;
+ }
+
+ // Separate query into two separate queries for improved performance
+ // @see https://github.com/rainlab/translate-plugin/pull/623
+ $translateIndexes = Db::table('rainlab_translate_indexes')
+ ->where('rainlab_translate_indexes.model_type', '=', $this->getClass())
+ ->where('rainlab_translate_indexes.locale', '=', $locale)
+ ->where('rainlab_translate_indexes.item', $index)
+ ->where('rainlab_translate_indexes.value', $operator, $value)
+ ->pluck('model_id');
+
+ if ($translateIndexes->count()) {
+ $query->whereIn($this->model->getQualifiedKeyName(), $translateIndexes);
+ } else {
+ $query->where($index, $operator, $value);
+ }
+
+ return $query;
+ }
+
+ /**
+ * Applies a sort operation with a translatable index to a basic query. This scope will join the index table.
+ * @param Builder $query
+ * @param string $index
+ * @param string $direction
+ * @param string $locale
+ * @return Builder
+ */
+ public function scopeTransOrderBy($query, $index, $direction = 'asc', $locale = null)
+ {
+ if (!$locale) {
+ $locale = $this->translatableContext;
+ }
+ $indexTableAlias = 'rainlab_translate_indexes_' . $index . '_' . $locale;
+
+ $query->select(
+ $this->model->getTable().'.*',
+ Db::raw('COALESCE(' . $indexTableAlias . '.value, '. $this->model->getTable() .'.'.$index.') AS translate_sorting_key')
+ );
+
+ $query->orderBy('translate_sorting_key', $direction);
+
+ $this->joinTranslateIndexesTable($query, $locale, $index, $indexTableAlias);
+
+ return $query;
+ }
+
+ /**
+ * Joins the translatable indexes table to a query.
+ * @param Builder $query
+ * @param string $locale
+ * @param string $indexTableAlias
+ * @return Builder
+ */
+ protected function joinTranslateIndexesTable($query, $locale, $index, $indexTableAlias)
+ {
+ $joinTableWithAlias = 'rainlab_translate_indexes as ' . $indexTableAlias;
+ // check if table with same name and alias is already joined
+ if (collect($query->getQuery()->joins)->contains('table', $joinTableWithAlias)) {
+ return $query;
+ }
+
+ $query->leftJoin($joinTableWithAlias, function($join) use ($locale, $index, $indexTableAlias) {
+ $join
+ ->on(Db::raw(DbDongle::cast($this->model->getQualifiedKeyName(), 'TEXT')), '=', $indexTableAlias . '.model_id')
+ ->where($indexTableAlias . '.model_type', '=', $this->getClass())
+ ->where($indexTableAlias . '.item', '=', $index)
+ ->where($indexTableAlias . '.locale', '=', $locale);
+ });
+
+ return $query;
+ }
+
+ /**
+ * Saves the translation data in the join table.
+ * @param string $locale
+ * @return void
+ */
+ protected function storeTranslatableData($locale = null)
+ {
+ if (!$locale) {
+ $locale = $this->translatableContext;
+ }
+
+ /*
+ * Model doesn't exist yet, defer this logic in memory
+ */
+ if (!$this->model->exists) {
+ $this->model->bindEventOnce('model.afterCreate', function() use ($locale) {
+ $this->storeTranslatableData($locale);
+ });
+
+ return;
+ }
+
+ /**
+ * @event model.translate.resolveComputedFields
+ * Resolve computed fields before saving
+ *
+ * Example usage:
+ *
+ * Override Model's __construct method
+ *
+ * public function __construct(array $attributes = [])
+ * {
+ * parent::__construct($attributes);
+ *
+ * $this->bindEvent('model.translate.resolveComputedFields', function ($locale) {
+ * return [
+ * 'content_html' =>
+ * self::formatHtml($this->asExtension('TranslatableModel')
+ * ->getAttributeTranslated('content', $locale))
+ * ];
+ * });
+ * }
+ *
+ */
+ $computedFields = $this->model->fireEvent('model.translate.resolveComputedFields', [$locale], true);
+ if (is_array($computedFields)) {
+ $this->translatableAttributes[$locale] = array_merge($this->translatableAttributes[$locale], $computedFields);
+ }
+
+ $this->storeTranslatableBasicData($locale);
+ $this->storeTranslatableIndexData($locale);
+ }
+
+ /**
+ * Saves the basic translation data in the join table.
+ * @param string $locale
+ * @return void
+ */
+ protected function storeTranslatableBasicData($locale = null)
+ {
+ $data = json_encode($this->translatableAttributes[$locale], JSON_UNESCAPED_UNICODE);
+
+ $obj = Db::table('rainlab_translate_attributes')
+ ->where('locale', $locale)
+ ->where('model_id', $this->model->getKey())
+ ->where('model_type', $this->getClass());
+
+ if ($obj->count() > 0) {
+ $obj->update(['attribute_data' => $data]);
+ }
+ else {
+ Db::table('rainlab_translate_attributes')->insert([
+ 'locale' => $locale,
+ 'model_id' => $this->model->getKey(),
+ 'model_type' => $this->getClass(),
+ 'attribute_data' => $data
+ ]);
+ }
+ }
+
+ /**
+ * Saves the indexed translation data in the join table.
+ * @param string $locale
+ * @return void
+ */
+ protected function storeTranslatableIndexData($locale = null)
+ {
+ $optionedAttributes = $this->getTranslatableAttributesWithOptions();
+ if (!count($optionedAttributes)) {
+ return;
+ }
+
+ $data = $this->translatableAttributes[$locale];
+
+ foreach ($optionedAttributes as $attribute => $options) {
+ if (!array_get($options, 'index', false)) {
+ continue;
+ }
+
+ $value = array_get($data, $attribute);
+
+ $obj = Db::table('rainlab_translate_indexes')
+ ->where('locale', $locale)
+ ->where('model_id', $this->model->getKey())
+ ->where('model_type', $this->getClass())
+ ->where('item', $attribute);
+
+ $recordExists = $obj->count() > 0;
+
+ if (!strlen($value)) {
+ if ($recordExists) {
+ $obj->delete();
+ }
+ continue;
+ }
+
+ if ($recordExists) {
+ $obj->update(['value' => $value]);
+ }
+ else {
+ Db::table('rainlab_translate_indexes')->insert([
+ 'locale' => $locale,
+ 'model_id' => $this->model->getKey(),
+ 'model_type' => $this->getClass(),
+ 'item' => $attribute,
+ 'value' => $value
+ ]);
+ }
+ }
+ }
+
+ /**
+ * Loads the translation data from the join table.
+ * @param string $locale
+ * @return array
+ */
+ protected function loadTranslatableData($locale = null)
+ {
+ if (!$locale) {
+ $locale = $this->translatableContext;
+ }
+
+ if (!$this->model->exists) {
+ return $this->translatableAttributes[$locale] = [];
+ }
+
+ $obj = $this->model->translations->first(function ($value, $key) use ($locale) {
+ return $value->attributes['locale'] === $locale;
+ });
+
+ $result = $obj ? json_decode($obj->attribute_data, true) : [];
+
+ return $this->translatableOriginals[$locale] = $this->translatableAttributes[$locale] = $result;
+ }
+
+ /**
+ * Returns the class name of the model. Takes any
+ * custom morphMap aliases into account.
+ *
+ * @return string
+ */
+ protected function getClass()
+ {
+ return $this->model->getMorphClass();
+ }
+}
diff --git a/plugins/rainlab/translate/behaviors/TranslatablePage.php b/plugins/rainlab/translate/behaviors/TranslatablePage.php
new file mode 100644
index 00000000..4797e29e
--- /dev/null
+++ b/plugins/rainlab/translate/behaviors/TranslatablePage.php
@@ -0,0 +1,140 @@
+model->bindEvent('model.afterFetch', function() {
+ $this->translatableOriginals = $this->getModelAttributes();
+
+ if (!App::runningInBackend()) {
+ $this->rewriteTranslatablePageAttributes();
+ }
+ });
+ }
+
+ public function isTranslatable($key)
+ {
+ if ($key === 'translatable' || $this->translatableDefault == $this->translatableContext) {
+ return false;
+ }
+
+ return in_array($key, $this->model->translatable);
+ }
+
+ public function getTranslatableAttributes()
+ {
+ $attributes = [];
+
+ foreach ($this->model->translatable as $attr) {
+ $attributes[] = 'settings['.$attr.']';
+ }
+
+ return $attributes;
+ }
+
+ public function getModelAttributes()
+ {
+ $attributes = [];
+
+ foreach ($this->model->translatable as $attr) {
+ $attributes[$attr] = $this->model[$attr];
+ }
+
+ return $attributes;
+ }
+
+ public function initTranslatableContext()
+ {
+ parent::initTranslatableContext();
+ $this->translatableOriginals = $this->getModelAttributes();
+ }
+
+ public function rewriteTranslatablePageAttributes($locale = null)
+ {
+ $locale = $locale ?: $this->translatableContext;
+
+ foreach ($this->model->translatable as $attr) {
+ $localeAttr = $this->translatableOriginals[$attr];
+
+ if ($locale != $this->translatableDefault) {
+ $translated = $this->getAttributeTranslated($attr, $locale);
+ $localeAttr = ($translated ?: $this->translatableUseFallback) ? $localeAttr : null;
+ }
+
+ $this->model[$attr] = $localeAttr;
+ }
+ }
+
+ public function getAttributeTranslated($key, $locale = null)
+ {
+ $locale = $locale ?: $this->translatableContext;
+
+ if (strpbrk($key, '[]') !== false) {
+ // Retrieve attr name within brackets (i.e. settings[title] yields title)
+ $key = preg_split("/[\[\]]/", $key)[1];
+ }
+
+ $default = ($locale == $this->translatableDefault || $this->translatableUseFallback)
+ ? array_get($this->translatableOriginals, $key)
+ : '';
+
+ $localeAttr = sprintf('viewBag.locale%s.%s', ucfirst($key), $locale);
+ return array_get($this->model->attributes, $localeAttr, $default);
+ }
+
+ public function setAttributeTranslated($key, $value, $locale = null)
+ {
+ $locale = $locale ?: $this->translatableContext;
+
+ if ($locale == $this->translatableDefault) {
+ return;
+ }
+
+ if (strpbrk($key, '[]') !== false) {
+ // Retrieve attr name within brackets (i.e. settings[title] yields title)
+ $key = preg_split("/[\[\]]/", $key)[1];
+ }
+
+ if ($value == array_get($this->translatableOriginals, $key)) {
+ return;
+ }
+
+ $this->saveTranslation($key, $value, $locale);
+ $this->model->bindEventOnce('model.beforeSave', function() use ($key, $value, $locale) {
+ $this->saveTranslation($key, $value, $locale);
+ });
+ }
+
+ public function saveTranslation($key, $value, $locale)
+ {
+ $localeAttr = sprintf('viewBag.locale%s.%s', ucfirst($key), $locale);
+ if (!$value) {
+ array_forget($this->model->attributes, $localeAttr);
+ }
+ else {
+ array_set($this->model->attributes, $localeAttr, $value);
+ }
+ }
+
+ // Not needed but parent abstract model requires those
+ protected function storeTranslatableData($locale = null) {}
+ protected function loadTranslatableData($locale = null) {}
+}
diff --git a/plugins/rainlab/translate/behaviors/TranslatablePageUrl.php b/plugins/rainlab/translate/behaviors/TranslatablePageUrl.php
new file mode 100644
index 00000000..bbc7147e
--- /dev/null
+++ b/plugins/rainlab/translate/behaviors/TranslatablePageUrl.php
@@ -0,0 +1,173 @@
+model = $model;
+
+ $this->initTranslatableContext();
+
+ $this->model->bindEvent('model.afterFetch', function() {
+ $this->translatableDefaultUrl = $this->getModelUrl();
+
+ if (!App::runningInBackend()) {
+ $this->rewriteTranslatablePageUrl();
+ }
+ });
+ }
+
+ protected function setModelUrl($value)
+ {
+ if ($this->model instanceof \RainLab\Pages\Classes\Page) {
+ array_set($this->model->attributes, 'viewBag.url', $value);
+ }
+ else {
+ $this->model->url = $value;
+ }
+ }
+
+ protected function getModelUrl()
+ {
+ if ($this->model instanceof \RainLab\Pages\Classes\Page) {
+ return array_get($this->model->attributes, 'viewBag.url');
+ }
+ else {
+ return $this->model->url;
+ }
+ }
+
+ /**
+ * Initializes this class, sets the default language code to use.
+ * @return void
+ */
+ public function initTranslatableContext()
+ {
+ $translate = Translator::instance();
+ $this->translatableContext = $translate->getLocale();
+ $this->translatableDefault = $translate->getDefaultLocale();
+ }
+
+ /**
+ * Checks if a translated URL exists and rewrites it, this method
+ * should only be called from the context of front-end.
+ * @return void
+ */
+ public function rewriteTranslatablePageUrl($locale = null)
+ {
+ $locale = $locale ?: $this->translatableContext;
+ $localeUrl = $this->translatableDefaultUrl;
+
+ if ($locale != $this->translatableDefault) {
+ $localeUrl = $this->getSettingsUrlAttributeTranslated($locale) ?: $localeUrl;
+ }
+
+ $this->setModelUrl($localeUrl);
+ }
+
+ /**
+ * Determines if a locale has a translated URL.
+ * @return bool
+ */
+ public function hasTranslatablePageUrl($locale = null)
+ {
+ $locale = $locale ?: $this->translatableContext;
+
+ return strlen($this->getSettingsUrlAttributeTranslated($locale)) > 0;
+ }
+
+ /**
+ * Mutator detected by MLControl
+ * @return string
+ */
+ public function getSettingsUrlAttributeTranslated($locale)
+ {
+ $defaults = ($locale == $this->translatableDefault) ? $this->translatableDefaultUrl : null;
+
+ return array_get($this->model->attributes, 'viewBag.localeUrl.'.$locale, $defaults);
+ }
+
+ /**
+ * Mutator detected by MLControl
+ * @return void
+ */
+ public function setSettingsUrlAttributeTranslated($value, $locale)
+ {
+ if ($locale == $this->translatableDefault) {
+ return;
+ }
+
+ if ($value == $this->translatableDefaultUrl) {
+ return;
+ }
+
+ /*
+ * The CMS controller will purge attributes just before saving, this
+ * will ensure the attributes are injected after this logic.
+ */
+ $this->model->bindEventOnce('model.beforeSave', function() use ($value, $locale) {
+ if (!$value) {
+ array_forget($this->model->attributes, 'viewBag.localeUrl.'.$locale);
+ }
+ else {
+ array_set($this->model->attributes, 'viewBag.localeUrl.'.$locale, $value);
+ }
+ });
+ }
+
+ /**
+ * Mutator detected by MLControl, proxy for Static Pages plugin.
+ * @return string
+ */
+ public function getViewBagUrlAttributeTranslated($locale)
+ {
+ return $this->getSettingsUrlAttributeTranslated($locale);
+ }
+
+ /**
+ * Mutator detected by MLControl, proxy for Static Pages plugin.
+ * @return void
+ */
+ public function setViewBagUrlAttributeTranslated($value, $locale)
+ {
+ $this->setSettingsUrlAttributeTranslated($value, $locale);
+ }
+}
diff --git a/plugins/rainlab/translate/classes/EventRegistry.php b/plugins/rainlab/translate/classes/EventRegistry.php
new file mode 100644
index 00000000..262bf917
--- /dev/null
+++ b/plugins/rainlab/translate/classes/EventRegistry.php
@@ -0,0 +1,408 @@
+code ?? null;
+
+ $properties = [];
+ foreach ($locales as $locale => $label) {
+ if ($locale == $defaultLocale) {
+ continue;
+ }
+
+ $properties[] = [
+ 'property' => 'localeUrl.'.$locale,
+ 'title' => 'cms::lang.editor.url',
+ 'tab' => $label,
+ 'type' => 'string',
+ ];
+
+ $properties[] = [
+ 'property' => 'localeTitle.'.$locale,
+ 'title' => 'cms::lang.editor.title',
+ 'tab' => $label,
+ 'type' => 'string',
+ ];
+
+ $properties[] = [
+ 'property' => 'localeDescription.'.$locale,
+ 'title' => 'cms::lang.editor.description',
+ 'tab' => $label,
+ 'type' => 'text',
+ ];
+
+ $properties[] = [
+ 'property' => 'localeMeta_title.'.$locale,
+ 'title' => 'cms::lang.editor.meta_title',
+ 'tab' => $label,
+ 'type' => 'string',
+ ];
+
+ $properties[] = [
+ 'property' => 'localeMeta_description.'.$locale,
+ 'title' => 'cms::lang.editor.meta_description',
+ 'tab' => $label,
+ 'type' => 'text',
+ ];
+ }
+
+ $dataHolder->buttons[] = [
+ 'button' => 'rainlab.translate::lang.plugin.name',
+ 'icon' => 'octo-icon-globe',
+ 'popupTitle' => 'Translate Page Properties',
+ 'properties' => $properties
+ ];
+ }
+
+ //
+ // Utility
+ //
+
+ /**
+ * registerFormFieldReplacements
+ */
+ public function registerFormFieldReplacements($widget)
+ {
+ // Replace with ML Controls for translatable attributes
+ $this->registerModelTranslation($widget);
+
+ // Handle URL translations
+ $this->registerPageUrlTranslation($widget);
+
+ // Handle RainLab.Pages MenuItem translations
+ if (PluginManager::instance()->exists('RainLab.Pages')) {
+ $this->registerMenuItemTranslation($widget);
+ }
+ }
+
+ /**
+ * registerMenuItemTranslation for RainLab.Pages MenuItem data
+ * @param Backend\Widgets\Form $widget
+ */
+ public function registerMenuItemTranslation($widget)
+ {
+ if ($widget->model instanceof \RainLab\Pages\Classes\MenuItem) {
+ $defaultLocale = LocaleModel::getDefault();
+ $availableLocales = LocaleModel::listAvailable();
+ $fieldsToTranslate = ['title', 'url'];
+
+ // Replace specified fields with multilingual versions
+ foreach ($fieldsToTranslate as $fieldName) {
+ $widget->fields[$fieldName]['type'] = 'mltext';
+
+ foreach ($availableLocales as $code => $locale) {
+ if (!$defaultLocale || $defaultLocale->code === $code) {
+ continue;
+ }
+
+ // Add data locker fields for the different locales under the `viewBag[locale]` property
+ $widget->fields["viewBag[locale][$code][$fieldName]"] = [
+ 'cssClass' => 'hidden',
+ 'attributes' => [
+ 'data-locale' => $code,
+ 'data-field-name' => $fieldName,
+ ],
+ ];
+ }
+ }
+ }
+ }
+
+ //
+ // Translate URLs
+ //
+
+ /**
+ * registerPageUrlTranslation
+ */
+ public function registerPageUrlTranslation($widget)
+ {
+ if (!$model = $widget->model) {
+ return;
+ }
+
+ if (
+ $model instanceof Page &&
+ isset($widget->fields['settings[url]'])
+ ) {
+ $widget->fields['settings[url]']['type'] = 'mltext';
+ }
+ elseif (
+ $model instanceof \RainLab\Pages\Classes\Page &&
+ isset($widget->fields['viewBag[url]'])
+ ) {
+ $widget->fields['viewBag[url]']['type'] = 'mltext';
+ }
+ }
+
+ //
+ // Translatable behavior
+ //
+
+ /**
+ * registerModelTranslation automatically replaces form fields for multi-lingual equivalents
+ */
+ public function registerModelTranslation($widget)
+ {
+ if ($widget->isNested) {
+ return;
+ }
+
+ if (!$model = $widget->model) {
+ return;
+ }
+
+ if (!method_exists($model, 'isClassExtendedWith')) {
+ return;
+ }
+
+ if (
+ !$model->isClassExtendedWith(\RainLab\Translate\Behaviors\TranslatableModel::class) &&
+ !$model->isClassExtendedWith(\RainLab\Translate\Behaviors\TranslatablePage::class) &&
+ !$model->isClassExtendedWith(\RainLab\Translate\Behaviors\TranslatableCmsObject::class)
+ ) {
+ return;
+ }
+
+ if (!$model->hasTranslatableAttributes()) {
+ return;
+ }
+
+ if (!empty($widget->fields)) {
+ $widget->fields = $this->processFormMLFields($widget->fields, $model);
+ }
+
+ if (!empty($widget->tabs['fields'])) {
+ $widget->tabs['fields'] = $this->processFormMLFields($widget->tabs['fields'], $model);
+ }
+
+ if (!empty($widget->secondaryTabs['fields'])) {
+ $widget->secondaryTabs['fields'] = $this->processFormMLFields($widget->secondaryTabs['fields'], $model);
+ }
+ }
+
+ /**
+ * processFormMLFields function to replace standard fields with multi-lingual equivalents
+ * @param array $fields
+ * @param Model $model
+ * @return array
+ */
+ protected function processFormMLFields($fields, $model)
+ {
+ $typesMap = [
+ 'text' => 'mltext',
+ 'textarea' => 'mltextarea',
+ 'richeditor' => 'mlricheditor',
+ 'markdown' => 'mlmarkdowneditor',
+ 'repeater' => 'mlrepeater',
+ 'nestedform' => 'mlnestedform',
+ 'mediafinder' => 'mlmediafinder',
+ ];
+
+ $translatable = array_flip($model->getTranslatableAttributes());
+
+ /*
+ * Special: A custom field "markup_html" is used for Content templates.
+ */
+ if ($model instanceof Content && array_key_exists('markup', $translatable)) {
+ $translatable['markup_html'] = true;
+ }
+
+ foreach ($fields as $name => $config) {
+ if (!array_key_exists($name, $translatable)) {
+ continue;
+ }
+
+ $type = array_get($config, 'type', 'text');
+
+ if (array_key_exists($type, $typesMap)) {
+ $fields[$name]['type'] = $typesMap[$type];
+ }
+ }
+
+ return $fields;
+ }
+
+ //
+ // Theme
+ //
+
+ /**
+ * importMessagesFromTheme
+ */
+ public function importMessagesFromTheme($themeCode)
+ {
+ try {
+ (new ThemeScanner)->scanThemeConfigForMessages($themeCode);
+ }
+ catch (Exception $ex) {}
+ }
+
+ //
+ // CMS objects
+ //
+
+ /**
+ * setMessageContext for translation caching.
+ */
+ public function setMessageContext($page)
+ {
+ if (!$page) {
+ return;
+ }
+
+ $translator = Translator::instance();
+
+ Message::setContext($translator->getLocale(), $page->url);
+ }
+
+ /**
+ * findTranslatedContentFile adds language suffixes to content files.
+ * @return string|null
+ */
+ public function findTranslatedContentFile($controller, $fileName)
+ {
+ if (!strlen(File::extension($fileName))) {
+ $fileName .= '.htm';
+ }
+
+ /*
+ * Splice the active locale in to the filename
+ * - content.htm -> content.en.htm
+ */
+ $locale = Translator::instance()->getLocale();
+ $fileName = substr_replace($fileName, '.'.$locale, strrpos($fileName, '.'), 0);
+ if (($content = Content::loadCached($controller->getTheme(), $fileName)) !== null) {
+ return $content;
+ }
+ }
+
+ //
+ // Static pages
+ //
+
+ /**
+ * pruneTranslatedContentTemplates removes localized content files from templates collection
+ * @param \October\Rain\Database\Collection $templates
+ * @return \October\Rain\Database\Collection
+ */
+ public function pruneTranslatedContentTemplates($templates)
+ {
+ $locales = LocaleModel::listAvailable();
+
+ $extensions = array_map(function($ext) {
+ return '.'.$ext;
+ }, array_keys($locales));
+
+ return $templates->filter(function($template) use ($extensions) {
+ return !Str::endsWith($template->getBaseFileName(), $extensions);
+ });
+ }
+
+ /**
+ * findLocalizedMailViewContent adds language suffixes to mail view files.
+ * @param \October\Rain\Mail\Mailer $mailer
+ * @param \Illuminate\Mail\Message $message
+ * @param string $view
+ * @param array $data
+ * @param string $raw
+ * @param string $plain
+ * @return bool|void Will return false if the translation process successfully replaced the original message with a translated version to prevent the original version from being processed.
+ */
+ public function findLocalizedMailViewContent($mailer, $message, $view, $data, $raw, $plain)
+ {
+ // Raw content cannot be localized at this level
+ if (!empty($raw)) {
+ return;
+ }
+
+ // Get the locale to use for this template
+ $locale = !empty($data['_current_locale']) ? $data['_current_locale'] : App::getLocale();
+
+ $factory = $mailer->getViewFactory();
+
+ if (!empty($view)) {
+ $view = $this->getLocalizedView($factory, $view, $locale);
+ }
+
+ if (!empty($plain)) {
+ $plain = $this->getLocalizedView($factory, $plain, $locale);
+ }
+
+ $code = $view ?: $plain;
+ if (empty($code)) {
+ return null;
+ }
+
+ $plainOnly = empty($view);
+
+ if (MailManager::instance()->addContentToMailer($message, $code, $data, $plainOnly)) {
+ // the caller who fired the event is expecting a FALSE response to halt the event
+ return false;
+ }
+ }
+
+ /**
+ * getLocalizedView searches mail view files based on locale
+ * @param \October\Rain\Mail\Mailer $mailer
+ * @param \Illuminate\Mail\Message $message
+ * @param string $code
+ * @param string $locale
+ * @return string|null
+ */
+ public function getLocalizedView($factory, $code, $locale)
+ {
+ $locale = strtolower($locale);
+
+ $searchPaths[] = $locale;
+
+ if (str_contains($locale, '-')) {
+ list($lang) = explode('-', $locale);
+ $searchPaths[] = $lang;
+ }
+
+ foreach ($searchPaths as $path) {
+ $localizedView = sprintf('%s-%s', $code, $path);
+
+ if ($factory->exists($localizedView)) {
+ return $localizedView;
+ }
+ }
+ return null;
+ }
+}
diff --git a/plugins/rainlab/translate/classes/LocaleMiddleware.php b/plugins/rainlab/translate/classes/LocaleMiddleware.php
new file mode 100644
index 00000000..20d4f036
--- /dev/null
+++ b/plugins/rainlab/translate/classes/LocaleMiddleware.php
@@ -0,0 +1,31 @@
+isConfigured();
+
+ if (!$translator->loadLocaleFromRequest()) {
+ if (Config::get('rainlab.translate::prefixDefaultLocale')) {
+ $translator->loadLocaleFromSession();
+ } else {
+ $translator->setLocale($translator->getDefaultLocale());
+ }
+ }
+
+ return $next($request);
+ }
+}
diff --git a/plugins/rainlab/translate/classes/MLCmsObject.php b/plugins/rainlab/translate/classes/MLCmsObject.php
new file mode 100644
index 00000000..2dce2a04
--- /dev/null
+++ b/plugins/rainlab/translate/classes/MLCmsObject.php
@@ -0,0 +1,56 @@
+theme);
+ }
+
+ public static function findLocale($locale, $page)
+ {
+ return static::forLocale($locale, $page)->find($page->fileName);
+ }
+
+ /**
+ * Returns the directory name corresponding to the object type.
+ * For pages the directory name is "pages", for layouts - "layouts", etc.
+ * @return string
+ */
+ public function getObjectTypeDirName()
+ {
+ $dirName = static::$parent->getObjectTypeDirName();
+
+ if (strlen(static::$locale)) {
+ $dirName .= '-' . static::$locale;
+ }
+
+ return $dirName;
+ }
+}
diff --git a/plugins/rainlab/translate/classes/MLContent.php b/plugins/rainlab/translate/classes/MLContent.php
new file mode 100644
index 00000000..0402d8d1
--- /dev/null
+++ b/plugins/rainlab/translate/classes/MLContent.php
@@ -0,0 +1,70 @@
+exists) {
+ return null;
+ }
+
+ $fileName = $page->getOriginal('fileName') ?: $page->fileName;
+
+ $fileName = static::addLocaleToFileName($fileName, $locale);
+
+ return static::forLocale($locale, $page)->find($fileName);
+ }
+
+ /**
+ * Returns the directory name corresponding to the object type.
+ * Content does not use localized sub directories, but as file suffix instead.
+ * @return string
+ */
+ public function getObjectTypeDirName()
+ {
+ return static::$parent->getObjectTypeDirName();
+ }
+
+ /**
+ * Splice in the locale when setting the file name.
+ * @param mixed $value
+ */
+ public function setFileNameAttribute($value)
+ {
+ $value = static::addLocaleToFileName($value, static::$locale);
+
+ parent::setFileNameAttribute($value);
+ }
+
+ /**
+ * Splice the active locale in to the filename
+ * - content.htm -> content.en.htm
+ */
+ protected static function addLocaleToFileName($fileName, $locale)
+ {
+ /*
+ * Check locale not already present
+ */
+ $parts = explode('.', $fileName);
+ array_shift($parts);
+
+ foreach ($parts as $part) {
+ if (trim($part) === $locale) {
+ return $fileName;
+ }
+ }
+
+ return substr_replace($fileName, '.'.$locale, strrpos($fileName, '.'), 0);
+ }
+}
diff --git a/plugins/rainlab/translate/classes/MLStaticPage.php b/plugins/rainlab/translate/classes/MLStaticPage.php
new file mode 100644
index 00000000..0fba7700
--- /dev/null
+++ b/plugins/rainlab/translate/classes/MLStaticPage.php
@@ -0,0 +1,118 @@
+getPlaceholdersAttribute();
+ }
+
+ /**
+ * Parses the page placeholder {% put %} tags and extracts the placeholder values.
+ * @return array Returns an associative array of the placeholder names and values.
+ */
+ public function getPlaceholdersAttribute()
+ {
+ if (!strlen($this->code)) {
+ return [];
+ }
+
+ if ($placeholders = array_get($this->attributes, 'placeholders')) {
+ return $placeholders;
+ }
+
+ $bodyNode = $this->getTwigNodeTree($this->code)->getNode('body')->getNode(0);
+ if ($bodyNode instanceof \Cms\Twig\PutNode) {
+ $bodyNode = [$bodyNode];
+ }
+
+ $result = [];
+ foreach ($bodyNode as $node) {
+ if (!$node instanceof \Cms\Twig\PutNode) {
+ continue;
+ }
+
+ // October CMS v2.2 and above
+ if (class_exists('System') && version_compare(\System::VERSION, '2.1') === 1) {
+ $names = $node->getNode('names');
+ $values = $node->getNode('values');
+ $isCapture = $node->getAttribute('capture');
+ if ($isCapture) {
+ $name = $names->getNode(0);
+ $result[$name->getAttribute('name')] = trim($values->getAttribute('data'));
+ }
+ }
+ // Legacy PutNode support
+ else {
+ $values = $node->getNode('body');
+ $result[$node->getAttribute('name')] = trim($values->getAttribute('data'));
+ }
+ }
+
+ $this->attributes['placeholders'] = $result;
+
+ return $result;
+ }
+
+ /**
+ * Takes an array of placeholder data (key: code, value: content) and renders
+ * it as a single string of Twig markup against the "code" attribute.
+ * @param array $value
+ * @return void
+ */
+ public function setPlaceholdersAttribute($value)
+ {
+ if (!is_array($value)) {
+ return;
+ }
+
+ $placeholders = $value;
+ $result = '';
+
+ foreach ($placeholders as $code => $content) {
+ if (!strlen($content)) {
+ continue;
+ }
+
+ $result .= '{% put '.$code.' %}'.PHP_EOL;
+ $result .= $content.PHP_EOL;
+ $result .= '{% endput %}'.PHP_EOL;
+ $result .= PHP_EOL;
+ }
+
+ $this->attributes['code'] = trim($result);
+ $this->attributes['placeholders'] = $placeholders;
+ }
+
+ /**
+ * Disables safe mode check for static pages.
+ *
+ * This allows developers to use placeholders in layouts even if safe mode is enabled.
+ *
+ * @return void
+ */
+ protected function checkSafeMode()
+ {
+ }
+}
diff --git a/plugins/rainlab/translate/classes/ThemeScanner.php b/plugins/rainlab/translate/classes/ThemeScanner.php
new file mode 100644
index 00000000..5341e517
--- /dev/null
+++ b/plugins/rainlab/translate/classes/ThemeScanner.php
@@ -0,0 +1,227 @@
+scanForMessages();
+
+ /**
+ * @event rainlab.translate.themeScanner.afterScan
+ * Fires after theme scanning.
+ *
+ * Example usage:
+ *
+ * Event::listen('rainlab.translate.themeScanner.afterScan', function (ThemeScanner $scanner) {
+ * // added an extra scan. Add generation files...
+ * });
+ *
+ */
+ Event::fire('rainlab.translate.themeScanner.afterScan', [$obj]);
+ }
+
+ /**
+ * Scans theme templates and config for messages.
+ * @return void
+ */
+ public function scanForMessages()
+ {
+ // Set all messages initially as being not found. The scanner later
+ // sets the entries it finds as found.
+ Message::query()->update(['found' => false]);
+
+ $this->scanThemeConfigForMessages();
+ $this->scanThemeTemplatesForMessages();
+ $this->scanMailTemplatesForMessages();
+ }
+
+ /**
+ * Scans the theme configuration for defined messages
+ * @return void
+ */
+ public function scanThemeConfigForMessages($themeCode = null)
+ {
+ if (!$themeCode) {
+ $theme = Theme::getActiveTheme();
+
+ if (!$theme) {
+ return;
+ }
+ }
+ else {
+ if (!Theme::exists($themeCode)) {
+ return;
+ }
+
+ $theme = Theme::load($themeCode);
+ }
+
+ // October v2.0
+ if (class_exists('System') && $theme->hasParentTheme()) {
+ $parentTheme = $theme->getParentTheme();
+
+ try {
+ $this->scanThemeConfigForMessagesInternal($theme);
+ }
+ catch (Exception $ex) {
+ $this->scanThemeConfigForMessagesInternal($parentTheme);
+ }
+ }
+ else {
+ $this->scanThemeConfigForMessagesInternal($theme);
+ }
+ }
+
+ /**
+ * scanThemeConfigForMessagesInternal
+ */
+ protected function scanThemeConfigForMessagesInternal(Theme $theme)
+ {
+ $config = $theme->getConfigArray('translate');
+
+ if (!count($config)) {
+ return;
+ }
+
+ $translator = Translator::instance();
+ $keys = [];
+
+ foreach ($config as $locale => $messages) {
+ if (is_string($messages)) {
+ // $message is a yaml filename, load the yaml file
+ $messages = $theme->getConfigArray('translate.'.$locale);
+ }
+ $keys = array_merge($keys, array_keys($messages));
+ }
+
+ Message::importMessages($keys);
+
+ foreach ($config as $locale => $messages) {
+ if (is_string($messages)) {
+ // $message is a yaml filename, load the yaml file
+ $messages = $theme->getConfigArray('translate.'.$locale);
+ }
+ Message::importMessageCodes($messages, $locale);
+ }
+ }
+
+ /**
+ * Scans the theme templates for message references.
+ * @return void
+ */
+ public function scanThemeTemplatesForMessages()
+ {
+ $messages = [];
+
+ foreach (Layout::all() as $layout) {
+ $messages = array_merge($messages, $this->parseContent($layout->markup));
+ }
+
+ foreach (Page::all() as $page) {
+ $messages = array_merge($messages, $this->parseContent($page->markup));
+ }
+
+ foreach (Partial::all() as $partial) {
+ $messages = array_merge($messages, $this->parseContent($partial->markup));
+ }
+
+ Message::importMessages($messages);
+ }
+
+ /**
+ * Scans the mail templates for message references.
+ * @return void
+ */
+ public function scanMailTemplatesForMessages()
+ {
+ $messages = [];
+
+ foreach (MailTemplate::allTemplates() as $mailTemplate) {
+ $messages = array_merge($messages, $this->parseContent($mailTemplate->subject));
+ $messages = array_merge($messages, $this->parseContent($mailTemplate->content_html));
+ }
+
+ Message::importMessages($messages);
+ }
+
+ /**
+ * Parse the known language tag types in to messages.
+ * @param string $content
+ * @return array
+ */
+ protected function parseContent($content)
+ {
+ $messages = [];
+ $messages = array_merge($messages, $this->processStandardTags($content));
+
+ return $messages;
+ }
+
+ /**
+ * Process standard language filter tag (_|)
+ * @param string $content
+ * @return array
+ */
+ protected function processStandardTags($content)
+ {
+ $messages = [];
+
+ /*
+ * Regex used:
+ *
+ * {{'AJAX framework'|_}}
+ * {{\s*'([^'])+'\s*[|]\s*_\s*}}
+ *
+ * {{'AJAX framework'|_(variables)}}
+ * {{\s*'([^'])+'\s*[|]\s*_\s*\([^\)]+\)\s*}}
+ */
+
+ $quoteChar = preg_quote("'");
+
+ preg_match_all('#{{\s*'.$quoteChar.'([^'.$quoteChar.']+)'.$quoteChar.'\s*[|]\s*_\s*(?:[|].+)?}}#', $content, $match);
+ if (isset($match[1])) {
+ $messages = array_merge($messages, $match[1]);
+ }
+
+ preg_match_all('#{{\s*'.$quoteChar.'([^'.$quoteChar.']+)'.$quoteChar.'\s*[|]\s*_\s*\([^\)]+\)\s*}}#', $content, $match);
+ if (isset($match[1])) {
+ $messages = array_merge($messages, $match[1]);
+ }
+
+ $quoteChar = preg_quote('"');
+
+ preg_match_all('#{{\s*'.$quoteChar.'([^'.$quoteChar.']+)'.$quoteChar.'\s*[|]\s*_\s*(?:[|].+)?}}#', $content, $match);
+ if (isset($match[1])) {
+ $messages = array_merge($messages, $match[1]);
+ }
+
+ preg_match_all('#{{\s*'.$quoteChar.'([^'.$quoteChar.']+)'.$quoteChar.'\s*[|]\s*_\s*\([^\)]+\)\s*}}#', $content, $match);
+ if (isset($match[1])) {
+ $messages = array_merge($messages, $match[1]);
+ }
+
+ return $messages;
+ }
+}
diff --git a/plugins/rainlab/translate/classes/TranslatableBehavior.php b/plugins/rainlab/translate/classes/TranslatableBehavior.php
new file mode 100644
index 00000000..3ae9e65a
--- /dev/null
+++ b/plugins/rainlab/translate/classes/TranslatableBehavior.php
@@ -0,0 +1,494 @@
+model = $model;
+
+ $this->initTranslatableContext();
+
+ $this->model->bindEvent('model.beforeGetAttribute', function ($key) use ($model) {
+ if ($this->isTranslatable($key)) {
+ $value = $this->getAttributeTranslated($key);
+ if ($model->hasGetMutator($key)) {
+ $method = 'get' . Str::studly($key) . 'Attribute';
+ $value = $model->{$method}($value);
+ }
+ return $value;
+ }
+ });
+
+ $this->model->bindEvent('model.beforeSetAttribute', function ($key, $value) use ($model) {
+ if ($this->isTranslatable($key)) {
+ $value = $this->setAttributeTranslated($key, $value);
+ if ($model->hasSetMutator($key)) {
+ $method = 'set' . Str::studly($key) . 'Attribute';
+ $value = $model->{$method}($value);
+ }
+ return $value;
+ }
+ });
+
+ $this->model->bindEvent('model.saveInternal', function() {
+ $this->syncTranslatableAttributes();
+ });
+ }
+
+ /**
+ * Initializes this class, sets the default language code to use.
+ * @return void
+ */
+ public function initTranslatableContext()
+ {
+ $translate = Translator::instance();
+ $this->translatableContext = $translate->getLocale();
+ $this->translatableDefault = $translate->getDefaultLocale();
+ }
+
+ /**
+ * Checks if an attribute should be translated or not.
+ * @param string $key
+ * @return boolean
+ */
+ public function isTranslatable($key)
+ {
+ if ($key === 'translatable' || $this->translatableDefault == $this->translatableContext) {
+ return false;
+ }
+
+ return in_array($key, $this->model->getTranslatableAttributes());
+ }
+
+ /**
+ * Disables translation fallback locale.
+ * @return self
+ */
+ public function noFallbackLocale()
+ {
+ $this->translatableUseFallback = false;
+
+ return $this->model;
+ }
+
+ /**
+ * Enables translation fallback locale.
+ * @return self
+ */
+ public function withFallbackLocale()
+ {
+ $this->translatableUseFallback = true;
+
+ return $this->model;
+ }
+
+ /**
+ * Returns a translated attribute value.
+ *
+ * The base value must come from 'attributes' on the model otherwise the process
+ * can possibly loop back to this event, then method triggered by __get() magic.
+ *
+ * @param string $key
+ * @param string $locale
+ * @return string
+ */
+ public function getAttributeTranslated($key, $locale = null)
+ {
+ if ($locale == null) {
+ $locale = $this->translatableContext;
+ }
+
+ /*
+ * Result should not return NULL to successfully hook beforeGetAttribute event
+ */
+ $result = '';
+
+ /*
+ * Default locale
+ */
+ if ($locale == $this->translatableDefault) {
+ $result = $this->getAttributeFromData($this->model->attributes, $key);
+ }
+ /*
+ * Other locale
+ */
+ else {
+ if (!array_key_exists($locale, $this->translatableAttributes)) {
+ $this->loadTranslatableData($locale);
+ }
+
+ if ($this->hasTranslation($key, $locale)) {
+ $result = $this->getAttributeFromData($this->translatableAttributes[$locale], $key);
+ }
+ elseif ($this->translatableUseFallback) {
+ $result = $this->getAttributeFromData($this->model->attributes, $key);
+ }
+ }
+
+ /*
+ * Handle jsonable attributes, default locale may return the value as a string
+ */
+ if (
+ is_string($result) &&
+ method_exists($this->model, 'isJsonable') &&
+ $this->model->isJsonable($key)
+ ) {
+ $result = json_decode($result, true);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Returns all translated attribute values.
+ * @param string $locale
+ * @return array
+ */
+ public function getTranslateAttributes($locale)
+ {
+ if (!array_key_exists($locale, $this->translatableAttributes)) {
+ $this->loadTranslatableData($locale);
+ }
+
+ return array_get($this->translatableAttributes, $locale, []);
+ }
+
+ /**
+ * Returns whether the attribute is translatable (has a translation) for the given locale.
+ * @param string $key
+ * @param string $locale
+ * @return bool
+ */
+ public function hasTranslation($key, $locale)
+ {
+ /*
+ * If the default locale is passed, the attributes are retreived from the model,
+ * otherwise fetch the attributes from the $translatableAttributes property
+ */
+ if ($locale == $this->translatableDefault) {
+ $translatableAttributes = $this->model->attributes;
+ }
+ else {
+ /*
+ * Ensure that the translatableData has been loaded
+ * @see https://github.com/rainlab/translate-plugin/issues/302
+ */
+ if (!isset($this->translatableAttributes[$locale])) {
+ $this->loadTranslatableData($locale);
+ }
+
+ $translatableAttributes = $this->translatableAttributes[$locale];
+ }
+
+ return !!$this->getAttributeFromData($translatableAttributes, $key);
+ }
+
+ /**
+ * Sets a translated attribute value.
+ * @param string $key Attribute
+ * @param string $value Value to translate
+ * @return string Translated value
+ */
+ public function setAttributeTranslated($key, $value, $locale = null)
+ {
+ if ($locale == null) {
+ $locale = $this->translatableContext;
+ }
+
+ if ($locale == $this->translatableDefault) {
+ return $this->setAttributeFromData($this->model->attributes, $key, $value);
+ }
+
+ if (!array_key_exists($locale, $this->translatableAttributes)) {
+ $this->loadTranslatableData($locale);
+ }
+
+ return $this->setAttributeFromData($this->translatableAttributes[$locale], $key, $value);
+ }
+
+ /**
+ * Restores the default language values on the model and
+ * stores the translated values in the attributes table.
+ * @return void
+ */
+ public function syncTranslatableAttributes()
+ {
+ /*
+ * Spin through the known locales, store the translations if necessary
+ */
+ $knownLocales = array_keys($this->translatableAttributes);
+ foreach ($knownLocales as $locale) {
+ if (!$this->isTranslateDirty(null, $locale)) {
+ continue;
+ }
+
+ $this->storeTranslatableData($locale);
+ }
+
+ /*
+ * Saving the default locale, no need to restore anything
+ */
+ if ($this->translatableContext == $this->translatableDefault) {
+ return;
+ }
+
+ /*
+ * Restore translatable values to models originals
+ */
+ $original = $this->model->getOriginal();
+ $attributes = $this->model->getAttributes();
+ $translatable = $this->model->getTranslatableAttributes();
+ $originalValues = array_intersect_key($original, array_flip($translatable));
+ $this->model->attributes = array_merge($attributes, $originalValues);
+ }
+
+ /**
+ * Changes the active language for this model
+ * @param string $context
+ * @return void
+ */
+ public function translateContext($context = null)
+ {
+ if ($context === null) {
+ return $this->translatableContext;
+ }
+
+ $this->translatableContext = $context;
+ }
+
+ /**
+ * Shorthand for translateContext method, and chainable.
+ * @param string $context
+ * @return self
+ */
+ public function lang($context = null)
+ {
+ $this->translateContext($context);
+
+ return $this->model;
+ }
+
+ /**
+ * Checks if this model has transatable attributes.
+ * @return true
+ */
+ public function hasTranslatableAttributes()
+ {
+ return is_array($this->model->translatable) &&
+ count($this->model->translatable) > 0;
+ }
+
+ /**
+ * Returns a collection of fields that will be hashed.
+ * @return array
+ */
+ public function getTranslatableAttributes()
+ {
+ $translatable = [];
+
+ if (!is_array($this->model->translatable)) {
+ return [];
+ }
+
+ foreach ($this->model->translatable as $attribute) {
+ $translatable[] = is_array($attribute) ? array_shift($attribute) : $attribute;
+ }
+
+ return $translatable;
+ }
+
+ /**
+ * Returns the defined options for a translatable attribute.
+ * @return array
+ */
+ public function getTranslatableAttributesWithOptions()
+ {
+ $attributes = [];
+
+ foreach ($this->model->translatable as $options) {
+ if (!is_array($options)) {
+ continue;
+ }
+
+ $attributeName = array_shift($options);
+
+ $attributes[$attributeName] = $options;
+ }
+
+ return $attributes;
+ }
+
+ /**
+ * Determine if the model or a given translated attribute has been modified.
+ * @param string|null $attribute
+ * @return bool
+ */
+ public function isTranslateDirty($attribute = null, $locale = null)
+ {
+ $dirty = $this->getTranslateDirty($locale);
+
+ if (is_null($attribute)) {
+ return count($dirty) > 0;
+ }
+ else {
+ return array_key_exists($attribute, $dirty);
+ }
+ }
+
+ /**
+ * Get the locales that have changed, if any
+ *
+ * @return array
+ */
+ public function getDirtyLocales()
+ {
+ $dirtyLocales = [];
+ $knownLocales = array_keys($this->translatableAttributes);
+ foreach ($knownLocales as $locale) {
+ if ($this->isTranslateDirty(null, $locale)) {
+ $dirtyLocales[] = $locale;
+ }
+ }
+
+ return $dirtyLocales;
+ }
+
+ /**
+ * Get the original values of the translated attributes.
+ * @param string|null $locale If `null`, the method will get the original data for all locales.
+ * @return array|null Returns locale data as an array, or `null` if an invalid locale is specified.
+ */
+ public function getTranslatableOriginals($locale = null)
+ {
+ if (!$locale) {
+ return $this->translatableOriginals;
+ } else {
+ return $this->translatableOriginals[$locale] ?? null;
+ }
+ }
+
+ /**
+ * Get the translated attributes that have been changed since last sync.
+ * @return array
+ */
+ public function getTranslateDirty($locale = null)
+ {
+ if (!$locale) {
+ $locale = $this->translatableContext;
+ }
+
+ if (!array_key_exists($locale, $this->translatableAttributes)) {
+ return [];
+ }
+
+ if (!array_key_exists($locale, $this->translatableOriginals)) {
+ return $this->translatableAttributes[$locale]; // All dirty
+ }
+
+ $dirty = [];
+
+ foreach ($this->translatableAttributes[$locale] as $key => $value) {
+
+ if (!array_key_exists($key, $this->translatableOriginals[$locale])) {
+ $dirty[$key] = $value;
+ }
+ elseif ($value != $this->translatableOriginals[$locale][$key]) {
+ $dirty[$key] = $value;
+ }
+ }
+
+ return $dirty;
+ }
+
+ /**
+ * Extracts a attribute from a model/array with nesting support.
+ * @param mixed $data
+ * @param string $attribute
+ * @return mixed
+ */
+ protected function getAttributeFromData($data, $attribute)
+ {
+ $keyArray = HtmlHelper::nameToArray($attribute);
+
+ return array_get($data, implode('.', $keyArray));
+ }
+
+ /**
+ * Sets an attribute from a model/array with nesting support.
+ * @param mixed $data
+ * @param string $attribute
+ * @return mixed
+ */
+ protected function setAttributeFromData(&$data, $attribute, $value)
+ {
+ $keyArray = HtmlHelper::nameToArray($attribute);
+
+ array_set($data, implode('.', $keyArray), $value);
+
+ return $value;
+ }
+
+ /**
+ * Saves the translation data for the model.
+ * @param string $locale
+ * @return void
+ */
+ abstract protected function storeTranslatableData($locale = null);
+
+ /**
+ * Loads the translation data from the model.
+ * @param string $locale
+ * @return array
+ */
+ abstract protected function loadTranslatableData($locale = null);
+}
diff --git a/plugins/rainlab/translate/classes/Translator.php b/plugins/rainlab/translate/classes/Translator.php
new file mode 100644
index 00000000..de9dd705
--- /dev/null
+++ b/plugins/rainlab/translate/classes/Translator.php
@@ -0,0 +1,259 @@
+defaultLocale = $this->isConfigured() ? array_get(Locale::getDefault(), 'code', 'en') : 'en';
+ $this->activeLocale = $this->defaultLocale;
+ }
+
+ /**
+ * Changes the locale in the application and optionally stores it in the session.
+ * @param string $locale Locale to use
+ * @param boolean $remember Set to false to not store in the session.
+ * @return boolean Returns true if the locale exists and is set.
+ */
+ public function setLocale($locale, $remember = true)
+ {
+ if (!Locale::isValid($locale)) {
+ return false;
+ }
+
+ App::setLocale($locale);
+
+ $this->activeLocale = $locale;
+
+ if ($remember) {
+ $this->setSessionLocale($locale);
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns the active locale set by this instance.
+ * @param boolean $fromSession Look in the session.
+ * @return string
+ */
+ public function getLocale($fromSession = false)
+ {
+ if ($fromSession && ($locale = $this->getSessionLocale())) {
+ return $locale;
+ }
+
+ return $this->activeLocale;
+ }
+
+ /**
+ * Returns the default locale as set by the application.
+ * @return string
+ */
+ public function getDefaultLocale()
+ {
+ return $this->defaultLocale;
+ }
+
+ /**
+ * Check if this plugin is installed and the database is available,
+ * stores the result in the session for efficiency.
+ * @return boolean
+ */
+ public function isConfigured()
+ {
+ if ($this->isConfigured !== null) {
+ return $this->isConfigured;
+ }
+
+ if (Session::has(self::SESSION_CONFIGURED)) {
+ $result = true;
+ }
+ elseif (App::hasDatabase() && Schema::hasTable('rainlab_translate_locales')) {
+ Session::put(self::SESSION_CONFIGURED, true);
+ $result = true;
+ }
+ else {
+ $result = false;
+ }
+
+ return $this->isConfigured = $result;
+ }
+
+ //
+ // Request handling
+ //
+
+ /**
+ * handleLocaleRoute will check if the route contains a translated locale prefix (/en/)
+ * and return that locale to be registered with the router.
+ * @return string
+ */
+ public function handleLocaleRoute()
+ {
+ if (Config::get('rainlab.translate::disableLocalePrefixRoutes', false)) {
+ return '';
+ }
+
+ if (App::runningInBackend()) {
+ return '';
+ }
+
+ if (!$this->isConfigured()) {
+ return '';
+ }
+
+ if (!$this->loadLocaleFromRequest()) {
+ return '';
+ }
+
+ $locale = $this->getLocale();
+ if (!$locale) {
+ return '';
+ }
+
+ return $locale;
+ }
+
+ /**
+ * Sets the locale based on the first URI segment.
+ * @return bool
+ */
+ public function loadLocaleFromRequest()
+ {
+ $locale = Request::segment(1);
+
+ if (!Locale::isValid($locale)) {
+ return false;
+ }
+
+ $this->setLocale($locale);
+ return true;
+ }
+
+ /**
+ * Returns the current path prefixed with language code.
+ *
+ * @param string $locale optional language code, default to the system default language
+ * @return string
+ */
+ public function getCurrentPathInLocale($locale = null)
+ {
+ return $this->getPathInLocale(Request::path(), $locale);
+ }
+
+ /**
+ * Returns the path prefixed with language code.
+ *
+ * @param string $path Path to rewrite, already translate, with or without locale prefixed
+ * @param string $locale optional language code, default to the system default language
+ * @param boolean $prefixDefaultLocale should we prefix the path when the locale = default locale
+ * @return string
+ */
+ public function getPathInLocale($path, $locale = null, $prefixDefaultLocale = null)
+ {
+ $prefixDefaultLocale = (is_null($prefixDefaultLocale))
+ ? Config::get('rainlab.translate::prefixDefaultLocale')
+ : $prefixDefaultLocale;
+
+ $segments = explode('/', $path);
+
+ $segments = array_values(array_filter($segments, function ($v) {
+ return $v != '';
+ }));
+
+ if (is_null($locale) || !Locale::isValid($locale)) {
+ $locale = $this->defaultLocale;
+ }
+
+ if (count($segments) == 0 || Locale::isValid($segments[0])) {
+ $segments[0] = $locale;
+ } else {
+ array_unshift($segments, $locale);
+ }
+
+ // If we don't want te default locale to be prefixed
+ // and the first segment equals the default locale
+ if (
+ !$prefixDefaultLocale &&
+ isset($segments[0]) &&
+ $segments[0] == $this->defaultLocale
+ ) {
+ // Remove the default locale
+ array_shift($segments);
+ };
+
+ return htmlspecialchars(implode('/', $segments), ENT_QUOTES, 'UTF-8');
+ }
+
+ //
+ // Session handling
+ //
+
+ /**
+ * Looks at the session storage to find a locale.
+ * @return bool
+ */
+ public function loadLocaleFromSession()
+ {
+ $locale = $this->getSessionLocale();
+
+ if (!$locale) {
+ return false;
+ }
+
+ $this->setLocale($locale);
+ return true;
+ }
+
+ protected function getSessionLocale()
+ {
+ if (!Session::has(self::SESSION_LOCALE)) {
+ return null;
+ }
+
+ return Session::get(self::SESSION_LOCALE);
+ }
+
+ protected function setSessionLocale($locale)
+ {
+ Session::put(self::SESSION_LOCALE, $locale);
+ }
+}
diff --git a/plugins/rainlab/translate/components/AlternateHrefLangElements.php b/plugins/rainlab/translate/components/AlternateHrefLangElements.php
new file mode 100644
index 00000000..687fd49d
--- /dev/null
+++ b/plugins/rainlab/translate/components/AlternateHrefLangElements.php
@@ -0,0 +1,82 @@
+ 'rainlab.translate::lang.alternate_hreflang.component_name',
+ 'description' => 'rainlab.translate::lang.alternate_hreflang.component_description'
+ ];
+ }
+
+ /**
+ * locales
+ */
+ public function locales()
+ {
+ // Available locales
+ $locales = collect(LocaleModel::listEnabled());
+
+ // Transform it to contain the new urls
+ $locales->transform(function ($item, $key) {
+ return $this->retrieveLocalizedUrl($key);
+ });
+
+ return $locales->toArray();
+ }
+
+ /**
+ * retrieveLocalizedUrl
+ */
+ protected function retrieveLocalizedUrl($locale)
+ {
+ $translator = Translator::instance();
+ $page = $this->getPage();
+
+ /*
+ * Static Page
+ */
+ if (isset($page->apiBag['staticPage'])) {
+ $staticPage = $page->apiBag['staticPage'];
+ $staticPage->rewriteTranslatablePageUrl($locale);
+ $localeUrl = array_get($staticPage->attributes, 'viewBag.url');
+ }
+ /*
+ * CMS Page
+ */
+ else {
+ $page->rewriteTranslatablePageUrl($locale);
+ $params = $this->getRouter()->getParameters();
+
+ $translatedParams = Event::fire('translate.localePicker.translateParams', [
+ $page,
+ $params,
+ $this->oldLocale,
+ $locale
+ ], true);
+
+ if ($translatedParams) {
+ $params = $translatedParams;
+ }
+
+ $router = new RainRouter;
+ $localeUrl = $router->urlFromPattern($page->url, $params);
+ }
+
+ return $translator->getPathInLocale($localeUrl, $locale);
+ }
+
+}
diff --git a/plugins/rainlab/translate/components/LocalePicker.php b/plugins/rainlab/translate/components/LocalePicker.php
new file mode 100644
index 00000000..fa5110d9
--- /dev/null
+++ b/plugins/rainlab/translate/components/LocalePicker.php
@@ -0,0 +1,226 @@
+ 'rainlab.translate::lang.locale_picker.component_name',
+ 'description' => 'rainlab.translate::lang.locale_picker.component_description',
+ ];
+ }
+
+ public function defineProperties()
+ {
+ return [
+ 'forceUrl' => [
+ 'title' => 'Force URL schema',
+ 'description' => 'Always prefix the URL with a language code.',
+ 'default' => 0,
+ 'type' => 'checkbox'
+ ],
+ ];
+ }
+
+ public function init()
+ {
+ $this->translator = Translator::instance();
+ }
+
+ public function onRun()
+ {
+ if ($redirect = $this->redirectForceUrl()) {
+ return $redirect;
+ }
+
+ $this->page['locales'] = $this->locales = LocaleModel::listEnabled();
+ $this->page['activeLocale'] = $this->activeLocale = $this->translator->getLocale();
+ $this->page['activeLocaleName'] = $this->activeLocaleName = array_get($this->locales, $this->activeLocale);
+ }
+
+ public function onSwitchLocale()
+ {
+ if (!$locale = post('locale')) {
+ return;
+ }
+
+ // Remember the current locale before switching to the requested one
+ $this->oldLocale = $this->translator->getLocale();
+
+ $this->translator->setLocale($locale);
+
+ $pageUrl = $this->withPreservedQueryString($this->makeLocaleUrlFromPage($locale), $locale);
+ if ($this->property('forceUrl')) {
+ return Redirect::to($this->translator->getPathInLocale($pageUrl, $locale));
+ }
+
+ return Redirect::to($pageUrl);
+ }
+
+ protected function redirectForceUrl()
+ {
+ if (
+ Request::ajax() ||
+ !$this->property('forceUrl') ||
+ $this->translator->loadLocaleFromRequest()
+ ) {
+ return;
+ }
+
+ $prefixDefaultLocale = Config::get('rainlab.translate::prefixDefaultLocale');
+ $locale = $this->translator->getLocale(false)
+ ?: $this->translator->getDefaultLocale();
+
+ if ($prefixDefaultLocale) {
+ return Redirect::to(
+ $this->withPreservedQueryString(
+ $this->translator->getCurrentPathInLocale($locale),
+ $locale
+ )
+ );
+ } elseif ( $locale == $this->translator->getDefaultLocale()) {
+ return;
+ } else {
+ $this->translator->setLocale($this->translator->getDefaultLocale());
+ return;
+ }
+
+ }
+
+ /**
+ * Returns the URL from a page object, including current parameter values.
+ * @return string
+ */
+ protected function makeLocaleUrlFromPage($locale)
+ {
+ $page = $this->getPage();
+
+ /*
+ * Static Page
+ */
+ if (isset($page->apiBag['staticPage'])) {
+ $staticPage = $page->apiBag['staticPage'];
+
+ $staticPage->rewriteTranslatablePageUrl($locale);
+
+ $localeUrl = array_get($staticPage->attributes, 'viewBag.url');
+ }
+ /*
+ * CMS Page
+ */
+ else {
+ $page->rewriteTranslatablePageUrl($locale);
+
+ $router = new RainRouter;
+
+ $params = $this->getRouter()->getParameters();
+
+ /**
+ * @event translate.localePicker.translateParams
+ * Enables manipulating the URL parameters
+ *
+ * You will have access to the page object, the old and new locale and the URL parameters.
+ *
+ * Example usage:
+ *
+ * Event::listen('translate.localePicker.translateParams', function($page, $params, $oldLocale, $newLocale) {
+ * if ($page->baseFileName == 'your-page-filename') {
+ * return YourModel::translateParams($params, $oldLocale, $newLocale);
+ * }
+ * });
+ *
+ */
+ $translatedParams = Event::fire('translate.localePicker.translateParams', [
+ $page,
+ $params,
+ $this->oldLocale,
+ $locale
+ ], true);
+
+ if ($translatedParams) {
+ $params = $translatedParams;
+ }
+
+ $localeUrl = $router->urlFromPattern($page->url, $params);
+ }
+
+ return $localeUrl;
+ }
+
+ /**
+ * Makes sure to add any existing query string to the redirect url.
+ *
+ * @param $pageUrl
+ * @param $locale
+ *
+ * @return string
+ */
+ protected function withPreservedQueryString($pageUrl, $locale)
+ {
+ $page = $this->getPage();
+ $query = request()->query();
+
+ /**
+ * @event translate.localePicker.translateQuery
+ * Enables manipulating the URL query parameters
+ *
+ * You will have access to the page object, the old and new locale and the URL query parameters.
+ *
+ * Example usage:
+ *
+ * Event::listen('translate.localePicker.translateQuery', function($page, $params, $oldLocale, $newLocale) {
+ * if ($page->baseFileName == 'your-page-filename') {
+ * return YourModel::translateParams($params, $oldLocale, $newLocale);
+ * }
+ * });
+ *
+ */
+ $translatedQuery = Event::fire('translate.localePicker.translateQuery', [
+ $page,
+ $query,
+ $this->oldLocale,
+ $locale
+ ], true);
+
+ $query = http_build_query($translatedQuery ?: $query);
+
+ return $query ? $pageUrl . '?' . $query : $pageUrl;
+ }
+}
diff --git a/plugins/rainlab/translate/components/alternatehreflangelements/default.htm b/plugins/rainlab/translate/components/alternatehreflangelements/default.htm
new file mode 100644
index 00000000..e7b5fd89
--- /dev/null
+++ b/plugins/rainlab/translate/components/alternatehreflangelements/default.htm
@@ -0,0 +1,3 @@
+{% for locale, alternateUrl in __SELF__.locales %}
+
+{% endfor %}
diff --git a/plugins/rainlab/translate/components/localepicker/default.htm b/plugins/rainlab/translate/components/localepicker/default.htm
new file mode 100644
index 00000000..90c3444f
--- /dev/null
+++ b/plugins/rainlab/translate/components/localepicker/default.htm
@@ -0,0 +1,7 @@
+{{ form_open() }}
+
+ {% for code, name in locales %}
+ {{ name }}
+ {% endfor %}
+
+{{ form_close() }}
\ No newline at end of file
diff --git a/plugins/rainlab/translate/composer.json b/plugins/rainlab/translate/composer.json
new file mode 100644
index 00000000..af341785
--- /dev/null
+++ b/plugins/rainlab/translate/composer.json
@@ -0,0 +1,25 @@
+{
+ "name": "rainlab/translate-plugin",
+ "type": "october-plugin",
+ "description": "Translate plugin for October CMS",
+ "homepage": "https://octobercms.com/plugin/rainlab-translate",
+ "keywords": ["october", "octobercms", "translate"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Alexey Bobkov",
+ "email": "aleksey.bobkov@gmail.com",
+ "role": "Co-founder"
+ },
+ {
+ "name": "Samuel Georges",
+ "email": "daftspunky@gmail.com",
+ "role": "Co-founder"
+ }
+ ],
+ "require": {
+ "php": ">=5.5.9",
+ "composer/installers": "~1.0"
+ },
+ "minimum-stability": "dev"
+}
diff --git a/plugins/rainlab/translate/config/config.php b/plugins/rainlab/translate/config/config.php
new file mode 100644
index 00000000..d9c60fc3
--- /dev/null
+++ b/plugins/rainlab/translate/config/config.php
@@ -0,0 +1,49 @@
+ env('TRANSLATE_FORCE_LOCALE', null),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Prefix the Default Locale
+ |--------------------------------------------------------------------------
+ |
+ | Specifies if the default locale be prefixed by the plugin.
+ |
+ */
+ 'prefixDefaultLocale' => env('TRANSLATE_PREFIX_LOCALE', true),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Cache Timeout in Minutes
+ |--------------------------------------------------------------------------
+ |
+ | By default all translations are cached for 24 hours (1440 min).
+ | This setting allows to change that period with given amount of minutes.
+ |
+ | For example, 43200 for 30 days or 525600 for one year.
+ |
+ */
+ 'cacheTimeout' => env('TRANSLATE_CACHE_TIMEOUT', 1440),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Disable Locale Prefix Routes
+ |--------------------------------------------------------------------------
+ |
+ | Disables the automatically generated locale prefixed routes
+ | (i.e. /en/original-route) when enabled.
+ |
+ */
+ 'disableLocalePrefixRoutes' => env('TRANSLATE_DISABLE_PREFIX_ROUTES', false),
+
+];
diff --git a/plugins/rainlab/translate/console/ScanCommand.php b/plugins/rainlab/translate/console/ScanCommand.php
new file mode 100644
index 00000000..4d38604f
--- /dev/null
+++ b/plugins/rainlab/translate/console/ScanCommand.php
@@ -0,0 +1,40 @@
+option('purge')) {
+ $this->output->writeln('Purging messages...');
+ Message::truncate();
+ }
+
+ ThemeScanner::scan();
+ $this->output->success('Messages scanned successfully.');
+ $this->output->note('You may need to run cache:clear for updated messages to take effect.');
+ }
+
+ protected function getArguments()
+ {
+ return [];
+ }
+
+ protected function getOptions()
+ {
+ return [
+ ['purge', 'null', InputOption::VALUE_NONE, 'First purge existing messages.', null],
+ ];
+ }
+}
diff --git a/plugins/rainlab/translate/controllers/Locales.php b/plugins/rainlab/translate/controllers/Locales.php
new file mode 100644
index 00000000..7f3be5d6
--- /dev/null
+++ b/plugins/rainlab/translate/controllers/Locales.php
@@ -0,0 +1,92 @@
+addJs('/plugins/rainlab/translate/assets/js/locales.js');
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function listInjectRowClass($record, $definition = null)
+ {
+ if (!$record->is_enabled) {
+ return 'safe disabled';
+ }
+ }
+
+ public function onCreateForm()
+ {
+ $this->asExtension('FormController')->create();
+
+ return $this->makePartial('create_form');
+ }
+
+ public function onCreate()
+ {
+ LocaleModel::clearCache();
+ $this->asExtension('FormController')->create_onSave();
+
+ return $this->listRefresh();
+ }
+
+ public function onUpdateForm()
+ {
+ $this->asExtension('FormController')->update(post('record_id'));
+ $this->vars['recordId'] = post('record_id');
+
+ return $this->makePartial('update_form');
+ }
+
+ public function onUpdate()
+ {
+ LocaleModel::clearCache();
+ $this->asExtension('FormController')->update_onSave(post('record_id'));
+
+ return $this->listRefresh();
+ }
+
+ public function onDelete()
+ {
+ LocaleModel::clearCache();
+ $this->asExtension('FormController')->update_onDelete(post('record_id'));
+
+ return $this->listRefresh();
+ }
+
+ public function onReorder()
+ {
+ LocaleModel::clearCache();
+ $this->asExtension('ReorderController')->onReorder();
+ }
+}
diff --git a/plugins/rainlab/translate/controllers/Messages.php b/plugins/rainlab/translate/controllers/Messages.php
new file mode 100644
index 00000000..4f842863
--- /dev/null
+++ b/plugins/rainlab/translate/controllers/Messages.php
@@ -0,0 +1,237 @@
+addJs('/plugins/rainlab/translate/assets/js/messages.js');
+ $this->addCss('/plugins/rainlab/translate/assets/css/messages.css');
+
+ $this->importColumns = MessageExport::getColumns();
+ $this->exportColumns = MessageExport::getColumns();
+ }
+
+ public function index()
+ {
+ $this->pageTitle = 'rainlab.translate::lang.messages.title';
+ $this->prepareTable();
+ }
+
+ public function onRefresh()
+ {
+ $this->prepareTable();
+ return ['#messagesContainer' => $this->makePartial('messages')];
+ }
+
+ public function onClearCache()
+ {
+ CacheHelper::clear();
+
+ Flash::success(Lang::get('rainlab.translate::lang.messages.clear_cache_success'));
+ }
+
+ public function onLoadScanMessagesForm()
+ {
+ return $this->makePartial('scan_messages_form');
+ }
+
+ public function onScanMessages()
+ {
+ if (post('purge_messages', false)) {
+ Message::truncate();
+ }
+
+ ThemeScanner::scan();
+
+ if (post('purge_deleted_messages', false)) {
+ Message::where('found', 0)->delete();
+ }
+
+ Flash::success(Lang::get('rainlab.translate::lang.messages.scan_messages_success'));
+
+ return $this->onRefresh();
+ }
+
+ public function prepareTable()
+ {
+ $fromCode = post('locale_from', null);
+ $toCode = post('locale_to', Locale::getDefault()->code);
+ $this->hideTranslated = post('hide_translated', false);
+
+ /*
+ * Page vars
+ */
+ $this->vars['hideTranslated'] = $this->hideTranslated;
+ $this->vars['defaultLocale'] = Locale::getDefault();
+ $this->vars['locales'] = Locale::all();
+ $this->vars['selectedFrom'] = $selectedFrom = Locale::findByCode($fromCode);
+ $this->vars['selectedTo'] = $selectedTo = Locale::findByCode($toCode);
+
+ /*
+ * Make table config, make default column read only
+ */
+ $config = $this->makeConfig('config_table.yaml');
+
+ if (!$selectedFrom) {
+ $config->columns['from']['readOnly'] = true;
+ }
+ if (!$selectedTo) {
+ $config->columns['to']['readOnly'] = true;
+ }
+
+ /*
+ * Make table widget
+ */
+ $widget = $this->makeWidget(\Backend\Widgets\Table::class, $config);
+ $widget->bindToController();
+
+ /*
+ * Populate data
+ */
+ $dataSource = $widget->getDataSource();
+
+ $dataSource->bindEvent('data.getRecords', function($offset, $count) use ($selectedFrom, $selectedTo) {
+ $messages = $this->listMessagesForDatasource([
+ 'offset' => $offset,
+ 'count' => $count
+ ]);
+
+ return $this->processTableData($messages, $selectedFrom, $selectedTo);
+ });
+
+ $dataSource->bindEvent('data.searchRecords', function($search, $offset, $count) use ($selectedFrom, $selectedTo) {
+ $messages = $this->listMessagesForDatasource([
+ 'search' => $search,
+ 'offset' => $offset,
+ 'count' => $count
+ ]);
+
+ return $this->processTableData($messages, $selectedFrom, $selectedTo);
+ });
+
+ $dataSource->bindEvent('data.getCount', function() {
+ return Message::count();
+ });
+
+ $dataSource->bindEvent('data.updateRecord', function($key, $data) {
+ $message = Message::find($key);
+ $this->updateTableData($message, $data);
+ CacheHelper::clear();
+ });
+
+ $dataSource->bindEvent('data.deleteRecord', function($key) {
+ if ($message = Message::find($key)) {
+ $message->delete();
+ }
+ });
+
+ $this->vars['table'] = $widget;
+ }
+
+ protected function isHideTranslated()
+ {
+ return post('hide_translated', false);
+ }
+
+ protected function listMessagesForDatasource($options = [])
+ {
+ extract(array_merge([
+ 'search' => null,
+ 'offset' => null,
+ 'count' => null,
+ ], $options));
+
+ $query = Message::orderBy('message_data','asc');
+
+ if ($search) {
+ $query = $query->searchWhere($search, ['message_data']);
+ }
+
+ if ($count) {
+ $query = $query->limit($count)->offset($offset);
+ }
+
+ return $query->get();
+ }
+
+ protected function processTableData($messages, $from, $to)
+ {
+ $fromCode = $from ? $from->code : null;
+ $toCode = $to ? $to->code : null;
+
+ $data = [];
+ foreach ($messages as $message) {
+ $toContent = $message->forLocale($toCode);
+ if ($this->hideTranslated && $toContent) {
+ continue;
+ }
+
+ $data[] = [
+ 'id' => $message->id,
+ 'code' => $message->code,
+ 'from' => $message->forLocale($fromCode),
+ 'to' => $toContent,
+ 'found' => $message->found ? '' : Lang::get('rainlab.translate::lang.messages.not_found'),
+ ];
+ }
+
+ return $data;
+ }
+
+ protected function updateTableData($message, $data)
+ {
+ if (!$message) {
+ return;
+ }
+
+ $fromCode = post('locale_from');
+ $toCode = post('locale_to');
+
+ // @todo This should be unified to a single save()
+ if ($fromCode) {
+ $fromValue = array_get($data, 'from');
+ if ($fromValue != $message->forLocale($fromCode)) {
+ $message->toLocale($fromCode, $fromValue);
+ }
+ }
+
+ if ($toCode) {
+ $toValue = array_get($data, 'to');
+ if ($toValue != $message->forLocale($toCode)) {
+ $message->toLocale($toCode, $toValue);
+ }
+ }
+ }
+}
diff --git a/plugins/rainlab/translate/controllers/locales/_create_form.htm b/plugins/rainlab/translate/controllers/locales/_create_form.htm
new file mode 100644
index 00000000..e33ba274
--- /dev/null
+++ b/plugins/rainlab/translate/controllers/locales/_create_form.htm
@@ -0,0 +1,55 @@
+= Form::open(['id' => 'createForm']) ?>
+
+
+
+ fatalError): ?>
+
+
+ = $this->formRender() ?>
+
+
+
+
+
+
+
= e(trans($this->fatalError)) ?>
+
+
+
+
+
+
+
+= Form::close() ?>
diff --git a/plugins/rainlab/translate/controllers/locales/_hint.htm b/plugins/rainlab/translate/controllers/locales/_hint.htm
new file mode 100644
index 00000000..45d85a49
--- /dev/null
+++ b/plugins/rainlab/translate/controllers/locales/_hint.htm
@@ -0,0 +1,4 @@
+
+
+ = e(trans('rainlab.translate::lang.locale.hint_locales')) ?>
+
\ No newline at end of file
diff --git a/plugins/rainlab/translate/controllers/locales/_list_toolbar.htm b/plugins/rainlab/translate/controllers/locales/_list_toolbar.htm
new file mode 100644
index 00000000..126253ba
--- /dev/null
+++ b/plugins/rainlab/translate/controllers/locales/_list_toolbar.htm
@@ -0,0 +1,15 @@
+
diff --git a/plugins/rainlab/translate/controllers/locales/_reorder_toolbar.htm b/plugins/rainlab/translate/controllers/locales/_reorder_toolbar.htm
new file mode 100644
index 00000000..30a2fd6e
--- /dev/null
+++ b/plugins/rainlab/translate/controllers/locales/_reorder_toolbar.htm
@@ -0,0 +1,7 @@
+
\ No newline at end of file
diff --git a/plugins/rainlab/translate/controllers/locales/_update_form.htm b/plugins/rainlab/translate/controllers/locales/_update_form.htm
new file mode 100644
index 00000000..c820921d
--- /dev/null
+++ b/plugins/rainlab/translate/controllers/locales/_update_form.htm
@@ -0,0 +1,67 @@
+= Form::open(['id' => 'updateForm']) ?>
+
+
+
+
+
+ fatalError): ?>
+
+
+ = $this->formRender() ?>
+
+
+
+
+
+
+
= e(trans($this->fatalError)) ?>
+
+
+
+
+
+
+
+= Form::close() ?>
diff --git a/plugins/rainlab/translate/controllers/locales/config_form.yaml b/plugins/rainlab/translate/controllers/locales/config_form.yaml
new file mode 100644
index 00000000..781bcfbe
--- /dev/null
+++ b/plugins/rainlab/translate/controllers/locales/config_form.yaml
@@ -0,0 +1,7 @@
+# ===================================
+# Form Behavior Config
+# ===================================
+
+form: ~/plugins/rainlab/translate/models/locale/fields.yaml
+modelClass: RainLab\Translate\Models\Locale
+defaultRedirect: rainlab/translate/locales
diff --git a/plugins/rainlab/translate/controllers/locales/config_list.yaml b/plugins/rainlab/translate/controllers/locales/config_list.yaml
new file mode 100644
index 00000000..6447ec88
--- /dev/null
+++ b/plugins/rainlab/translate/controllers/locales/config_list.yaml
@@ -0,0 +1,52 @@
+# ===================================
+# List Behavior Config
+# ===================================
+
+# Model List Column configuration
+list: ~/plugins/rainlab/translate/models/locale/columns.yaml
+
+# Model Class name
+modelClass: RainLab\Translate\Models\Locale
+
+# List Title
+title: rainlab.translate::lang.locale.title
+
+# Link URL for each record
+# recordUrl: rainlab/translate/locale/update/:id
+
+recordOnClick: $.translateLocales.clickRecord(:id)
+
+# Message to display if the list is empty
+noRecordsMessage: backend::lang.list.no_records
+
+# Records to display per page
+recordsPerPage: 20
+
+# Displays the list column set up button
+showSetup: true
+
+# Displays the sorting link on each column
+showSorting: true
+
+# Default sorting column
+defaultSort:
+ column: sort_order
+ direction: asc
+
+# Display checkboxes next to each record
+# showCheckboxes: true
+
+# Toolbar widget configuration
+toolbar:
+ # Partial for toolbar buttons
+ buttons: list_toolbar
+
+ # Search widget configuration
+ search:
+ prompt: backend::lang.list.search_prompt
+
+# Reordering
+structure:
+ showTree: false
+ showReorder: true
+ maxDepth: 1
diff --git a/plugins/rainlab/translate/controllers/locales/config_reorder.yaml b/plugins/rainlab/translate/controllers/locales/config_reorder.yaml
new file mode 100644
index 00000000..797a21f5
--- /dev/null
+++ b/plugins/rainlab/translate/controllers/locales/config_reorder.yaml
@@ -0,0 +1,17 @@
+# ===================================
+# Reorder Behavior Config
+# ===================================
+
+# Reorder Title
+title: rainlab.translate::lang.locale.reorder_title
+
+# Attribute name
+nameFrom: name
+
+# Model Class name
+modelClass: RainLab\Translate\Models\Locale
+
+# Toolbar widget configuration
+toolbar:
+ # Partial for toolbar buttons
+ buttons: reorder_toolbar
\ No newline at end of file
diff --git a/plugins/rainlab/translate/controllers/locales/index.htm b/plugins/rainlab/translate/controllers/locales/index.htm
new file mode 100644
index 00000000..5bdfd58e
--- /dev/null
+++ b/plugins/rainlab/translate/controllers/locales/index.htm
@@ -0,0 +1,12 @@
+
+
+
+
+
+ = $this->makeHintPartial('translation_locales_hint', 'hint') ?>
+
+
+= $this->listRender() ?>
diff --git a/plugins/rainlab/translate/controllers/locales/reorder.htm b/plugins/rainlab/translate/controllers/locales/reorder.htm
new file mode 100644
index 00000000..f820c6ae
--- /dev/null
+++ b/plugins/rainlab/translate/controllers/locales/reorder.htm
@@ -0,0 +1,9 @@
+
+
+
+
+= $this->reorderRender() ?>
diff --git a/plugins/rainlab/translate/controllers/messages/_hint.htm b/plugins/rainlab/translate/controllers/messages/_hint.htm
new file mode 100644
index 00000000..18a041b5
--- /dev/null
+++ b/plugins/rainlab/translate/controllers/messages/_hint.htm
@@ -0,0 +1,6 @@
+
+
+ = Lang::get('rainlab.translate::lang.messages.hint_translate') ?>
+ = Lang::get('rainlab.translate::lang.messages.clear_cache_hint') ?>
+ = Lang::get('rainlab.translate::lang.messages.scan_messages_hint') ?>
+
\ No newline at end of file
diff --git a/plugins/rainlab/translate/controllers/messages/_messages.htm b/plugins/rainlab/translate/controllers/messages/_messages.htm
new file mode 100644
index 00000000..87ed504c
--- /dev/null
+++ b/plugins/rainlab/translate/controllers/messages/_messages.htm
@@ -0,0 +1,10 @@
+
+
+
+ = $this->makePartial('table_toolbar') ?>
+
+
+ = $table->render() ?>
+
diff --git a/plugins/rainlab/translate/controllers/messages/_scan_messages_form.htm b/plugins/rainlab/translate/controllers/messages/_scan_messages_form.htm
new file mode 100644
index 00000000..101f82a3
--- /dev/null
+++ b/plugins/rainlab/translate/controllers/messages/_scan_messages_form.htm
@@ -0,0 +1,80 @@
+
+
+
diff --git a/plugins/rainlab/translate/controllers/messages/_table_headers.htm b/plugins/rainlab/translate/controllers/messages/_table_headers.htm
new file mode 100644
index 00000000..8a5c03c9
--- /dev/null
+++ b/plugins/rainlab/translate/controllers/messages/_table_headers.htm
@@ -0,0 +1,105 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/rainlab/translate/controllers/messages/_table_toolbar.htm b/plugins/rainlab/translate/controllers/messages/_table_toolbar.htm
new file mode 100644
index 00000000..eb24d787
--- /dev/null
+++ b/plugins/rainlab/translate/controllers/messages/_table_toolbar.htm
@@ -0,0 +1,24 @@
+
diff --git a/plugins/rainlab/translate/controllers/messages/config_import_export.yaml b/plugins/rainlab/translate/controllers/messages/config_import_export.yaml
new file mode 100644
index 00000000..3aaf39f1
--- /dev/null
+++ b/plugins/rainlab/translate/controllers/messages/config_import_export.yaml
@@ -0,0 +1,9 @@
+import:
+ title: 'rainlab.translate::lang.messages.import_messages_link'
+ modelClass: RainLab\Translate\Models\MessageImport
+ redirect: rainlab/translate/messages
+
+export:
+ title: 'rainlab.translate::lang.messages.export_messages_link'
+ modelClass: RainLab\Translate\Models\MessageExport
+ redirect: rainlab/translate/messages
diff --git a/plugins/rainlab/translate/controllers/messages/config_table.yaml b/plugins/rainlab/translate/controllers/messages/config_table.yaml
new file mode 100644
index 00000000..331f33ee
--- /dev/null
+++ b/plugins/rainlab/translate/controllers/messages/config_table.yaml
@@ -0,0 +1,17 @@
+# ===================================
+# Grid Widget Configuration
+# ===================================
+
+dataSource: server
+keyFrom: id
+recordsPerPage: 500
+adding: false
+searching: true
+
+columns:
+ from:
+ title: From
+ to:
+ title: To
+ found:
+ title: Scan errors
diff --git a/plugins/rainlab/translate/controllers/messages/export.htm b/plugins/rainlab/translate/controllers/messages/export.htm
new file mode 100644
index 00000000..589b1919
--- /dev/null
+++ b/plugins/rainlab/translate/controllers/messages/export.htm
@@ -0,0 +1,26 @@
+
+
+
+
+= Form::open(['class' => 'layout']) ?>
+
+
+ = $this->exportRender() ?>
+
+
+
+
+ = e(trans('rainlab.translate::lang.messages.export_messages_link')) ?>
+
+
+
+= Form::close() ?>
diff --git a/plugins/rainlab/translate/controllers/messages/import.htm b/plugins/rainlab/translate/controllers/messages/import.htm
new file mode 100644
index 00000000..95382159
--- /dev/null
+++ b/plugins/rainlab/translate/controllers/messages/import.htm
@@ -0,0 +1,26 @@
+
+
+
+
+= Form::open(['class' => 'layout']) ?>
+
+
+ = $this->importRender() ?>
+
+
+
+
+ = e(trans('rainlab.translate::lang.messages.import_messages_link')) ?>
+
+
+
+= Form::close() ?>
diff --git a/plugins/rainlab/translate/controllers/messages/index.htm b/plugins/rainlab/translate/controllers/messages/index.htm
new file mode 100644
index 00000000..3f3777af
--- /dev/null
+++ b/plugins/rainlab/translate/controllers/messages/index.htm
@@ -0,0 +1,34 @@
+
+
+
+
+
+ = $this->makeHintPartial('translation_messages_hint', 'hint') ?>
+
+
+= Form::open(['id' => 'messagesForm', 'class'=>'layout-item stretch layout-column', 'onsubmit'=>'return false']) ?>
+
+
+ = $this->makePartial('messages') ?>
+
+
+
+
+
+
+= Form::close() ?>
+
+
+
+
diff --git a/plugins/rainlab/translate/formwidgets/MLMarkdownEditor.php b/plugins/rainlab/translate/formwidgets/MLMarkdownEditor.php
new file mode 100644
index 00000000..ec746730
--- /dev/null
+++ b/plugins/rainlab/translate/formwidgets/MLMarkdownEditor.php
@@ -0,0 +1,107 @@
+initLocale();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function render()
+ {
+ $this->actAsParent();
+ $parentContent = parent::render();
+ $this->actAsParent(false);
+
+ if (!$this->isAvailable) {
+ return $parentContent;
+ }
+
+ $this->vars['markdowneditor'] = $parentContent;
+ return $this->makePartial('mlmarkdowneditor');
+ }
+
+ public function prepareVars()
+ {
+ parent::prepareVars();
+ $this->prepareLocaleVars();
+ }
+
+ /**
+ * getSaveValue returns an array of translated values for this field
+ * @return array
+ */
+ public function getSaveValue($value)
+ {
+ return $this->getLocaleSaveValue($value);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function loadAssets()
+ {
+ $this->actAsParent();
+ parent::loadAssets();
+ $this->actAsParent(false);
+
+ if (Locale::isAvailable()) {
+ $this->loadLocaleAssets();
+ $this->addJs('js/mlmarkdowneditor.js');
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function getParentViewPath()
+ {
+ return base_path().'/modules/backend/formwidgets/markdowneditor/partials';
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function getParentAssetPath()
+ {
+ return '/modules/backend/formwidgets/markdowneditor/assets';
+ }
+}
diff --git a/plugins/rainlab/translate/formwidgets/MLMediaFinder.php b/plugins/rainlab/translate/formwidgets/MLMediaFinder.php
new file mode 100644
index 00000000..5e21269f
--- /dev/null
+++ b/plugins/rainlab/translate/formwidgets/MLMediaFinder.php
@@ -0,0 +1,104 @@
+initLocale();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function render()
+ {
+ $this->actAsParent();
+ $parentContent = parent::render();
+ $this->actAsParent(false);
+
+ if (!$this->isAvailable) {
+ return $parentContent;
+ }
+
+ $this->vars['mediafinder'] = $parentContent;
+ return $this->makePartial('mlmediafinder');
+ }
+
+ /**
+ * Prepares the form widget view data
+ */
+ public function prepareVars()
+ {
+ parent::prepareVars();
+ $this->prepareLocaleVars();
+ // make root path of media files accessible
+ $this->vars['mediaPath'] = $this->mediaPath = MediaLibrary::url('/');
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getSaveValue($value)
+ {
+ return $this->getLocaleSaveValue($value);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function loadAssets()
+ {
+ $this->actAsParent();
+ parent::loadAssets();
+ $this->actAsParent(false);
+
+ if (Locale::isAvailable()) {
+ $this->loadLocaleAssets();
+ $this->addJs('js/mlmediafinder.js');
+ $this->addCss('css/mlmediafinder.css');
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function getParentViewPath()
+ {
+ return base_path().'/modules/backend/formwidgets/mediafinder/partials';
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function getParentAssetPath()
+ {
+ return '/modules/backend/formwidgets/mediafinder/assets';
+ }
+}
diff --git a/plugins/rainlab/translate/formwidgets/MLMediaFinderv2.php b/plugins/rainlab/translate/formwidgets/MLMediaFinderv2.php
new file mode 100644
index 00000000..250463b7
--- /dev/null
+++ b/plugins/rainlab/translate/formwidgets/MLMediaFinderv2.php
@@ -0,0 +1,108 @@
+initLocale();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function render()
+ {
+ $this->actAsParent();
+ $parentContent = parent::render();
+ $this->actAsParent(false);
+
+ if (!$this->isAvailable) {
+ return $parentContent;
+ }
+
+ $this->vars['mediafinder'] = $parentContent;
+ return $this->makePartial('mlmediafinder');
+ }
+
+ /**
+ * prepareVars prepares the form widget view data
+ */
+ public function prepareVars()
+ {
+ parent::prepareVars();
+ $this->prepareLocaleVars();
+ // make root path of media files accessible
+ $this->vars['mediaPath'] = $this->mediaPath = MediaLibrary::url('/');
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getSaveValue($value)
+ {
+ if ($this->isAvailable) {
+ return $this->getLocaleSaveValue($value);
+ }
+
+ return parent::getSaveValue($value);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function loadAssets()
+ {
+ $this->actAsParent();
+ parent::loadAssets();
+ $this->actAsParent(false);
+
+ if (Locale::isAvailable()) {
+ $this->loadLocaleAssets();
+ $this->addJs('js/mlmediafinder.js');
+ $this->addCss('../../mlmediafinder/assets/css/mlmediafinder.css');
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function getParentViewPath()
+ {
+ return base_path().'/modules/media/formwidgets/mediafinder/partials';
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function getParentAssetPath()
+ {
+ return '/modules/media/formwidgets/mediafinder/assets';
+ }
+}
diff --git a/plugins/rainlab/translate/formwidgets/MLNestedForm.php b/plugins/rainlab/translate/formwidgets/MLNestedForm.php
new file mode 100644
index 00000000..f04896b4
--- /dev/null
+++ b/plugins/rainlab/translate/formwidgets/MLNestedForm.php
@@ -0,0 +1,187 @@
+initLocale();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function render()
+ {
+ $this->actAsParent();
+ $parentContent = parent::render();
+ $this->actAsParent(false);
+
+ if (!$this->isAvailable) {
+ return $parentContent;
+ }
+
+ $this->vars['nestedform'] = $parentContent;
+
+ return $this->makePartial('mlnestedform');
+ }
+
+ /**
+ * prepareVars for viewing
+ */
+ public function prepareVars()
+ {
+ parent::prepareVars();
+
+ $this->prepareLocaleVars();
+ }
+
+ /**
+ * getSaveValue returns an array of translated values for this field
+ * @return array
+ */
+ public function getSaveValue($value)
+ {
+ $this->rewritePostValues();
+
+ return $this->getLocaleSaveValue($value);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function loadAssets()
+ {
+ $this->actAsParent();
+ parent::loadAssets();
+ $this->actAsParent(false);
+
+ if (Locale::isAvailable()) {
+ $this->loadLocaleAssets();
+ $this->addJs('js/mlnestedform.js');
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function getParentViewPath()
+ {
+ return base_path().'/modules/backend/formwidgets/nestedform/partials';
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function getParentAssetPath()
+ {
+ return '/modules/backend/formwidgets/nestedform/assets';
+ }
+
+ /**
+ * onSwitchItemLocale handler
+ */
+ public function onSwitchItemLocale()
+ {
+ if (!$locale = post('_nestedform_locale')) {
+ throw new ApplicationException('Unable to find a nested form locale for: '.$locale);
+ }
+
+ // Store previous value
+ $previousLocale = post('_nestedform_previous_locale');
+ $previousValue = $this->getPrimarySaveDataAsArray();
+
+ // Update widget to show form for switched locale
+ $lockerData = $this->getLocaleSaveDataAsArray($locale) ?: [];
+ $this->formWidget->setFormValues($lockerData);
+
+ $this->actAsParent();
+ $parentContent = parent::render();
+ $this->actAsParent(false);
+
+ return [
+ '#'.$this->getId('mlNestedForm') => $parentContent,
+ 'updateValue' => json_encode($previousValue),
+ 'updateLocale' => $previousLocale,
+ ];
+ }
+
+ /**
+ * getPrimarySaveDataAsArray gets the active values from the selected locale.
+ */
+ protected function getPrimarySaveDataAsArray(): array
+ {
+ return post($this->formField->getName()) ?: [];
+ }
+
+ /**
+ * getLocaleSaveDataAsArray returns the stored locale data as an array.
+ */
+ protected function getLocaleSaveDataAsArray($locale): ?array
+ {
+ $saveData = array_get($this->getLocaleSaveData(), $locale, []);
+
+ if (!is_array($saveData)) {
+ $saveData = json_decode($saveData, true);
+ }
+
+ return $saveData;
+ }
+
+ /**
+ * rewritePostValues since the locker does always contain the latest values,
+ * this method will take the save data from the nested form and merge it in to
+ * the locker based on which ever locale is selected using an item map
+ */
+ protected function rewritePostValues()
+ {
+ // Get the selected locale at postback
+ $data = post('RLTranslateNestedFormLocale');
+ $fieldName = implode('.', HtmlHelper::nameToArray($this->fieldName));
+ $locale = array_get($data, $fieldName);
+
+ if (!$locale) {
+ return;
+ }
+
+ // Splice the save data in to the locker data for selected locale
+ $data = $this->getPrimarySaveDataAsArray();
+ $fieldName = 'RLTranslate.'.$locale.'.'.implode('.', HtmlHelper::nameToArray($this->fieldName));
+
+ $requestData = Request::all();
+ array_set($requestData, $fieldName, json_encode($data));
+ $this->mergeWithPost($requestData);
+ }
+
+ /**
+ * mergeWithPost will apply postback values globally
+ */
+ protected function mergeWithPost(array $values)
+ {
+ Request::merge($values);
+ $_POST = array_merge($_POST, $values);
+ }
+}
diff --git a/plugins/rainlab/translate/formwidgets/MLRepeater.php b/plugins/rainlab/translate/formwidgets/MLRepeater.php
new file mode 100644
index 00000000..237d145b
--- /dev/null
+++ b/plugins/rainlab/translate/formwidgets/MLRepeater.php
@@ -0,0 +1,239 @@
+initLocale();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function render()
+ {
+ $this->actAsParent();
+ $parentContent = parent::render();
+ $this->actAsParent(false);
+
+ if (!$this->isAvailable) {
+ return $parentContent;
+ }
+
+ $this->vars['repeater'] = $parentContent;
+ return $this->makePartial('mlrepeater');
+ }
+
+ /**
+ * prepareVars
+ */
+ public function prepareVars()
+ {
+ parent::prepareVars();
+ $this->prepareLocaleVars();
+ }
+
+ /**
+ * getSaveValue returns an array of translated values for this field
+ * @return array
+ */
+ public function getSaveValue($value)
+ {
+ $this->rewritePostValues();
+
+ return $this->getLocaleSaveValue(is_array($value) ? array_values($value) : $value);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function loadAssets()
+ {
+ $this->actAsParent();
+ parent::loadAssets();
+ $this->actAsParent(false);
+
+ if (Locale::isAvailable()) {
+ $this->loadLocaleAssets();
+ $this->addJs('js/mlrepeater.js');
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function getParentViewPath()
+ {
+ return base_path().'/modules/backend/formwidgets/repeater/partials';
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function getParentAssetPath()
+ {
+ return '/modules/backend/formwidgets/repeater/assets';
+ }
+
+ /**
+ * onAddItem
+ */
+ public function onAddItem()
+ {
+ $this->actAsParent();
+ return parent::onAddItem();
+ }
+
+ /**
+ * onDuplicateItem
+ */
+ public function onDuplicateItem()
+ {
+ $this->actAsParent();
+ return parent::onDuplicateItem();
+ }
+
+ /**
+ * onSwitchItemLocale
+ */
+ public function onSwitchItemLocale()
+ {
+ if (!$locale = post('_repeater_locale')) {
+ throw new ApplicationException('Unable to find a repeater locale for: '.$locale);
+ }
+
+ // Store previous value
+ $previousLocale = post('_repeater_previous_locale');
+ $previousValue = $this->getPrimarySaveDataAsArray();
+
+ // Update widget to show form for switched locale
+ $lockerData = $this->getLocaleSaveDataAsArray($locale) ?: [];
+ $this->reprocessLocaleItems($lockerData);
+
+ foreach ($this->formWidgets as $key => $widget) {
+ $value = array_shift($lockerData);
+ if (!$value) {
+ unset($this->formWidgets[$key]);
+ }
+ else {
+ $widget->setFormValues($value);
+ }
+ }
+
+ $this->actAsParent();
+ $parentContent = parent::render();
+ $this->actAsParent(false);
+
+ return [
+ '#'.$this->getId('mlRepeater') => $parentContent,
+ 'updateValue' => json_encode($previousValue),
+ 'updateLocale' => $previousLocale,
+ ];
+ }
+
+ /**
+ * reprocessLocaleItems ensures that the current locale data is processed by
+ * the repeater instead of the original non-translated data
+ * @return void
+ */
+ protected function reprocessLocaleItems($data)
+ {
+ $this->formWidgets = [];
+
+ $this->formField->value = $data;
+
+ $key = implode('.', HtmlHelper::nameToArray($this->formField->getName()));
+
+ $requestData = Request::all();
+
+ array_set($requestData, $key, $data);
+
+ $this->mergeWithPost($requestData);
+
+ $this->processItems();
+ }
+
+ /**
+ * getPrimarySaveDataAsArray gets the active values from the selected locale.
+ * @return array
+ */
+ protected function getPrimarySaveDataAsArray()
+ {
+ $data = post($this->formField->getName()) ?: [];
+
+ return $this->processSaveValue($data);
+ }
+
+ /**
+ * getLocaleSaveDataAsArray returns the stored locale data as an array.
+ * @return array
+ */
+ protected function getLocaleSaveDataAsArray($locale)
+ {
+ $saveData = array_get($this->getLocaleSaveData(), $locale, []);
+
+ if (!is_array($saveData)) {
+ $saveData = json_decode($saveData, true);
+ }
+
+ return $saveData;
+ }
+
+ /**
+ * rewritePostValues since the locker does always contain the latest values, this method
+ * will take the save data from the repeater and merge it in to the
+ * locker based on which ever locale is selected using an item map
+ * @return void
+ */
+ protected function rewritePostValues()
+ {
+ // Get the selected locale at postback
+ $data = post('RLTranslateRepeaterLocale');
+ $fieldName = implode('.', HtmlHelper::nameToArray($this->fieldName));
+ $locale = array_get($data, $fieldName);
+
+ if (!$locale) {
+ return;
+ }
+
+ // Splice the save data in to the locker data for selected locale
+ $data = $this->getPrimarySaveDataAsArray();
+ $fieldName = 'RLTranslate.'.$locale.'.'.implode('.', HtmlHelper::nameToArray($this->fieldName));
+
+ $requestData = Request::all();
+ array_set($requestData, $fieldName, json_encode($data));
+ $this->mergeWithPost($requestData);
+ }
+
+ /**
+ * mergeWithPost will apply postback values globally
+ */
+ protected function mergeWithPost(array $values)
+ {
+ Request::merge($values);
+ $_POST = array_merge($_POST, $values);
+ }
+}
diff --git a/plugins/rainlab/translate/formwidgets/MLRichEditor.php b/plugins/rainlab/translate/formwidgets/MLRichEditor.php
new file mode 100644
index 00000000..92f4bd28
--- /dev/null
+++ b/plugins/rainlab/translate/formwidgets/MLRichEditor.php
@@ -0,0 +1,119 @@
+initLocale();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function render()
+ {
+ $this->actAsParent();
+ $parentContent = parent::render();
+ $this->actAsParent(false);
+
+ if (!$this->isAvailable) {
+ return $parentContent;
+ }
+
+ $this->vars['richeditor'] = $parentContent;
+ return $this->makePartial('mlricheditor');
+ }
+
+ /**
+ * prepareVars
+ */
+ public function prepareVars()
+ {
+ parent::prepareVars();
+ $this->prepareLocaleVars();
+ }
+
+ /**
+ * getSaveValue returns an array of translated values for this field
+ * @return array
+ */
+ public function getSaveValue($value)
+ {
+ return $this->getLocaleSaveValue($value);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function loadAssets()
+ {
+ $this->actAsParent();
+ parent::loadAssets();
+ $this->actAsParent(false);
+
+ if (Locale::isAvailable()) {
+ $this->loadLocaleAssets();
+ $this->addJs('js/mlricheditor.js');
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function onLoadPageLinksForm()
+ {
+ $this->actAsParent();
+ return parent::onLoadPageLinksForm();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function getParentViewPath()
+ {
+ return base_path().'/modules/backend/formwidgets/richeditor/partials';
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function getParentAssetPath()
+ {
+ return '/modules/backend/formwidgets/richeditor/assets';
+ }
+}
diff --git a/plugins/rainlab/translate/formwidgets/MLText.php b/plugins/rainlab/translate/formwidgets/MLText.php
new file mode 100644
index 00000000..c0a74bcb
--- /dev/null
+++ b/plugins/rainlab/translate/formwidgets/MLText.php
@@ -0,0 +1,59 @@
+initLocale();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function render()
+ {
+ $this->prepareLocaleVars();
+
+ if ($this->isAvailable) {
+ return $this->makePartial('mltext');
+ }
+ else {
+ return $this->renderFallbackField();
+ }
+ }
+
+ /**
+ * getSaveValue returns an array of translated values for this field
+ * @return array
+ */
+ public function getSaveValue($value)
+ {
+ return $this->getLocaleSaveValue($value);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function loadAssets()
+ {
+ $this->loadLocaleAssets();
+ }
+}
diff --git a/plugins/rainlab/translate/formwidgets/MLTextarea.php b/plugins/rainlab/translate/formwidgets/MLTextarea.php
new file mode 100644
index 00000000..12b0837c
--- /dev/null
+++ b/plugins/rainlab/translate/formwidgets/MLTextarea.php
@@ -0,0 +1,64 @@
+initLocale();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function render()
+ {
+ $this->prepareLocaleVars();
+
+ if ($this->isAvailable) {
+ return $this->makePartial('mltextarea');
+ }
+ else {
+ return $this->renderFallbackField();
+ }
+ }
+
+ /**
+ * getSaveValue returns an array of translated values for this field
+ * @return array
+ */
+ public function getSaveValue($value)
+ {
+ return $this->getLocaleSaveValue($value);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function loadAssets()
+ {
+ $this->loadLocaleAssets();
+ }
+}
diff --git a/plugins/rainlab/translate/formwidgets/mlmarkdowneditor/assets/js/mlmarkdowneditor.js b/plugins/rainlab/translate/formwidgets/mlmarkdowneditor/assets/js/mlmarkdowneditor.js
new file mode 100644
index 00000000..e6b0f565
--- /dev/null
+++ b/plugins/rainlab/translate/formwidgets/mlmarkdowneditor/assets/js/mlmarkdowneditor.js
@@ -0,0 +1,107 @@
+/*
+ * MLMarkdownEditor plugin
+ *
+ * Data attributes:
+ * - data-control="mlmarkdowneditor" - enables the plugin on an element
+ * - data-textarea-element="textarea#id" - an option with a value
+ *
+ * JavaScript API:
+ * $('a#someElement').mlMarkdownEditor({ option: 'value' })
+ *
+ */
+
++function ($) { "use strict";
+
+ var Base = $.oc.foundation.base,
+ BaseProto = Base.prototype
+
+ // MLMARKDOWNEDITOR CLASS DEFINITION
+ // ============================
+
+ var MLMarkdownEditor = function(element, options) {
+ this.options = options
+ this.$el = $(element)
+ this.$textarea = $(options.textareaElement)
+ this.$markdownEditor = $('[data-control=markdowneditor]:first', this.$el)
+
+ $.oc.foundation.controlUtils.markDisposable(element)
+ Base.call(this)
+
+ // Init
+ this.init()
+ }
+
+ MLMarkdownEditor.prototype = Object.create(BaseProto)
+ MLMarkdownEditor.prototype.constructor = MLMarkdownEditor
+
+ MLMarkdownEditor.DEFAULTS = {
+ textareaElement: null,
+ placeholderField: null,
+ defaultLocale: 'en'
+ }
+
+ MLMarkdownEditor.prototype.init = function() {
+ this.$el.multiLingual()
+
+ this.$el.on('setLocale.oc.multilingual', this.proxy(this.onSetLocale))
+ this.$textarea.on('changeContent.oc.markdowneditor', this.proxy(this.onChangeContent))
+
+ this.$el.one('dispose-control', this.proxy(this.dispose))
+ }
+
+ MLMarkdownEditor.prototype.dispose = function() {
+ this.$el.off('setLocale.oc.multilingual', this.proxy(this.onSetLocale))
+ this.$textarea.off('changeContent.oc.markdowneditor', this.proxy(this.onChangeContent))
+ this.$el.off('dispose-control', this.proxy(this.dispose))
+
+ this.$el.removeData('oc.mlMarkdownEditor')
+
+ this.$textarea = null
+ this.$markdownEditor = null
+ this.$el = null
+
+ this.options = null
+
+ BaseProto.dispose.call(this)
+ }
+
+ MLMarkdownEditor.prototype.onSetLocale = function(e, locale, localeValue) {
+ if (typeof localeValue === 'string' && this.$markdownEditor.data('oc.markdownEditor')) {
+ this.$markdownEditor.markdownEditor('setContent', localeValue);
+ }
+ }
+
+ MLMarkdownEditor.prototype.onChangeContent = function(ev, markdowneditor, value) {
+ this.$el.multiLingual('setLocaleValue', value)
+ }
+
+ var old = $.fn.mlMarkdownEditor
+
+ $.fn.mlMarkdownEditor = function (option) {
+ var args = Array.prototype.slice.call(arguments, 1), result
+
+ this.each(function () {
+ var $this = $(this)
+ var data = $this.data('oc.mlMarkdownEditor')
+ var options = $.extend({}, MLMarkdownEditor.DEFAULTS, $this.data(), typeof option == 'object' && option)
+ if (!data) $this.data('oc.mlMarkdownEditor', (data = new MLMarkdownEditor(this, options)))
+ if (typeof option == 'string') result = data[option].apply(data, args)
+ if (typeof result != 'undefined') return false
+ })
+
+ return result ? result : this
+ }
+
+ $.fn.mlMarkdownEditor.Constructor = MLMarkdownEditor;
+
+ $.fn.mlMarkdownEditor.noConflict = function () {
+ $.fn.mlMarkdownEditor = old
+ return this
+ }
+
+ $(document).render(function (){
+ $('[data-control="mlmarkdowneditor"]').mlMarkdownEditor()
+ })
+
+
+}(window.jQuery);
diff --git a/plugins/rainlab/translate/formwidgets/mlmarkdowneditor/partials/_mlmarkdowneditor.htm b/plugins/rainlab/translate/formwidgets/mlmarkdowneditor/partials/_mlmarkdowneditor.htm
new file mode 100644
index 00000000..03179f90
--- /dev/null
+++ b/plugins/rainlab/translate/formwidgets/mlmarkdowneditor/partials/_mlmarkdowneditor.htm
@@ -0,0 +1,23 @@
+
+
+
+ = $markdowneditor ?>
+
+
+
+
+ = $this->makeMLPartial('locale_values') ?>
+
+ = $this->makeMLPartial('locale_selector') ?>
+
diff --git a/plugins/rainlab/translate/formwidgets/mlmediafinder/assets/css/mlmediafinder.css b/plugins/rainlab/translate/formwidgets/mlmediafinder/assets/css/mlmediafinder.css
new file mode 100644
index 00000000..9bd687b1
--- /dev/null
+++ b/plugins/rainlab/translate/formwidgets/mlmediafinder/assets/css/mlmediafinder.css
@@ -0,0 +1,8 @@
+.field-multilingual-mediafinder > .ml-btn {
+ top: -28px;
+ right: 4px;
+}
+
+.field-multilingual-mediafinder > .ml-dropdown-menu {
+ top: 1px;
+}
diff --git a/plugins/rainlab/translate/formwidgets/mlmediafinder/assets/js/mlmediafinder.js b/plugins/rainlab/translate/formwidgets/mlmediafinder/assets/js/mlmediafinder.js
new file mode 100644
index 00000000..1b512341
--- /dev/null
+++ b/plugins/rainlab/translate/formwidgets/mlmediafinder/assets/js/mlmediafinder.js
@@ -0,0 +1,136 @@
+/*
+ * MLMediaFinder plugin
+ *
+ * Data attributes:
+ * - data-control="mlmediafinder" - enables the plugin on an element
+ * - data-option="value" - an option with a value
+ *
+ * JavaScript API:
+ * $('a#someElement').mlMediaFinder({ option: 'value' })
+ *
+ * Dependences:
+ * - mediafinder (mediafinder.js)
+ */
+
++function($) { "use strict";
+ var Base = $.oc.foundation.base,
+ BaseProto = Base.prototype
+
+ // MLMEDIAFINDER CLASS DEFINITION
+ // ============================
+
+ var MLMediaFinder = function(element, options) {
+ this.options = options
+ this.$el = $(element)
+ this.$mediafinder = $('[data-control=mediafinder]', this.$el)
+ this.$findValue = $('[data-find-value]', this.$el)
+
+ $.oc.foundation.controlUtils.markDisposable(element)
+ Base.call(this)
+ this.init()
+ }
+
+ MLMediaFinder.prototype = Object.create(BaseProto)
+ MLMediaFinder.prototype.constructor = MLMediaFinder
+
+ MLMediaFinder.DEFAULTS = {
+ placeholderField: null,
+ defaultLocale: 'en',
+ mediaPath: '/',
+ }
+
+ MLMediaFinder.prototype.init = function() {
+
+ this.$el.multiLingual()
+ this.$el.on('setLocale.oc.multilingual', this.proxy(this.onSetLocale))
+ this.$el.one('dispose-control', this.proxy(this.dispose))
+ // Listen for change event from mediafinder
+ this.$findValue.on('change', this.proxy(this.setValue))
+
+ // Stop here for preview mode
+ if (this.options.isPreview)
+ return
+ }
+
+ // Simplify setPath
+ MLMediaFinder.prototype.setValue = function(e) {
+ this.setPath($(e.target).val())
+ }
+
+ MLMediaFinder.prototype.dispose = function() {
+ this.$el.off('setLocale.oc.multilingual', this.proxy(this.onSetLocale))
+ this.$el.off('dispose-control', this.proxy(this.dispose))
+ this.$findValue.off('change', this.proxy(this.setValue))
+
+ this.$el.removeData('oc.mlMediaFinder')
+
+ this.$findValue = null
+ this.$mediafinder = null;
+ this.$el = null
+
+ // In some cases options could contain callbacks,
+ // so it's better to clean them up too.
+ this.options = null
+
+ BaseProto.dispose.call(this)
+ }
+
+
+ MLMediaFinder.prototype.onSetLocale = function(e, locale, localeValue) {
+ this.setPath(localeValue)
+ }
+
+ MLMediaFinder.prototype.setPath = function(localeValue) {
+ if (typeof localeValue === 'string') {
+ this.$findValue = localeValue;
+
+ var path = localeValue ? this.options.mediaPath + localeValue : ''
+
+ $('[data-find-image]', this.$mediafinder).attr('src', path)
+ $('[data-find-file-name]', this.$mediafinder).text(localeValue.substring(1))
+
+ // if value is present display image/file, else display open icon for media manager
+ this.$mediafinder.toggleClass('is-populated', !!localeValue)
+
+ this.$el.multiLingual('setLocaleValue', localeValue);
+ }
+ }
+
+ // MLMEDIAFINDER PLUGIN DEFINITION
+ // ============================
+
+ var old = $.fn.mlMediaFinder
+
+ $.fn.mlMediaFinder = function (option) {
+ var args = Array.prototype.slice.call(arguments, 1), result
+ this.each(function () {
+ var $this = $(this)
+ var data = $this.data('oc.mlMediaFinder')
+ var options = $.extend({}, MLMediaFinder.DEFAULTS, $this.data(), typeof option == 'object' && option)
+ if (!data) $this.data('oc.mlMediaFinder', (data = new MLMediaFinder(this, options)))
+ if (typeof option === 'string') result = data[option].apply(data, args)
+ if (typeof result !== 'undefined') return false
+ })
+
+ return result ? result : this
+ }
+
+ $.fn.mlMediaFinder.Constructor = MLMediaFinder
+
+ // MLMEDIAFINDER NO CONFLICT
+ // =================
+
+ $.fn.mlMediaFinder.noConflict = function () {
+ $.fn.mlMediaFinder = old
+ return this
+ }
+
+ // MLMEDIAFINDER DATA-API
+ // ===============
+
+ $(document).render(function () {
+ $('[data-control="mlmediafinder"]').mlMediaFinder()
+ })
+
+
+}(window.jQuery);
diff --git a/plugins/rainlab/translate/formwidgets/mlmediafinder/partials/_mlmediafinder.htm b/plugins/rainlab/translate/formwidgets/mlmediafinder/partials/_mlmediafinder.htm
new file mode 100644
index 00000000..8c0d86e5
--- /dev/null
+++ b/plugins/rainlab/translate/formwidgets/mlmediafinder/partials/_mlmediafinder.htm
@@ -0,0 +1,22 @@
+
+
+
+ = $mediafinder ?>
+
+
+
+
+ = $this->makeMLPartial('locale_values') ?>
+
+ = $this->makeMLPartial('locale_selector') ?>
+
diff --git a/plugins/rainlab/translate/formwidgets/mlmediafinderv2/assets/js/mlmediafinder.js b/plugins/rainlab/translate/formwidgets/mlmediafinderv2/assets/js/mlmediafinder.js
new file mode 100644
index 00000000..7e0f562c
--- /dev/null
+++ b/plugins/rainlab/translate/formwidgets/mlmediafinderv2/assets/js/mlmediafinder.js
@@ -0,0 +1,180 @@
+/*
+ * MLMediaFinder plugin
+ *
+ * Data attributes:
+ * - data-control="mlmediafinder" - enables the plugin on an element
+ * - data-option="value" - an option with a value
+ *
+ * JavaScript API:
+ * $('a#someElement').mlMediaFinder({ option: 'value' })
+ *
+ * Dependences:
+ * - mediafinder (mediafinder.js)
+ */
+
++function($) { "use strict";
+ var Base = $.oc.foundation.base,
+ BaseProto = Base.prototype
+
+ // MLMEDIAFINDER CLASS DEFINITION
+ // ============================
+
+ var MLMediaFinder = function(element, options) {
+ this.options = options
+ this.$el = $(element)
+ this.$mediafinder = $('[data-control=mediafinder]', this.$el)
+ this.$dataLocker = $('[data-data-locker]', this.$el)
+ this.isMulti = this.$mediafinder.hasClass('is-multi')
+
+ $.oc.foundation.controlUtils.markDisposable(element)
+ Base.call(this)
+ this.init()
+ }
+
+ MLMediaFinder.prototype = Object.create(BaseProto)
+ MLMediaFinder.prototype.constructor = MLMediaFinder
+
+ MLMediaFinder.DEFAULTS = {
+ placeholderField: null,
+ defaultLocale: 'en',
+ mediaPath: '/',
+ }
+
+ MLMediaFinder.prototype.init = function() {
+
+ this.$el.multiLingual()
+ this.$el.on('setLocale.oc.multilingual', this.proxy(this.onSetLocale))
+ this.$el.one('dispose-control', this.proxy(this.dispose))
+
+ // Listen for change event from mediafinder
+ this.$dataLocker.on('change', this.proxy(this.setValue))
+
+ // Stop here for preview mode
+ if (this.options.isPreview) {
+ return;
+ }
+ }
+
+ MLMediaFinder.prototype.dispose = function() {
+ this.$el.off('setLocale.oc.multilingual', this.proxy(this.onSetLocale));
+ this.$el.off('dispose-control', this.proxy(this.dispose));
+ this.$dataLocker.off('change', this.proxy(this.setValue));
+
+ this.$el.removeData('oc.mlMediaFinder');
+
+ this.$dataLocker = null;
+ this.$mediafinder = null;
+ this.$el = null;
+
+ // In some cases options could contain callbacks,
+ // so it's better to clean them up too.
+ this.options = null;
+
+ BaseProto.dispose.call(this)
+ }
+
+ MLMediaFinder.prototype.setValue = function(e) {
+ var mediafinder = this.$mediafinder.data('oc.mediaFinder'),
+ value = mediafinder.getValue();
+
+ if (value) {
+ if (this.isMulti) {
+ value = JSON.stringify(value);
+ }
+ else {
+ value = value[0];
+ }
+ }
+
+ this.setPath(value);
+ }
+
+ MLMediaFinder.prototype.onSetLocale = function(e, locale, localeValue) {
+ this.setPath(localeValue)
+ }
+
+ MLMediaFinder.prototype.setPath = function(localeValue) {
+ if (typeof localeValue === 'string') {
+ var self = this,
+ isMulti = this.isMulti,
+ mediaFinder = this.$mediafinder.data('oc.mediaFinder'),
+ items = [],
+ localeValueArr = [];
+
+ try {
+ localeValueArr = JSON.parse(localeValue);
+ if (!$.isArray(localeValueArr)) {
+ localeValueArr = [localeValueArr];
+ }
+ }
+ catch(e) {
+ isMulti = false;
+ }
+
+ mediaFinder.$filesContainer.empty();
+
+ if (isMulti) {
+ $.each(localeValueArr, function(k, v) {
+ if (v) {
+ items.push({
+ path: v,
+ publicUrl: self.options.mediaPath + v,
+ title: v.substring(1)
+ });
+ }
+ });
+ }
+ else {
+ if (localeValue) {
+ items = [{
+ path: localeValue,
+ publicUrl: this.options.mediaPath + localeValue,
+ title: localeValue.substring(1)
+ }];
+ }
+ }
+
+ mediaFinder.addItems(items);
+ mediaFinder.evalIsPopulated();
+ mediaFinder.evalIsMaxReached();
+
+ this.$el.multiLingual('setLocaleValue', localeValue);
+ }
+ }
+
+ // MLMEDIAFINDER PLUGIN DEFINITION
+ // ============================
+
+ var old = $.fn.mlMediaFinder
+
+ $.fn.mlMediaFinder = function (option) {
+ var args = Array.prototype.slice.call(arguments, 1), result
+ this.each(function () {
+ var $this = $(this)
+ var data = $this.data('oc.mlMediaFinder')
+ var options = $.extend({}, MLMediaFinder.DEFAULTS, $this.data(), typeof option == 'object' && option)
+ if (!data) $this.data('oc.mlMediaFinder', (data = new MLMediaFinder(this, options)))
+ if (typeof option === 'string') result = data[option].apply(data, args)
+ if (typeof result !== 'undefined') return false
+ })
+
+ return result ? result : this
+ }
+
+ $.fn.mlMediaFinder.Constructor = MLMediaFinder
+
+ // MLMEDIAFINDER NO CONFLICT
+ // =================
+
+ $.fn.mlMediaFinder.noConflict = function () {
+ $.fn.mlMediaFinder = old
+ return this
+ }
+
+ // MLMEDIAFINDER DATA-API
+ // ===============
+
+ $(document).render(function () {
+ $('[data-control="mlmediafinder"]').mlMediaFinder()
+ });
+}(window.jQuery);
diff --git a/plugins/rainlab/translate/formwidgets/mlmediafinderv2/partials/_mlmediafinder.htm b/plugins/rainlab/translate/formwidgets/mlmediafinderv2/partials/_mlmediafinder.htm
new file mode 100644
index 00000000..8c0d86e5
--- /dev/null
+++ b/plugins/rainlab/translate/formwidgets/mlmediafinderv2/partials/_mlmediafinder.htm
@@ -0,0 +1,22 @@
+
+
+
+ = $mediafinder ?>
+
+
+
+
+ = $this->makeMLPartial('locale_values') ?>
+
+ = $this->makeMLPartial('locale_selector') ?>
+
diff --git a/plugins/rainlab/translate/formwidgets/mlnestedform/assets/js/mlnestedform.js b/plugins/rainlab/translate/formwidgets/mlnestedform/assets/js/mlnestedform.js
new file mode 100644
index 00000000..ace10ad0
--- /dev/null
+++ b/plugins/rainlab/translate/formwidgets/mlnestedform/assets/js/mlnestedform.js
@@ -0,0 +1,126 @@
+/*
+ * MLNestedForm plugin
+ *
+ * Data attributes:
+ * - data-control="mlnestedform" - enables the plugin on an element
+ *
+ * JavaScript API:
+ * $('a#someElement').mlNestedForm({ option: 'value' })
+ *
+ */
++function ($) { "use strict";
+
+ var Base = $.oc.foundation.base,
+ BaseProto = Base.prototype
+
+ // MLREPEATER CLASS DEFINITION
+ // ============================
+
+ var MLNestedForm = function(element, options) {
+ this.options = options;
+ this.$el = $(element);
+ this.$selector = $('[data-locale-dropdown]', this.$el);
+ this.$locale = $('[data-nestedform-active-locale]', this.$el);
+ this.locale = options.defaultLocale;
+
+ $.oc.foundation.controlUtils.markDisposable(element);
+ Base.call(this);
+
+ // Init
+ this.init();
+ }
+
+ MLNestedForm.prototype = Object.create(BaseProto);
+ MLNestedForm.prototype.constructor = MLNestedForm;
+
+ MLNestedForm.DEFAULTS = {
+ switchHandler: null,
+ defaultLocale: 'en'
+ }
+
+ MLNestedForm.prototype.init = function() {
+ this.$el.multiLingual();
+
+ this.$el.on('setLocale.oc.multilingual', this.proxy(this.onSetLocale));
+
+ this.$el.one('dispose-control', this.proxy(this.dispose));
+ }
+
+ MLNestedForm.prototype.dispose = function() {
+ this.$el.off('setLocale.oc.multilingual', this.proxy(this.onSetLocale));
+
+ this.$el.off('dispose-control', this.proxy(this.dispose));
+
+ this.$el.removeData('oc.mlNestedForm');
+
+ this.$selector = null;
+ this.$locale = null;
+ this.locale = null;
+ this.$el = null;
+
+ this.options = null;
+
+ BaseProto.dispose.call(this);
+ }
+
+ MLNestedForm.prototype.onSetLocale = function(e, locale, localeValue) {
+ var self = this,
+ previousLocale = this.locale;
+
+ this.$el
+ .addClass('loading-indicator-container size-form-field')
+ .loadIndicator();
+
+ this.locale = locale;
+ this.$locale.val(locale);
+
+ this.$el.request(this.options.switchHandler, {
+ data: {
+ _nestedform_previous_locale: previousLocale,
+ _nestedform_locale: locale
+ },
+ success: function(data) {
+ self.$el.multiLingual('setLocaleValue', data.updateValue, data.updateLocale);
+ self.$el.loadIndicator('hide');
+ this.success(data);
+ }
+ });
+ }
+
+ // MLREPEATER PLUGIN DEFINITION
+ // ============================
+
+ var old = $.fn.mlNestedForm;
+
+ $.fn.mlNestedForm = function (option) {
+ var args = Array.prototype.slice.call(arguments, 1), result;
+ this.each(function () {
+ var $this = $(this);
+ var data = $this.data('oc.mlNestedForm');
+ var options = $.extend({}, MLNestedForm.DEFAULTS, $this.data(), typeof option == 'object' && option);
+ if (!data) $this.data('oc.mlNestedForm', (data = new MLNestedForm(this, options)));
+ if (typeof option == 'string') result = data[option].apply(data, args);
+ if (typeof result != 'undefined') return false;
+ })
+
+ return result ? result : this;
+ }
+
+ $.fn.mlNestedForm.Constructor = MLNestedForm;
+
+ // MLREPEATER NO CONFLICT
+ // =================
+
+ $.fn.mlNestedForm.noConflict = function () {
+ $.fn.mlNestedForm = old
+ return this
+ }
+
+ // MLREPEATER DATA-API
+ // ===============
+
+ $(document).render(function () {
+ $('[data-control="mlnestedform"]').mlNestedForm();
+ });
+
+}(window.jQuery);
diff --git a/plugins/rainlab/translate/formwidgets/mlnestedform/partials/_mlnestedform.htm b/plugins/rainlab/translate/formwidgets/mlnestedform/partials/_mlnestedform.htm
new file mode 100644
index 00000000..d46d6f93
--- /dev/null
+++ b/plugins/rainlab/translate/formwidgets/mlnestedform/partials/_mlnestedform.htm
@@ -0,0 +1,31 @@
+
+
diff --git a/plugins/rainlab/translate/formwidgets/mlrepeater/assets/js/mlrepeater.js b/plugins/rainlab/translate/formwidgets/mlrepeater/assets/js/mlrepeater.js
new file mode 100644
index 00000000..d412b0d1
--- /dev/null
+++ b/plugins/rainlab/translate/formwidgets/mlrepeater/assets/js/mlrepeater.js
@@ -0,0 +1,139 @@
+/*
+ * MLRepeater plugin
+ *
+ * Data attributes:
+ * - data-control="mlrepeater" - enables the plugin on an element
+ *
+ * JavaScript API:
+ * $('a#someElement').mlRepeater({ option: 'value' })
+ *
+ */
+
++function ($) { "use strict";
+
+ var Base = $.oc.foundation.base,
+ BaseProto = Base.prototype
+
+ // MLREPEATER CLASS DEFINITION
+ // ============================
+
+ var MLRepeater = function(element, options) {
+ this.options = options
+ this.$el = $(element)
+ this.$selector = $('[data-locale-dropdown]', this.$el)
+ this.$locale = $('[data-repeater-active-locale]', this.$el)
+ this.locale = options.defaultLocale
+
+ $.oc.foundation.controlUtils.markDisposable(element)
+ Base.call(this)
+
+ // Init
+ this.init()
+ }
+
+ MLRepeater.prototype = Object.create(BaseProto)
+ MLRepeater.prototype.constructor = MLRepeater
+
+ MLRepeater.DEFAULTS = {
+ switchHandler: null,
+ defaultLocale: 'en'
+ }
+
+ MLRepeater.prototype.init = function() {
+ this.$el.multiLingual()
+
+ this.checkEmptyItems()
+
+ $(document).on('render', this.proxy(this.checkEmptyItems))
+
+ this.$el.on('setLocale.oc.multilingual', this.proxy(this.onSetLocale))
+
+ this.$el.one('dispose-control', this.proxy(this.dispose))
+ }
+
+ MLRepeater.prototype.dispose = function() {
+
+ $(document).off('render', this.proxy(this.checkEmptyItems))
+
+ this.$el.off('setLocale.oc.multilingual', this.proxy(this.onSetLocale))
+
+ this.$el.off('dispose-control', this.proxy(this.dispose))
+
+ this.$el.removeData('oc.mlRepeater')
+
+ this.$selector = null
+ this.$locale = null
+ this.locale = null
+ this.$el = null
+
+ this.options = null
+
+ BaseProto.dispose.call(this)
+ }
+
+ MLRepeater.prototype.checkEmptyItems = function() {
+ var isEmpty = !$('ul.field-repeater-items > li', this.$el).length
+ this.$el.toggleClass('is-empty', isEmpty)
+ }
+
+ MLRepeater.prototype.onSetLocale = function(e, locale, localeValue) {
+ var self = this,
+ previousLocale = this.locale
+
+ this.$el
+ .addClass('loading-indicator-container size-form-field')
+ .loadIndicator()
+
+ this.locale = locale
+ this.$locale.val(locale)
+
+ this.$el.request(this.options.switchHandler, {
+ data: {
+ _repeater_previous_locale: previousLocale,
+ _repeater_locale: locale
+ },
+ success: function(data) {
+ self.$el.multiLingual('setLocaleValue', data.updateValue, data.updateLocale)
+ self.$el.loadIndicator('hide')
+ this.success(data)
+ }
+ })
+ }
+
+ // MLREPEATER PLUGIN DEFINITION
+ // ============================
+
+ var old = $.fn.mlRepeater
+
+ $.fn.mlRepeater = function (option) {
+ var args = Array.prototype.slice.call(arguments, 1), result
+ this.each(function () {
+ var $this = $(this)
+ var data = $this.data('oc.mlRepeater')
+ var options = $.extend({}, MLRepeater.DEFAULTS, $this.data(), typeof option == 'object' && option)
+ if (!data) $this.data('oc.mlRepeater', (data = new MLRepeater(this, options)))
+ if (typeof option == 'string') result = data[option].apply(data, args)
+ if (typeof result != 'undefined') return false
+ })
+
+ return result ? result : this
+ }
+
+ $.fn.mlRepeater.Constructor = MLRepeater
+
+ // MLREPEATER NO CONFLICT
+ // =================
+
+ $.fn.mlRepeater.noConflict = function () {
+ $.fn.mlRepeater = old
+ return this
+ }
+
+ // MLREPEATER DATA-API
+ // ===============
+
+ $(document).render(function () {
+ $('[data-control="mlrepeater"]').mlRepeater()
+ })
+
+}(window.jQuery);
diff --git a/plugins/rainlab/translate/formwidgets/mlrepeater/partials/_mlrepeater.htm b/plugins/rainlab/translate/formwidgets/mlrepeater/partials/_mlrepeater.htm
new file mode 100644
index 00000000..1d6480e9
--- /dev/null
+++ b/plugins/rainlab/translate/formwidgets/mlrepeater/partials/_mlrepeater.htm
@@ -0,0 +1,31 @@
+
+
+
+
+ = $repeater ?>
+
+
+
+
+
+
+
+ = $this->makeMLPartial('locale_values') ?>
+
+ = $this->makeMLPartial('locale_selector') ?>
+
diff --git a/plugins/rainlab/translate/formwidgets/mlricheditor/assets/js/mlricheditor.js b/plugins/rainlab/translate/formwidgets/mlricheditor/assets/js/mlricheditor.js
new file mode 100644
index 00000000..807848a1
--- /dev/null
+++ b/plugins/rainlab/translate/formwidgets/mlricheditor/assets/js/mlricheditor.js
@@ -0,0 +1,135 @@
+/*
+ * MLRichEditor plugin
+ *
+ * Data attributes:
+ * - data-control="mlricheditor" - enables the plugin on an element
+ * - data-textarea-element="textarea#id" - an option with a value
+ *
+ * JavaScript API:
+ * $('a#someElement').mlRichEditor({ option: 'value' })
+ *
+ */
+
++function ($) { "use strict";
+
+ var Base = $.oc.foundation.base,
+ BaseProto = Base.prototype
+
+ // MLRICHEDITOR CLASS DEFINITION
+ // ============================
+
+ var MLRichEditor = function(element, options) {
+ this.options = options
+ this.$el = $(element)
+ this.$textarea = $(options.textareaElement)
+ this.$richeditor = $('[data-control=richeditor]:first', this.$el)
+
+ $.oc.foundation.controlUtils.markDisposable(element)
+ Base.call(this)
+
+ // Init
+ this.init()
+ }
+
+ MLRichEditor.prototype = Object.create(BaseProto)
+ MLRichEditor.prototype.constructor = MLRichEditor
+
+ MLRichEditor.DEFAULTS = {
+ textareaElement: null,
+ placeholderField: null,
+ defaultLocale: 'en'
+ }
+
+ MLRichEditor.prototype.init = function() {
+ this.$el.multiLingual()
+
+ this.$el.on('setLocale.oc.multilingual', this.proxy(this.onSetLocale))
+ this.$textarea.on('syncContent.oc.richeditor', this.proxy(this.onSyncContent))
+
+ this.updateLayout()
+
+ $(window).on('resize', this.proxy(this.updateLayout))
+ $(window).on('oc.updateUi', this.proxy(this.updateLayout))
+ this.$el.one('dispose-control', this.proxy(this.dispose))
+ }
+
+ MLRichEditor.prototype.dispose = function() {
+ this.$el.off('setLocale.oc.multilingual', this.proxy(this.onSetLocale))
+ this.$textarea.off('syncContent.oc.richeditor', this.proxy(this.onSyncContent))
+ $(window).off('resize', this.proxy(this.updateLayout))
+ $(window).off('oc.updateUi', this.proxy(this.updateLayout))
+
+ this.$el.off('dispose-control', this.proxy(this.dispose))
+
+ this.$el.removeData('oc.mlRichEditor')
+
+ this.$textarea = null
+ this.$richeditor = null
+ this.$el = null
+
+ this.options = null
+
+ BaseProto.dispose.call(this)
+ }
+
+ MLRichEditor.prototype.onSetLocale = function(e, locale, localeValue) {
+ if (typeof localeValue === 'string' && this.$richeditor.data('oc.richEditor')) {
+ this.$richeditor.richEditor('setContent', localeValue);
+ }
+ }
+
+ MLRichEditor.prototype.onSyncContent = function(ev, richeditor, value) {
+ this.$el.multiLingual('setLocaleValue', value.html)
+ }
+
+ MLRichEditor.prototype.updateLayout = function() {
+ var $toolbar = $('.fr-toolbar', this.$el),
+ $btn = $('.ml-btn[data-active-locale]:first', this.$el),
+ $dropdown = $('.ml-dropdown-menu[data-locale-dropdown]:first', this.$el)
+
+ if (!$toolbar.length) {
+ return
+ }
+
+ var height = $toolbar.outerHeight(true)
+ $btn.css('top', height + 6)
+ $dropdown.css('top', height + 40)
+ }
+
+ // MLRICHEDITOR PLUGIN DEFINITION
+ // ============================
+
+ var old = $.fn.mlRichEditor
+
+ $.fn.mlRichEditor = function (option) {
+ var args = Array.prototype.slice.call(arguments, 1), result
+ this.each(function () {
+ var $this = $(this)
+ var data = $this.data('oc.mlRichEditor')
+ var options = $.extend({}, MLRichEditor.DEFAULTS, $this.data(), typeof option == 'object' && option)
+ if (!data) $this.data('oc.mlRichEditor', (data = new MLRichEditor(this, options)))
+ if (typeof option == 'string') result = data[option].apply(data, args)
+ if (typeof result != 'undefined') return false
+ })
+
+ return result ? result : this
+ }
+
+ $.fn.mlRichEditor.Constructor = MLRichEditor
+
+ // MLRICHEDITOR NO CONFLICT
+ // =================
+
+ $.fn.mlRichEditor.noConflict = function () {
+ $.fn.mlRichEditor = old
+ return this
+ }
+
+ // MLRICHEDITOR DATA-API
+ // ===============
+
+ $(document).render(function () {
+ $('[data-control="mlricheditor"]').mlRichEditor()
+ })
+
+}(window.jQuery);
diff --git a/plugins/rainlab/translate/formwidgets/mlricheditor/partials/_mlricheditor.htm b/plugins/rainlab/translate/formwidgets/mlricheditor/partials/_mlricheditor.htm
new file mode 100644
index 00000000..6f3e19c0
--- /dev/null
+++ b/plugins/rainlab/translate/formwidgets/mlricheditor/partials/_mlricheditor.htm
@@ -0,0 +1,23 @@
+
+
+
+ = $richeditor ?>
+
+
+
+
+ = $this->makeMLPartial('locale_values') ?>
+
+ = $this->makeMLPartial('locale_selector') ?>
+
diff --git a/plugins/rainlab/translate/formwidgets/mltext/partials/_mltext.htm b/plugins/rainlab/translate/formwidgets/mltext/partials/_mltext.htm
new file mode 100644
index 00000000..b8a57350
--- /dev/null
+++ b/plugins/rainlab/translate/formwidgets/mltext/partials/_mltext.htm
@@ -0,0 +1,36 @@
+
+previewMode): ?>
+ = $field->value ? e($field->value) : ' ' ?>
+
+
+
+ hasAttribute('maxlength') ? '' : 'maxlength="255"' ?>
+ = $field->getAttributes() ?>
+ />
+
+
+
+
+ = $this->makeMLPartial('locale_values') ?>
+
+ = $this->makeMLPartial('locale_selector') ?>
+
+
+
diff --git a/plugins/rainlab/translate/formwidgets/mltextarea/partials/_mltextarea.htm b/plugins/rainlab/translate/formwidgets/mltextarea/partials/_mltextarea.htm
new file mode 100644
index 00000000..1a710bd2
--- /dev/null
+++ b/plugins/rainlab/translate/formwidgets/mltextarea/partials/_mltextarea.htm
@@ -0,0 +1,32 @@
+
+previewMode): ?>
+ = nl2br(e($field->value)) ?>
+
+
+
+
+
+
+
+
+ = $this->makeMLPartial('locale_values') ?>
+
+ = $this->makeMLPartial('locale_selector') ?>
+
+
+
diff --git a/plugins/rainlab/translate/lang/ar/ar.php b/plugins/rainlab/translate/lang/ar/ar.php
new file mode 100644
index 00000000..aee91f44
--- /dev/null
+++ b/plugins/rainlab/translate/lang/ar/ar.php
@@ -0,0 +1,59 @@
+ [
+ 'name' => 'ترجمة',
+ 'description' => 'تفعيل تعدد اللغات للموقع.',
+ 'tab' => 'ترجمة',
+ 'manage_locales' => 'إدارة اللغات',
+ 'manage_messages' => 'إدارة النصوص',
+ ],
+ 'locale_picker' => [
+ 'component_name' => 'محدد اللغات',
+ 'component_description' => 'عرض قائمة لتحديد لغة الواجهة.',
+ ],
+ 'alternate_hreflang' => [
+ 'component_name' => 'عناصر بدائل hrefLang',
+ 'component_description' => 'إضافة بدائل اللغة للصفحة كعنصر hreflang '
+ ],
+ 'locale' => [
+ 'title' => 'إدارة اللغات',
+ 'update_title' => 'تحديث اللغة',
+ 'create_title' => 'إنشاء لغة',
+ 'select_label' => 'تحديد اللغة',
+ 'default_suffix' => 'افتراضي',
+ 'unset_default' => '":locale" بالفعل افتراضي ولايمكن نزع الافتراضية عنها.',
+ 'delete_default' => '":locale" هو افتراضي فلا يمكن حذفه.',
+ 'disabled_default' => '":locale" معطل فلايمكن تعيينه كافتراضي.',
+ 'name' => 'اسم',
+ 'code' => 'رمز',
+ 'is_default' => 'افتراضي',
+ 'is_default_help' => 'اللغة الافتراضية تمثل المحتوى قبل ترجمته.',
+ 'is_enabled' => 'مفعل',
+ 'is_enabled_help' => 'اللغات المعطلة ستكون غير متاحة في الواجهة.',
+ 'not_available_help' => 'لا توجد لغات أخرى لإعدادها.',
+ 'hint_locales' => 'إنشاء لغة جديدة هنا لترجمة محتويات الواجهة. اللغة الافتراضية تمثل المحتوى قبل ترجمته.',
+ 'reorder_title' => 'إعادة ترتيب لغات',
+ 'sort_order' => 'اتجاه الترتيب',
+ ],
+ 'messages' => [
+ 'title' => 'ترجمة النصوص',
+ 'description' => 'تحديث النصوص',
+ 'clear_cache_link' => 'مسح المخبآت',
+ 'clear_cache_loading' => 'يتم مسح مخبآت التطبيق...',
+ 'clear_cache_success' => 'تم مسح مخبآت التطبيق بنجاح!',
+ 'clear_cache_hint' => 'ربما يجب عليك الضغط على مسح المخبآت لترى التغييرات في الواجهة.',
+ 'scan_messages_link' => 'استكشاف النصوص',
+ 'scan_messages_begin_scan' => 'بدء الاستكشاف',
+ 'scan_messages_loading' => 'يتم استكشاف نصوص جديدة...',
+ 'scan_messages_success' => 'تم استكشاف ملفات القالب بنجاح!',
+ 'scan_messages_hint' => 'عند الضغط على استكشاف النصوص سيتم استكشاف ملفات القالب النشط لإيجاد نصوص جديدة لترجمتها.',
+ 'scan_messages_process' => 'هذه العملية ستقوم باستكشاف ملفات القالب النشط لإيجاد أي نصوص جديدة يمكن ترجمتها.',
+ 'scan_messages_process_limitations' => 'بعض النصوص قد لا يتم التقاطها وستظهر بعد أول استعمال لها.',
+ 'scan_messages_purge_label' => 'إفراغ جميع الرسائل أولا',
+ 'scan_messages_purge_help' => 'عند تحديد هذه الخاصية سيتم حذف جميع النصوص قبل علية الاستكشاف.',
+ 'scan_messages_purge_confirm' => 'هل أنت متأكد من حذف جميع النصوص? لن يمكنك التراجع بعد الآن!',
+ 'hint_translate' => 'هنا يمكن ترجمة النصوص المستعملة في الواجهة، سيتم الحفظ آليا.',
+ 'hide_translated' => 'إخفاء النصوص المترجمة',
+ 'export_messages_link' => 'تصدير النصوص',
+ 'import_messages_link' => 'استيراد النصوص',
+ ],
+];
diff --git a/plugins/rainlab/translate/lang/bg/lang.php b/plugins/rainlab/translate/lang/bg/lang.php
new file mode 100644
index 00000000..99d362c3
--- /dev/null
+++ b/plugins/rainlab/translate/lang/bg/lang.php
@@ -0,0 +1,46 @@
+ [
+ 'name' => 'Превод',
+ 'description' => 'Активира функцията многоезични уебсайтове.',
+ 'tab' => 'Преводи',
+ 'manage_locales' => 'Настройка на локация',
+ 'manage_messages' => 'Настройка на съобщения'
+ ],
+ 'locale_picker' => [
+ 'component_name' => 'Избор на местоположение',
+ 'component_description' => 'Показва меню за избор на езика на сайта.',
+ ],
+ 'locale' => [
+ 'title' => 'Настройка на езици',
+ 'update_title' => 'Актуализация на език',
+ 'create_title' => 'Създай език',
+ 'select_label' => 'Избери език',
+ 'default_suffix' => 'по подразбиране',
+ 'unset_default' => '":locale" вече е по подразбиране и не може да бъде изключен по подразбиране.',
+ 'disabled_default' => '":locale" е забранен и не може да бъде зададен по подразбиране.',
+ 'name' => 'Име',
+ 'code' => 'Код',
+ 'is_default' => 'По подразбиране',
+ 'is_default_help' => 'Основният език представлява съдържанието на сайта преди превод.',
+ 'is_enabled' => 'Включен',
+ 'is_enabled_help' => 'Изключените езици няма да са налични за преглед в сайта.',
+ 'not_available_help' => 'Не съществуват други езици за настройка.',
+ 'hint_locales' => 'Създайте нови езици за превод на съдържанието на сайта. Основният език представлява съдържанието, преди той да бъде преведен.',
+ ],
+ 'messages' => [
+ 'title' => 'Преведени Съобщения',
+ 'description' => 'Актуализарай Съобщения',
+ 'clear_cache_link' => 'Изтрий кеш-паметта',
+ 'clear_cache_loading' => 'Изчистване кеша на приложението...',
+ 'clear_cache_success' => 'Кеш кеш-паметта успешно изтрита!',
+ 'clear_cache_hint' => 'Може да е необходимо да кликнете на бутона Изтрий кеш-паметта за да видите промените на вашата страница.',
+ 'scan_messages_link' => 'Сканирай за съобщения',
+ 'scan_messages_loading' => 'Сканиране за нови съобщения...',
+ 'scan_messages_success' => 'Темата е сканирана успешно!',
+ 'scan_messages_hint' => 'Ако кликнете на Сканирай за съобщения това ще провери темата за нови съобщения (изречения) за превод.',
+ 'hint_translate' => 'Тук може да превеждате съобщенията (изреченията) на самият сайт, полетата ще се запазят автоматично.',
+ 'hide_translated' => 'Скрий преведените',
+ ],
+];
\ No newline at end of file
diff --git a/plugins/rainlab/translate/lang/cs/lang.php b/plugins/rainlab/translate/lang/cs/lang.php
new file mode 100644
index 00000000..8bc9f5d4
--- /dev/null
+++ b/plugins/rainlab/translate/lang/cs/lang.php
@@ -0,0 +1,46 @@
+ [
+ 'name' => 'Překlady',
+ 'description' => 'Aktivuje vícejazyčné stránky a překlady.',
+ 'tab' => 'Překlad',
+ 'manage_locales' => 'Správa jazyků',
+ 'manage_messages' => 'Správa překladů'
+ ],
+ 'locale_picker' => [
+ 'component_name' => 'Výběr jazyka',
+ 'component_description' => 'Zobrazí možnost výberu jazyka ve stránkách.',
+ ],
+ 'locale' => [
+ 'title' => 'Správa jazyků',
+ 'update_title' => 'Upravit jazyk',
+ 'create_title' => 'Přidat jazyk',
+ 'select_label' => 'Výběr jazyka',
+ 'default_suffix' => 'výchozí',
+ 'unset_default' => '":locale" je již výchozí a nemůže být odnastavena. Zkuste nastavit jiný jazyk jako výchozí.',
+ 'disabled_default' => '":locale" je neaktivní, takže nemůže být nastavený jako výchozí.',
+ 'name' => 'Název',
+ 'code' => 'Kód',
+ 'is_default' => 'Výchozí',
+ 'is_default_help' => 'Výchozí jazyk je jazyk webových stránek před překladem.',
+ 'is_enabled' => 'Aktivní',
+ 'is_enabled_help' => 'Neaktivní jazyky nepůjdou vybrat na webových stránkách.',
+ 'not_available_help' => 'Nemáte nastavené žádné jiné jazyky.',
+ 'hint_locales' => 'Zde můžete přidat nový jazyk pro překlad webových stránek. Výchozí jazyk reprezentuje obsah stránek ještě před překladem.',
+ ],
+ 'messages' => [
+ 'title' => 'Překlad textů',
+ 'description' => 'Upravit text',
+ 'clear_cache_link' => 'Vymazat cache',
+ 'clear_cache_loading' => 'Mazání aplikační cache...',
+ 'clear_cache_success' => 'Aplikační cache úspěšně vymazána!',
+ 'clear_cache_hint' => 'Možná bude potřeba kliknout na Vymazat cache , aby se změny projevily na webobých stránkách.',
+ 'scan_messages_link' => 'Najít texty k překladu',
+ 'scan_messages_loading' => 'Hledání textů k překladu...',
+ 'scan_messages_success' => 'Prohledávání šablon pro získání textů k překladu úspěšně dokončeno!',
+ 'scan_messages_hint' => 'Kliknutím na Najít texty k překladu zkontroluje soubory aktivních témat a najde texty k překladu.',
+ 'hint_translate' => 'Zde můžete přeložit texty použité na webových stránkách. Pole budou automaticky uložena.',
+ 'hide_translated' => 'Schovat přeložené',
+ ],
+];
diff --git a/plugins/rainlab/translate/lang/de/lang.php b/plugins/rainlab/translate/lang/de/lang.php
new file mode 100644
index 00000000..bd6e366b
--- /dev/null
+++ b/plugins/rainlab/translate/lang/de/lang.php
@@ -0,0 +1,47 @@
+ [
+ 'name' => 'Translate',
+ 'description' => 'Ermöglicht mehrsprachige Seiten.',
+ 'manage_locales' => 'Sprachen verwalten',
+ 'manage_messages' => 'Übersetzungen verwalten',
+ ],
+ 'locale_picker' => [
+ 'component_name' => 'Sprachauswahl',
+ 'component_description' => 'Zeigt ein Dropdown-Menü zur Auswahl der Sprache im Frontend.',
+ ],
+ 'locale' => [
+ 'title' => 'Sprachen verwalten',
+ 'update_title' => 'Sprache bearbeiten',
+ 'create_title' => 'Sprache erstellen',
+ 'select_label' => 'Sprache auswählen',
+ 'default_suffix' => 'Standard',
+ 'unset_default' => '":locale" ist bereits die Standardsprache und kann nicht abgewählt werden.',
+ 'disabled_default' => '":locale" ist deaktiviert und kann deshalb nicht als Standardsprache festgelegt werden.',
+ 'name' => 'Name',
+ 'code' => 'Code',
+ 'is_default' => 'Standard',
+ 'is_default_help' => 'Die Übersetzung der Standardsprache wird verwendet, um Inhalte anzuzeigen, die in der Sprache des Nutzers nicht vorhanden sind.',
+ 'is_enabled' => 'Aktiv',
+ 'is_enabled_help' => 'Deaktivierte Sprachen sind im Frontend nicht verfügbar.',
+ 'not_available_help' => 'Es gibt keine anderen Sprachen.',
+ 'hint_locales' => 'Hier können neue Sprachen angelegt werden, in die Inhalte im Frontend übersetzt werden können. Die Standardsprache dient als Ausgangssprache für Übersetzungen.',
+ 'reorder_title' => 'Sprachen sortieren',
+ 'sort_order' => 'Sortierung',
+ ],
+ 'messages' => [
+ 'title' => 'Übersetzungen verwalten',
+ 'description' => 'Inhalte verwalten und übersetzen',
+ 'clear_cache_link' => 'Cache leeren',
+ 'clear_cache_loading' => 'Leere Application-Cache...',
+ 'clear_cache_success' => 'Application-Cache erfolgreich geleert!',
+ 'clear_cache_hint' => 'Möglicherweise muss der Cache geleert werden (Button Cache leeren ), bevor Änderungen im Frontend sichtbar werden.',
+ 'scan_messages_link' => 'Nach Inhalten suchen',
+ 'scan_messages_loading' => 'Suche nach neuen Inhalte...',
+ 'scan_messages_success' => 'Suche nach neuen Inhalte erfolgreich abgeschlossen!',
+ 'scan_messages_hint' => 'Ein Klick auf Nach Inhalten suchen sucht nach neuen Inhalten, die übersetzt werden können.',
+ 'hint_translate' => 'Hier können Inhalte aus dem Frontend übersetzt werden. Die Felder werden automatisch gespeichert.',
+ 'hide_translated' => 'Bereits übersetzte Inhalte ausblenden',
+ 'export_messages_link' => 'Übersetzungen exportieren',
+ 'import_messages_link' => 'Übersetzungen importieren',
+ ],
+];
diff --git a/plugins/rainlab/translate/lang/en/lang.php b/plugins/rainlab/translate/lang/en/lang.php
new file mode 100644
index 00000000..8fe2ce3f
--- /dev/null
+++ b/plugins/rainlab/translate/lang/en/lang.php
@@ -0,0 +1,64 @@
+ [
+ 'name' => 'Translate',
+ 'description' => 'Enables multi-lingual websites.',
+ 'tab' => 'Translation',
+ 'manage_locales' => 'Manage locales',
+ 'manage_messages' => 'Manage messages',
+ ],
+ 'locale_picker' => [
+ 'component_name' => 'Locale Picker',
+ 'component_description' => 'Shows a dropdown to select a front-end language.',
+ ],
+ 'alternate_hreflang' => [
+ 'component_name' => 'Alternate hrefLang elements',
+ 'component_description' => 'Injects the language alternatives for page as hreflang elements'
+ ],
+ 'locale' => [
+ 'title' => 'Manage Languages',
+ 'update_title' => 'Update Language',
+ 'create_title' => 'Create Language',
+ 'select_label' => 'Select Language',
+ 'default_suffix' => 'default',
+ 'unset_default' => '":locale" is already default and cannot be unset as default.',
+ 'delete_default' => '":locale" is the default and cannot be deleted.',
+ 'disabled_default' => '":locale" is disabled and cannot be set as default.',
+ 'name' => 'Name',
+ 'code' => 'Code',
+ 'is_default' => 'Default',
+ 'is_default_help' => 'The default language represents the content before translation.',
+ 'is_enabled' => 'Enabled',
+ 'is_enabled_help' => 'Disabled languages will not be available in the front-end.',
+ 'not_available_help' => 'There are no other languages set up.',
+ 'hint_locales' => 'Create new languages here for translating front-end content. The default language represents the content before it has been translated.',
+ 'reorder_title' => 'Reorder Languages',
+ 'sort_order' => 'Sort Order',
+ ],
+ 'messages' => [
+ 'title' => 'Translate Messages',
+ 'description' => 'Update messages',
+ 'clear_cache_link' => 'Clear Cache',
+ 'clear_cache_loading' => 'Clearing application cache...',
+ 'clear_cache_success' => 'Cleared the application cache successfully!',
+ 'clear_cache_hint' => 'You may need to click Clear cache to see the changes on the front-end.',
+ 'scan_messages_link' => 'Scan for Messages',
+ 'scan_messages_begin_scan' => 'Begin Scan',
+ 'scan_messages_loading' => 'Scanning for new messages...',
+ 'scan_messages_success' => 'Scanned theme template files successfully!',
+ 'scan_messages_hint' => 'Clicking Scan for messages will check the active theme files for any new messages to translate.',
+ 'scan_messages_process' => 'This process will attempt to scan the active theme for messages that can be translated.',
+ 'scan_messages_process_limitations' => 'Some messages may not be captured and will only appear after the first time they are used.',
+ 'scan_messages_purge_label' => 'Purge all messages first',
+ 'scan_messages_purge_help' => 'If checked, this will delete all messages, including their translations, before performing the scan.',
+ 'scan_messages_purge_confirm' => 'Are you sure you want to delete all messages? This cannot be undone!',
+ 'scan_messages_purge_deleted_label' => 'Purge missing messages after scan',
+ 'scan_messages_purge_deleted_help' => 'If checked, after the scan is done, any messages the scanner did not find, including their translations, will be deleted. This cannot be undone!',
+ 'hint_translate' => 'Here you can translate messages used on the front-end, the fields will save automatically.',
+ 'hide_translated' => 'Hide translated',
+ 'export_messages_link' => 'Export Messages',
+ 'import_messages_link' => 'Import Messages',
+ 'not_found' => 'Not found',
+ 'found_help' => 'Whether any errors occurred during scanning.',
+ 'found_title' => 'Scan errors',
+ ],
+];
diff --git a/plugins/rainlab/translate/lang/es/lang.php b/plugins/rainlab/translate/lang/es/lang.php
new file mode 100644
index 00000000..f7a11c6c
--- /dev/null
+++ b/plugins/rainlab/translate/lang/es/lang.php
@@ -0,0 +1,51 @@
+ [
+ 'name' => 'Multilenguaje',
+ 'description' => 'Permite sitios web multilingües',
+ 'manage_locales' => 'Manage locales',
+ 'manage_messages' => 'Manage messages'
+ ],
+ 'locale_picker' => [
+ 'component_name' => 'Selección de idioma',
+ 'component_description' => 'Muestra una lista desplegable para seleccionar un idioma para el usuario',
+ ],
+ 'locale' => [
+ 'title' => 'Administrar idiomas',
+ 'update_title' => 'Actualizar idioma',
+ 'create_title' => 'Crear idioma',
+ 'select_label' => 'Seleccionar idioma',
+ 'default_suffix' => 'Defecto',
+ 'unset_default' => '": locale" ya está predeterminado y no puede ser nulo por defecto.',
+ 'disabled_default' => '":locale" esta desactivado y no puede ser idioma por defecto',
+ 'name' => 'Nombre',
+ 'code' => 'Código',
+ 'is_default' => 'Por defecto',
+ 'is_default_help' => 'El idioma por defecto con el que se representa el contenido antes de la traducción.',
+ 'is_enabled' => 'Habilitado',
+ 'is_enabled_help' => 'Los idiomas desactivados no estarán disponibles en el front-end',
+ 'not_available_help' => 'No hay otros idiomas establecidos.',
+ 'hint_locales' => 'Crear nuevos idiomas aquí para traducir el contenido de front-end. El idioma por defecto representa el contenido antes de que haya sido traducido.',
+ ],
+ 'messages' => [
+ 'title' => 'Traducir mensajes',
+ 'description' => 'Editar mensajes',
+ 'clear_cache_link' => 'Limpiar cache',
+ 'clear_cache_loading' => 'Borrado de la memoria caché de aplicaciones ...',
+ 'clear_cache_success' => 'Se ha borrado la memoria cache dela aplicación con éxito',
+ 'clear_cache_hint' => 'Es posible que tenga que hacer clic en Borrar caché strong> para ver los cambios en el front-end.',
+ 'scan_messages_link' => 'Escanear mensajes',
+ 'scan_messages_loading' => 'Escaneando nuevos mensajes...',
+ 'scan_messages_success' => 'Escaneado de los archivos del tema completado!',
+ 'scan_messages_hint' => 'Al hacer click en Escanear comprobaremos los mensajes de strong> los archivos de los temas activos para localizar nuevos mensajes a traducir.',
+ 'hint_translate' => 'Aquí usted puede traducir los mensajes utilizados en el front-end, los campos se guardará automáticamente.',
+ 'hide_translated' => 'Ocultar traducción',
+ ],
+];
\ No newline at end of file
diff --git a/plugins/rainlab/translate/lang/fa/lang.php b/plugins/rainlab/translate/lang/fa/lang.php
new file mode 100644
index 00000000..ed608e0c
--- /dev/null
+++ b/plugins/rainlab/translate/lang/fa/lang.php
@@ -0,0 +1,46 @@
+ [
+ 'name' => 'مترجم',
+ 'description' => 'فعال سازی وب سایت چند زبانه',
+ 'tab' => 'ترجمه',
+ 'manage_locales' => 'مدیریت مناطق',
+ 'manage_messages' => 'مدیریت پیغام ها'
+ ],
+ 'locale_picker' => [
+ 'component_name' => 'انتخابگر منطقه',
+ 'component_description' => 'نمایش انتخابگر کشویی جهت انتخاب زبان.',
+ ],
+ 'locale' => [
+ 'title' => 'مدیریت زبان ها',
+ 'update_title' => 'به روز رسانی زبان',
+ 'create_title' => 'ایجاد زبان',
+ 'select_label' => 'انتخاب زبان',
+ 'default_suffix' => 'پیشفرض',
+ 'unset_default' => '":locale" در حال حاظر پیشفرض می باشد و نمیتوانید آن را خارج کنید.',
+ 'disabled_default' => '":locale" غیر فعال می باشد و نمیتوانید آن را پیشفرض قرار دهید.',
+ 'name' => 'نام',
+ 'code' => 'کد یکتا',
+ 'is_default' => 'پیشفرض',
+ 'is_default_help' => 'زبان پیشفرضی که داده ها قبل از ترجمه به آن زبان وارد می شوند',
+ 'is_enabled' => 'فعال',
+ 'is_enabled_help' => 'زبان های غیر فعال در دسترس نخواهند بود.',
+ 'not_available_help' => 'زبان دیگری جهت نصب وجود ندارد.',
+ 'hint_locales' => 'زبان جدیدی را جهت ترجمه محتوی ایجاد نمایید.',
+ ],
+ 'messages' => [
+ 'title' => 'ترجمه پیغام ها',
+ 'description' => 'به روز رسانی پیغام ها',
+ 'clear_cache_link' => 'پاکسازی حافظه کش',
+ 'clear_cache_loading' => 'در حال پاکسازی حافظه کش برنامه...',
+ 'clear_cache_success' => 'عملیات پاکسازی حافظه کش به اتمام رسید.',
+ 'clear_cache_hint' => 'جهت نمایش تغیرات در سایت نیاز است که شما بر رور پاکسازی حافظه کش کلیک نمایید.',
+ 'scan_messages_link' => 'جستجوی پیغام ها',
+ 'scan_messages_loading' => 'جستجوی پیغام های جدید...',
+ 'scan_messages_success' => 'جستجوی پیغام های جدید به اتمام رسید.',
+ 'scan_messages_hint' => 'جهت جستجو و نمایش پیغامهای جدیدی که باید ترجمه شوند بر روی جستجوی پیغام های جدید کلیک نمایید.',
+ 'hint_translate' => 'در این قسمت شما میتوانید پیغام ها را ترجمه نمایید. گزینه ها به صورت خودکار ذخیره میشوند.',
+ 'hide_translated' => 'مخفی سازی ترجمه شده ها',
+ ],
+];
diff --git a/plugins/rainlab/translate/lang/fr/lang.php b/plugins/rainlab/translate/lang/fr/lang.php
new file mode 100644
index 00000000..3d12f86e
--- /dev/null
+++ b/plugins/rainlab/translate/lang/fr/lang.php
@@ -0,0 +1,59 @@
+ [
+ 'name' => 'Traductions',
+ 'description' => 'Permet de créer des sites Internet multilingues',
+ 'tab' => 'Traduction',
+ 'manage_locales' => 'Manage locales',
+ 'manage_messages' => 'Manage messages'
+ ],
+ 'locale_picker' => [
+ 'component_name' => 'Sélection de la langue',
+ 'component_description' => 'Affiche un menu déroulant pour sélectionner la langue sur le site.',
+ ],
+ 'alternate_hreflang' => [
+ 'component_name' => 'Éléments hrefLang alternatifs',
+ 'component_description' => "Injecte les alternatives linguistiques pour la page en tant qu'éléments hreflang"
+ ],
+ 'locale' => [
+ 'title' => 'Gestion des langues',
+ 'update_title' => 'Mettre à jour la langue',
+ 'create_title' => 'Ajouter une langue',
+ 'select_label' => 'Sélectionner une langue',
+ 'default_suffix' => 'défaut',
+ 'unset_default' => '":locale" est déjà la langue par défaut et ne peut être désactivée',
+ 'delete_default' => '":locale" est la valeur par défaut et ne peut pas être supprimé.',
+ 'disabled_default' => '":locale" est désactivé et ne peut être utilisé comme paramètre par défaut.',
+ 'name' => 'Nom',
+ 'code' => 'Code',
+ 'is_default' => 'Défaut',
+ 'is_default_help' => 'La langue par défaut représente le contenu avant la traduction.',
+ 'is_enabled' => 'Activé',
+ 'is_enabled_help' => 'Les langues désactivées ne seront plus disponibles sur le site.',
+ 'not_available_help' => 'Aucune autre langue n\'est définie.',
+ 'hint_locales' => 'Vous pouvez ajouter de nouvelles langues et traduire les messages du site. La langue par défaut est celle utilisée pour les contenus avant toute traduction.',
+ 'reorder_title' => 'Réorganiser les langues',
+ 'sort_order' => 'Ordre de tri',
+ ],
+ 'messages' => [
+ 'title' => 'Traduction des Messages',
+ 'description' => 'Mettre à jour Messages',
+ 'clear_cache_link' => 'Supprimer le cache',
+ 'clear_cache_loading' => 'Suppression du cache de l\'application...',
+ 'clear_cache_success' => 'Le cache de l\'application a été supprimé !',
+ 'clear_cache_hint' => 'Vous devez cliquer sur Supprimer le cache pour voir les modifications sur le site.',
+ 'scan_messages_link' => 'Rechercher des messages à traduire',
+ 'scan_messages_begin_scan' => 'Commencer la recherche',
+ 'scan_messages_loading' => 'Recherche de nouveaux messages...',
+ 'scan_messages_success' => 'Recherche dans les fichiers du thème effectuée !',
+ 'scan_messages_hint' => 'Cliquez sur Rechercher des messages à traduire pour parcourir les fichiers du thème actif à la recherche de messages à traduire.',
+ 'scan_messages_process' => 'Ce processus tentera d\'analyser le thème actif pour les messages qui peuvent être traduits.',
+ 'scan_messages_process_limitations' => 'Certains messages peuvent ne pas être capturés et n\'apparaîtront qu\'après leur première utilisation.',
+ 'scan_messages_purge_label' => 'Purger tous les messages en premier',
+ 'scan_messages_purge_help' => 'Si cette case est cochée, tous les messages seront supprimés avant d\'effectuer l\'analyse.',
+ 'scan_messages_purge_confirm' => 'Êtes-vous sûr de vouloir supprimer tous les messages? Cela ne peut pas être annulé!',
+ 'hint_translate' => 'Vous pouvez traduire les messages affichés sur le site, les champs s\'enregistrent automatiquement.',
+ 'hide_translated' => 'Masquer les traductions',
+ 'export_messages_link' => 'Exporter les messages',
+ 'import_messages_link' => 'Importer les messages',
+ ],
+];
diff --git a/plugins/rainlab/translate/lang/gr/lang.php b/plugins/rainlab/translate/lang/gr/lang.php
new file mode 100644
index 00000000..b49eac42
--- /dev/null
+++ b/plugins/rainlab/translate/lang/gr/lang.php
@@ -0,0 +1,64 @@
+ [
+ 'name' => 'Μετέφρασε',
+ 'description' => 'Ενεργοποίηση πολύγλωσσων ιστότοπων.',
+ 'tab' => 'Μετάφραση',
+ 'manage_locales' => 'Διαχείριση τοπικών ρυθμίσεων',
+ 'manage_messages' => 'Διαχείριση μηνυμάτων',
+ ],
+ 'locale_picker' => [
+ 'component_name' => 'Επιλογή τοπικών ρυθμίσεων',
+ 'component_description' => 'Εμφάνιση αναπτυσσόμενου μενού για επιλογή γλώσσας front-end.',
+ ],
+ 'alternate_hreflang' => [
+ 'component_name' => 'Εναλλακτικά στοιχεία hrefLang',
+ 'component_description' => 'Εισαγωγή εναλλακτικών γλωσσών για τη σελίδα ως στοιχεία hreflang'
+ ],
+ 'locale' => [
+ 'title' => 'Διαχείριση γλώσσας',
+ 'update_title' => 'Ενημέρωση γλώσσας',
+ 'create_title' => 'Δημιουργία γλώσσας',
+ 'select_label' => 'Επιλογή γλώσσας',
+ 'default_suffix' => 'Προεπιλογή',
+ 'unset_default' => '":locale" είναι ήδη προεπιλογή και δεν μπορεί να οριστεί ως προεπιλογή.',
+ 'delete_default' => '":locale" είναι ήδη προεπιλογή και δεν μπορεί να διαγραφεί.',
+ 'disabled_default' => '":locale" είναι απενεργοποιημένο και δεν μπορεί να οριστεί ως προεπιλογή.',
+ 'name' => 'Όνομα',
+ 'code' => 'Κωδικός',
+ 'is_default' => 'Προεπιλογή',
+ 'is_default_help' => 'Η προεπιλεγμένη γλώσσα αντιπροσωπεύει το περιεχόμενο πριν από τη μετάφραση.',
+ 'is_enabled' => 'Ενεργοποίηση',
+ 'is_enabled_help' => 'Οι απενεργοποιημένες γλώσσες δεν θα είναι διαθέσιμες στο front-end.',
+ 'not_available_help' => 'Δεν έχουν ρυθμιστεί άλλες γλώσσες.',
+ 'hint_locales' => 'Δημιουργήστε νέες γλώσσες εδώ για τη μετάφραση περιεχομένου front-end. Η προεπιλεγμένη γλώσσα αντιπροσωπεύει το περιεχόμενο προτού μεταφραστεί.',
+ 'reorder_title' => 'Αναδιάταξη γλωσσών',
+ 'sort_order' => 'Σειρά ταξινόμησης',
+ ],
+ 'messages' => [
+ 'title' => 'Μετάφραση μηνυμάτων',
+ 'description' => 'Ενημέρωση μηνυμάτων',
+ 'clear_cache_link' => 'Εκκαθάριση προσωρινής μνήμης',
+ 'clear_cache_loading' => 'Εκκαθάριση προσωρινής μνήμης εφαρμογής ...',
+ 'clear_cache_success' => 'Επιτυχής εκκαθάριση της προσωρινής μνήμης της εφαρμογής!',
+ 'clear_cache_hint' => 'Ίσως χρειαστεί να κάνετε κλικ στην επιλογή Clear cashe για να δείτε τις αλλαγές στο front-end.',
+ 'scan_messages_link' => 'Σάρωση για μηνύματα',
+ 'scan_messages_begin_scan' => 'Έναρξη σάρωσης',
+ 'scan_messages_loading' => 'Σάρωση για νέα μηνύματα ...',
+ 'scan_messages_success' => 'Επιτυχής σάρωση θεματικών πρότυπων αρχείων!',
+ 'scan_messages_hint' => 'Κάνοντας κλικ στην επιλογή Scan για messages θα ελεγθούν τα ενεργά αρχεία θεμάτων για τυχόν νέα μηνύματα που χρειάζονται μετάφραση.',
+ 'scan_messages_process' => 'Αυτή η διαδικασία θα προσπαθήσει να σαρώσει το ενεργό θέμα για μηνύματα που μπορούν να μεταφραστούν.',
+ 'scan_messages_process_limitations' => 'Ορισμένα μηνύματα ενδέχεται να μην καταγράφονται και θα εμφανίζονται μόνο μετά την πρώτη φορά που χρησιμοποιούνται.',
+ 'scan_messages_purge_label' => 'Εκκαθάριση πρώτα όλων των μηνυμάτων',
+ 'scan_messages_purge_help' => 'Επιλέγοντας εδώ θα διαγραφούν όλα τα μηνύματα, συμπεριλαμβανομένων και των μεταφράσεών τους, πριν από τη σάρωση.',
+ 'scan_messages_purge_confirm' => 'Είστε βέβαιοι ότι θέλετε να διαγράψετε όλα τα μηνύματα; Αυτό δεν μπορεί να αναιρεθεί!',
+ 'scan_messages_purge_deleted_label' => 'Εκκαθάριση απολεσθέντων μηνυμάτων μετά τη σάρωση',
+ 'scan_messages_purge_deleted_help' => 'Επιλέγοντας εδώ, αφού ολοκληρωθεί η σάρωση, τυχόν μηνύματα που δεν βρήκε ο σαρωτής, συμπεριλαμβανομένων και των μεταφράσεών τους, θα διαγραφούν. Αυτό δεν μπορεί να αναιρεθεί!',
+ 'hint_translate' => 'Εδώ μπορείτε να μεταφράσετε μηνύματα που χρησιμοποιούνται στο front-end, τα πεδία θα αποθηκεύονται αυτόματα.',
+ 'hide_translated' => 'Απόκρυψη μετάφρασης',
+ 'export_messages_link' => 'Εξαγωγή μηνυμάτων',
+ 'import_messages_link' => 'Εισαγωγή μηνυμάτων',
+ 'not_found' => 'Δεν βρέθηκε',
+ 'found_help' => 'Εάν τυχόν σφάλματα παρουσιάστηκαν κατά τη σάρωση.',
+ 'found_title' => 'Σάρωση σφαλμάτων',
+ ],
+];
\ No newline at end of file
diff --git a/plugins/rainlab/translate/lang/hu/lang.php b/plugins/rainlab/translate/lang/hu/lang.php
new file mode 100644
index 00000000..364613f9
--- /dev/null
+++ b/plugins/rainlab/translate/lang/hu/lang.php
@@ -0,0 +1,66 @@
+ [
+ 'name' => 'Fordítás',
+ 'description' => 'Többnyelvű weboldal létrehozását teszi lehetővé.',
+ 'tab' => 'Fordítás',
+ 'manage_locales' => 'Nyelvek kezelése',
+ 'manage_messages' => 'Szövegek fordítása'
+ ],
+ 'locale_picker' => [
+ 'component_name' => 'Nyelvi választó',
+ 'component_description' => 'Legördülő menüt jelenít meg a nyelv kiválasztásához.'
+ ],
+ 'alternate_hreflang' => [
+ 'component_name' => 'Nyelvi oldalak',
+ 'component_description' => 'A hreflang HTML meta sorok generálása a keresők számára.'
+ ],
+ 'locale' => [
+ 'title' => 'Nyelvek',
+ 'update_title' => 'Nyelv frissítése',
+ 'create_title' => 'Nyelv hozzáadása',
+ 'select_label' => 'Nyelv választása',
+ 'default_suffix' => 'alapértelmezett',
+ 'unset_default' => 'Már a(z) ":locale" nyelv az alapértelmezett, így nem használható alapértelmezettként.',
+ 'delete_default' => 'A(z) ":locale" nyelv az alapértelmezett, így nem törölhető.',
+ 'disabled_default' => 'A(z) ":locale" nyelv letiltott, így nem állítható be alapértelmezettként.',
+ 'name' => 'Név',
+ 'code' => 'Kód',
+ 'is_default' => 'Alapértelmezett',
+ 'is_default_help' => 'Az alapértelmezett nyelv a fordítás előtti tartalmat képviseli.',
+ 'is_enabled' => 'Engedélyezve',
+ 'is_enabled_help' => 'A letiltott nyelvek nem lesznek elérhetőek a látogatói oldalon.',
+ 'not_available_help' => 'Nincsenek más beállított nyelvek.',
+ 'hint_locales' => 'Itt hozhat létre új nyelveket a látogatói oldal tartalmának lefordításához. Az alapértelmezett nyelv képviseli a fordítás előtti tartalmat.',
+ 'reorder_title' => 'Rendezés',
+ 'sort_order' => 'Sorrend'
+ ],
+ 'messages' => [
+ 'title' => 'Szövegek',
+ 'description' => 'Nyelvi változatok menedzselése.',
+ 'clear_cache_link' => 'Gyorsítótár kiürítése',
+ 'clear_cache_loading' => 'A weboldal gyorsítótár kiürítése...',
+ 'clear_cache_success' => 'Sikerült a weboldal gyorsítótár kiürítése!',
+ 'clear_cache_hint' => 'Kattintson a Gyorsítótár kiürítése gombra, hogy biztosan láthatóvá váljanak a beírt módosítások a látogatói oldalon is.',
+ 'scan_messages_link' => 'Szövegek keresése',
+ 'scan_messages_begin_scan' => 'Keresés indítása',
+ 'scan_messages_loading' => 'Új szövegek keresése...',
+ 'scan_messages_success' => 'Sikerült a szövegek beolvasása!',
+ 'scan_messages_hint' => 'A Szövegek keresése gombra kattintva pedig beolvashatja a lefordítandó szövegeket.',
+ 'scan_messages_process' => 'A folyamat megkisérli beolvasni az aktív témában lévő lefordítandó szövegeket.',
+ 'scan_messages_process_limitations' => 'Néhány szöveg nem biztos, hogy azonnal meg fog jelenni a listában.',
+ 'scan_messages_purge_label' => 'Szövegek törlése a művelet előtt',
+ 'scan_messages_purge_help' => 'Amennyiben bejelöli, úgy minden szöveg törlésre kerül a beolvasást megelőzően.',
+ 'scan_messages_purge_confirm' => 'Biztos, hogy töröljük az összes szöveget?',
+ 'scan_messages_purge_deleted_label' => 'Törölje a hiányzó üzeneteket a vizsgálat után',
+ 'scan_messages_purge_deleted_help' => 'Ha be van jelölve, akkor a keresés befejezése után minden olyan üzenet törlődik, amelyet a keresés nem talált. A művelet nem visszavonható!',
+ 'hint_translate' => 'Itt fordíthatja le a látogatók által elérhető oldalon megjelenő szövegeket. A beírt változtatások automatikusan mentésre kerülnek.',
+ 'hide_translated' => 'Lefordítottak elrejtése',
+ 'export_messages_link' => 'Szövegek exportálása',
+ 'import_messages_link' => 'Szövegek importálása',
+ 'not_found' => 'Nem található',
+ 'found_help' => 'Történt-e hiba a keresés során.',
+ 'found_title' => 'Hibák',
+ ]
+];
diff --git a/plugins/rainlab/translate/lang/it/lang.php b/plugins/rainlab/translate/lang/it/lang.php
new file mode 100644
index 00000000..d1213b49
--- /dev/null
+++ b/plugins/rainlab/translate/lang/it/lang.php
@@ -0,0 +1,53 @@
+ [
+ 'name' => 'Traduci',
+ 'description' => 'Abilita siti multi-lingua.',
+ 'tab' => 'Traduzioni',
+ 'manage_locales' => 'Gestisci lingue',
+ 'manage_messages' => 'Gestisci messaggi',
+ ],
+ 'locale_picker' => [
+ 'component_name' => 'Selezione lingua',
+ 'component_description' => 'Mostra un elenco a discesa per selezionare una delle lingue del sito web.',
+ ],
+ 'locale' => [
+ 'title' => 'Gestisci lingue',
+ 'update_title' => 'Aggiorna lingua',
+ 'create_title' => 'Crea lingua',
+ 'select_label' => 'Scegli lingua',
+ 'default_suffix' => 'default',
+ 'unset_default' => '":locale" è già impostato come default e non si può modificare.',
+ 'delete_default' => '":locale" è la lingua di default e non può essere cancellata.',
+ 'disabled_default' => '":locale" è disabilitata e non può essere impostata come default.',
+ 'name' => 'Nome',
+ 'code' => 'Codice',
+ 'is_default' => 'Default',
+ 'is_default_help' => 'La lingua di default rappresenta il contenuto prima che sia tradotto.',
+ 'is_enabled' => 'Abilitata',
+ 'is_enabled_help' => 'Le lingue disabilitate non saranno visualizzate nel sito web.',
+ 'not_available_help' => 'Non ci sono altre lingue da impostare.',
+ 'hint_locales' => 'Crea qui le nuove lingue per tradurre i contenuti del sito web. La lingua di default rappresenta il contenuto prima che sia tradotto',
+ 'reorder_title' => 'Riordina lingue',
+ 'sort_order' => 'Criterio di ordinamento',
+ ],
+ 'messages' => [
+ 'title' => 'Traduci messaggi',
+ 'description' => 'Aggiorna messaggi',
+ 'clear_cache_link' => 'Pulisci cache',
+ 'clear_cache_loading' => 'Pulizia della cache dell\'applicazione in corso...',
+ 'clear_cache_success' => 'La cache è stata pulita con successo!',
+ 'clear_cache_hint' => 'Potrebbe essere necessario cliccare il bottobe Pulisci cache per vedere i cambiamenti nel sito web.',
+ 'scan_messages_link' => 'Scansiona per nuovi messaggi',
+ 'scan_messages_begin_scan' => 'Inizio scansione',
+ 'scan_messages_loading' => 'Scansione in corso per nuovi messaggi...',
+ 'scan_messages_success' => 'File del template scansionati con successo!',
+ 'scan_messages_hint' => 'Cliccando Scansiona per nuovi messaggi avvierai la ricerca di nuovi messaggi da tradurre nel template attivo.',
+ 'scan_messages_process' => 'Questo processo cercherà di scansionare il tema attivo per trovare messaggi che possono essere tradotti.',
+ 'scan_messages_process_limitations' => 'Qualche messaggio potrebbe non essere individuato e apparirà solo dopo la prima volta che verrà usato.',
+ 'scan_messages_purge_label' => 'Rimuovi prima tutti i messaggi',
+ 'scan_messages_purge_help' => 'Se selezionato, prima di eseguire la scansione verranno eliminati tutti i messaggi già presenti.',
+ 'scan_messages_purge_confirm' => 'Sei sicuro di voler cancellare tutti i messaggi? Questa operazione non può essere annullata!',
+ 'hint_translate' => 'Qui puoi tradurre i messaggi usati nel sito web, i campi verranno salvati automaticamente.',
+ 'hide_translated' => 'Nascondi i messaggi tradotti',
+ ],
+];
diff --git a/plugins/rainlab/translate/lang/nl/lang.php b/plugins/rainlab/translate/lang/nl/lang.php
new file mode 100644
index 00000000..26823d8f
--- /dev/null
+++ b/plugins/rainlab/translate/lang/nl/lang.php
@@ -0,0 +1,57 @@
+ [
+ 'name' => 'Vertaal',
+ 'description' => 'Stelt meerdere talen in voor een website.',
+ 'tab' => 'Vertalingen',
+ 'manage_locales' => 'Beheer talen',
+ 'manage_messages' => 'Beheer vertaalde berichten'
+ ],
+ 'locale_picker' => [
+ 'component_name' => 'Taalkeuze menu',
+ 'component_description' => 'Geeft een taal keuzemenu weer om de taal te wijzigen voor de website.',
+ ],
+ 'alternate_hreflang' => [
+ 'component_name' => 'Alternatieve hrefLang elementen',
+ 'component_description' => 'Toont hreflang elementen voor de alt. talen'
+ ],
+ 'locale' => [
+ 'title' => 'Beheer talen',
+ 'update_title' => 'Wijzig taal',
+ 'create_title' => 'Taal toevoegen',
+ 'select_label' => 'Selecteer taal',
+ 'default_suffix' => 'standaard',
+ 'unset_default' => '":locale" is de standaard taal en kan niet worden uitgeschakeld',
+ 'delete_default' => '":locale" is de standaard taal en kan niet worden verwijderd.',
+ 'disabled_default' => '":locale" is uitgeschakeld en kan niet als standaard taal worden ingesteld.',
+ 'name' => 'Naam',
+ 'code' => 'Code',
+ 'is_default' => 'Standaard',
+ 'is_default_help' => 'De standaard taal voor de inhoud.',
+ 'is_enabled' => 'Geactiveerd',
+ 'is_enabled_help' => 'Uitgeschakelde talen zijn niet beschikbaar op de website.',
+ 'not_available_help' => 'Er zijn geen andere talen beschikbaar.',
+ 'hint_locales' => 'Voeg hier nieuwe talen toe voor het vertalen van de website inhoud. De standaard taal geeft de inhoud weer voordat het is vertaald.',
+ 'reorder_title' => 'Talen rangschikken',
+ 'sort_order' => 'Volgorde',
+ ],
+ 'messages' => [
+ 'title' => 'Vertaal berichten',
+ 'description' => 'Wijzig berichten',
+ 'clear_cache_link' => 'Leeg cache',
+ 'clear_cache_loading' => 'Applicatie cache legen...',
+ 'clear_cache_success' => 'De applicatie cache is succesvol geleegd.',
+ 'clear_cache_hint' => 'Het kan zijn dat het nodig is om op cache legen " te klikken om wijzigingen op de website te zien.',
+ 'scan_messages_link' => 'Zoek naar nieuwe berichten',
+ 'scan_messages_begin_scan' => 'Begin Scan',
+ 'scan_messages_loading' => 'Zoeken naar nieuwe berichten...',
+ 'scan_messages_success' => 'De thema bestanden zijn succesvol gescand!',
+ 'scan_messages_hint' => 'Klikken op Zoeken naar nieuwe berichten controleert de bestanden van het huidige thema op nieuwe berichten om te vertalen.',
+ 'scan_messages_process' => 'Dit proces zal proberen het huidige thema te scannen voor berichten om te vertalen',
+ 'scan_messages_process_limitations' => 'Sommige berichten zullen niet worden herkend en zullen pas verschijnen nadat ze voor de eerste keer zijn gebruikt',
+ 'scan_messages_purge_label' => 'Verwijder eerst alle berichten',
+ 'scan_messages_purge_help' => 'Als dit is aangevinkt zullen alle berichten worden verwijderd voordat de scan wordt uitgevoerd.',
+ 'scan_messages_purge_confirm' => 'Weet je zeker dat je alle berichten wilt verwijderen? Dit kan niet ongedaan gemaakt worden',
+ 'hint_translate' => 'Hier kan je berichten vertalen die worden gebruikt op de website. De velden worden automatisch opgeslagen.',
+ 'hide_translated' => 'Verberg vertaalde berichten',
+ ],
+];
diff --git a/plugins/rainlab/translate/lang/pl/lang.php b/plugins/rainlab/translate/lang/pl/lang.php
new file mode 100644
index 00000000..1b6e4afc
--- /dev/null
+++ b/plugins/rainlab/translate/lang/pl/lang.php
@@ -0,0 +1,66 @@
+ [
+ 'name' => 'Tłumaczenia',
+ 'description' => 'Umożliwia tworzenie stron wielojęzycznych.',
+ 'tab' => 'Tłumaczenie',
+ 'manage_locales' => 'Zarządzaj językami',
+ 'manage_messages' => 'Zarządzaj treścią'
+ ],
+ 'locale_picker' => [
+ 'component_name' => 'Lista języków',
+ 'component_description' => 'Wyświetla menu wyboru języków strony.',
+ ],
+ 'alternate_hreflang' => [
+ 'component_name' => 'Alternatywne ustawienia hreflang',
+ 'component_description' => 'Ustawia alternatywne języki dla strony jako parametry hreflang'
+ ],
+ 'locale' => [
+ 'title' => 'Zarządzaj językami',
+ 'update_title' => 'Edytuj język',
+ 'create_title' => 'Stwórz język',
+ 'select_label' => 'Wybierz język',
+ 'default_suffix' => 'domyślny',
+ 'unset_default' => 'Język ":locale" jest już domyślny i nie można go zmienić.',
+ 'delete_default' => 'Język ":locale" jest już domyślny i nie może zostać usunięty.',
+ 'disabled_default' => 'Język ":locale" jest wyłączony i nie można go ustawić jako domyślny.',
+ 'name' => 'Nazwa',
+ 'code' => 'Kod',
+ 'is_default' => 'Domyślny',
+ 'is_default_help' => 'Domyślny język to język treści strony przed tłumaczeniem.',
+ 'is_enabled' => 'Włączony',
+ 'is_enabled_help' => 'Wyłączone języki nie będą dostępne na stronie.',
+ 'not_available_help' => 'Nie skonfigurowano innych języków.',
+ 'hint_locales' => 'Stwórz nowe języki, na które chcesz tłumaczyć treść strony. Domyślny język to język treści strony przed tłumaczeniem. ',
+ 'reorder_title' => 'Zmień kolejność języków',
+ 'sort_order' => 'Sortowanie',
+ ],
+ 'messages' => [
+ 'title' => 'Tłumacz Treść',
+ 'description' => 'Tłumaczenie treści strony',
+ 'clear_cache_link' => 'Wyczyść Cache',
+ 'clear_cache_loading' => 'Czyszczenie cache...',
+ 'clear_cache_success' => 'Pomyślnie wyczyszczono cache aplikacji!',
+ 'clear_cache_hint' => 'Jeśli nie widzisz zmian na stronie, kliknij przycisk Wyczyść cache .',
+ 'scan_messages_link' => 'Skanuj treść',
+ 'scan_messages_begin_scan' => 'Rozpocznij skanowanie',
+ 'scan_messages_loading' => 'Szukanie nowych pozycji...',
+ 'scan_messages_success' => 'Skanowanie plików motywu zakończyło się powodzeniem!',
+ 'scan_messages_hint' => 'Kliknięcie przycisku Skanuj treść rozpocznie skanowanie w poszukiwaniu nowych pozycji do przetłumaczenia.',
+ 'scan_messages_process' => 'Ten proces podejmie próbę przeskanowania aktywnego motywu w poszukiwaniu wiadomości, które można przetłumaczyć.',
+ 'scan_messages_process_limitations' => 'Niektóre wiadomości mogą nie zostać przechwycone i pojawią się dopiero po pierwszym użyciu.',
+ 'scan_messages_purge_label' => 'Najpierw usuń wszystkie wiadomości',
+ 'scan_messages_purge_help' => 'Zaznaczenie tej opcji spowoduje usunięcie wszystkich wiadomości, w tym ich tłumaczeń, przed wykonaniem skanowania.',
+ 'scan_messages_purge_confirm' => 'Czy jesteś pewny że chcesz usunąć wszystkie wiadomości? Po usunięciu nie będzie można ich przywrócić',
+ 'scan_messages_purge_deleted_label' => 'Usuń utracone wiadomości po zeskanowaniu',
+ 'scan_messages_purge_deleted_help' => 'Jeśli ta opcja jest zaznaczona, po zakończeniu skanowania wszystkie wiadomości, których skaner nie znalazł (w tym ich tłumaczenia) zostaną usunięte. Po zaznaczeniu tej opcji nie będzie możliwości przywrócenia zmian!',
+ 'hint_translate' => 'Możesz tu przetłumaczyć treść strony. Pola zapisują się automatycznie.',
+ 'hide_translated' => 'Ukryj przetłumaczone',
+ 'export_messages_link' => 'Wyeksportuj treść',
+ 'import_messages_link' => 'Zaimportuj treść',
+ 'not_found' => 'Nie znaleziono',
+ 'found_help' => 'Wystąpiły błędy podczas skanowania',
+ 'found_title' => 'Błąd skanowania',
+ ],
+];
diff --git a/plugins/rainlab/translate/lang/pt-br/lang.php b/plugins/rainlab/translate/lang/pt-br/lang.php
new file mode 100644
index 00000000..e1995f97
--- /dev/null
+++ b/plugins/rainlab/translate/lang/pt-br/lang.php
@@ -0,0 +1,53 @@
+ [
+ 'name' => 'Traduções',
+ 'description' => 'Permite sites com multi-idiomas.',
+ 'tab' => 'Tradução',
+ 'manage_locales' => 'Gerenciar locais',
+ 'manage_messages' => 'Gerenciar mensagens'
+ ],
+ 'locale_picker' => [
+ 'component_name' => 'Seleção de idiomas',
+ 'component_description' => 'Exibe um campo de seleção de idiomas.',
+ ],
+ 'locale' => [
+ 'title' => 'Gerenciar idiomas',
+ 'update_title' => 'Atualizar idioma',
+ 'create_title' => 'Criar idioma',
+ 'select_label' => 'Selecionar idioma',
+ 'default_suffix' => 'padrão',
+ 'unset_default' => '":locale" é o idioma padrão e não pode ser desativado.',
+ 'delete_default' => '":locale" é o padrão e não pode ser excluído.',
+ 'disabled_default' => '":locale" está desativado e não pode ser definido como padrão.',
+ 'name' => 'Nome',
+ 'code' => 'Código',
+ 'is_default' => 'Padrão',
+ 'is_default_help' => 'O idioma padrão apresenta o conteúdo antes das traduções.',
+ 'is_enabled' => 'Ativo',
+ 'is_enabled_help' => 'Idiomas desativados não estarão disponíveis na página.',
+ 'not_available_help' => 'Não há outros idiomas configurados.',
+ 'hint_locales' => 'Crie novos idiomas para traduzir o conteúdo da página. O idioma padrão apresenta o conteúdo antes das traduções.',
+ ],
+ 'messages' => [
+ 'title' => 'Traduzir mensagens',
+ 'description' => 'Atualizar mensagens',
+ 'clear_cache_link' => 'Limpar cache',
+ 'clear_cache_loading' => 'Limpando o cache da aplicação...',
+ 'clear_cache_success' => 'Cache da aplicação limpo com sucesso!',
+ 'clear_cache_hint' => 'Talvez você terá que clicar em Limpar cache para visualizar as modificações na página.',
+ 'scan_messages_link' => 'Buscar por mensagens',
+ 'scan_messages_begin_scan' => 'Inciar Busca',
+ 'scan_messages_loading' => 'Buscando por novas mensagens...',
+ 'scan_messages_success' => 'Busca por novas mensagens nos arquivos concluída com sucesso!',
+ 'scan_messages_hint' => 'Clicando em Buscar por mensagens o sistema buscará por qualquer mensagem da aplicação que possa ser traduzida.',
+ 'scan_messages_process' => 'Este processo tentará scanear o tema ativo para mensagens que podem ser traduzidas.',
+ 'scan_messages_process_limitations' => 'Algumas mensagens podem não ser capturadas e só aparecerão depois da primeira vez que forem usadas.',
+ 'scan_messages_purge_label' => 'Limpar primeiro todas as mensagens',
+ 'scan_messages_purge_help' => 'Se selecionado, isso excluirá todas as mensagens antes de executar a verificação.',
+ 'scan_messages_purge_confirm' => 'Tem certeza de que deseja excluir todas as mensagens? Isto não pode ser desfeito!',
+ 'hint_translate' => 'Aqui você pode raduzir as mensagens utilizadas na página, os campos são salvos automaticamente.',
+ 'hide_translated' => 'Ocultar traduzidas',
+ ],
+];
diff --git a/plugins/rainlab/translate/lang/ru/lang.php b/plugins/rainlab/translate/lang/ru/lang.php
new file mode 100644
index 00000000..a3b9177c
--- /dev/null
+++ b/plugins/rainlab/translate/lang/ru/lang.php
@@ -0,0 +1,61 @@
+ [
+ 'name' => 'Translate',
+ 'description' => 'Настройки мультиязычности сайта.',
+ 'tab' => 'Перевод',
+ 'manage_locales' => 'Управление языками',
+ 'manage_messages' => 'Управление сообщениями'
+ ],
+ 'locale_picker' => [
+ 'component_name' => 'Выбор языка',
+ 'component_description' => 'Просмотр списка языков интерфейса.',
+ ],
+ 'alternate_hreflang' => [
+ 'component_name' => 'Альтернативные элементы hrefLang',
+ 'component_description' => 'Внедряет языковые альтернативы для страницы в качестве элементов hreflang'
+ ],
+ 'locale' => [
+ 'title' => 'Управление языками',
+ 'update_title' => 'Обновить язык',
+ 'create_title' => 'Создать язык',
+ 'select_label' => 'Выбрать язык',
+ 'default_suffix' => 'По умолчанию',
+ 'unset_default' => '":locale" уже установлен как язык по умолчанию.',
+ 'delete_default' => '":locale" используется по умолчанию и не может быть удален.',
+ 'disabled_default' => '":locale" отключен и не может быть использован как язык по умолчанию.',
+ 'name' => 'Название',
+ 'code' => 'Код',
+ 'is_default' => 'По умолчанию',
+ 'is_default_help' => 'Использовать этот язык, как язык по умолчанию.',
+ 'is_enabled' => 'Включено',
+ 'is_enabled_help' => 'Сделать язык доступным в интерфейсе сайта.',
+ 'not_available_help' => 'Нет настроек других языков.',
+ 'hint_locales' => 'Создание новых переводов содержимого интерфейса сайта.',
+ 'reorder_title' => 'Изменить порядок языков',
+ 'sort_order' => 'Порядок сортировки',
+ ],
+ 'messages' => [
+ 'title' => 'Перевод сообщений',
+ 'description' => 'Перевод статических сообщений в шаблоне',
+ 'clear_cache_link' => 'Очистить кэш',
+ 'clear_cache_loading' => 'Очистка кэша приложения...',
+ 'clear_cache_success' => 'Очистка кэша завершена успешно!',
+ 'clear_cache_hint' => 'Используйте кнопку Очистить кэш , чтобы увидеть изменения в интерфейсе сайта.',
+ 'scan_messages_link' => 'Сканирование сообщений',
+ 'scan_messages_begin_scan' => 'Начать сканирование',
+ 'scan_messages_loading' => 'Сканирование наличия новых сообщений...',
+ 'scan_messages_success' => 'Сканирование файлов шаблона темы успешно завершено!',
+ 'scan_messages_hint' => 'Используйте кнопку Сканирование сообщений для поиска новых ключей перевода активной темы интерфейса сайта.',
+ 'scan_messages_process' => 'Этот процесс попытается отсканировать активную тему для сообщений, которые можно перевести.',
+ 'scan_messages_process_limitations' => 'Некоторые сообщения могут не быть отсканированы и появлятся только после первого использования.',
+ 'scan_messages_purge_label' => 'Сначала очистить все сообщения',
+ 'scan_messages_purge_help' => 'Если этот флажок установлен, это приведет к удалению всех сообщений перед выполнением сканирования.',
+ 'scan_messages_purge_confirm' => 'Вы действительно хотите удалить все сообщения? Операция не может быть отменена!',
+ 'hint_translate' => 'Здесь вы можете переводить сообщения, которые используются в интерфейсе сайта.',
+ 'hide_translated' => 'Скрыть перевод',
+ 'export_messages_link' => 'Экспорт сообщений',
+ 'import_messages_link' => 'Импорт сообщений',
+ ],
+];
diff --git a/plugins/rainlab/translate/lang/sk/lang.php b/plugins/rainlab/translate/lang/sk/lang.php
new file mode 100644
index 00000000..82b48278
--- /dev/null
+++ b/plugins/rainlab/translate/lang/sk/lang.php
@@ -0,0 +1,59 @@
+ [
+ 'name' => 'Preklady',
+ 'description' => 'Umožňuje viacjazyčné webové stránky.',
+ 'tab' => 'Preklad',
+ 'manage_locales' => 'Spravovať jazyky',
+ 'manage_messages' => 'Spravovať správy',
+ ],
+ 'locale_picker' => [
+ 'component_name' => 'Výber jazyka',
+ 'component_description' => 'Zobrazí rozbaľovaciu ponuku na výber jazyka front-endu.',
+ ],
+ 'alternate_hreflang' => [
+ 'component_name' => 'Alternatívne prvky hrefLang',
+ 'component_description' => 'Vloží jazykové alternatívy pre stránku ako prvky hreflang'
+ ],
+ 'locale' => [
+ 'title' => 'Spravovať jazyky',
+ 'update_title' => 'Aktualizovať jazyk',
+ 'create_title' => 'Vytvoriť jazyk',
+ 'select_label' => 'Zvoliť jazyk',
+ 'default_suffix' => 'predvolený',
+ 'unset_default' => '":locale" je už predvolený a nemožno ho nastaviť ako predvolený.',
+ 'delete_default' => '":locale" je predvolený a nemôže byť zmazaný.',
+ 'disabled_default' => '":locale" je neaktívny a nemôýe byť nastavený ako predvolený.',
+ 'name' => 'Meno',
+ 'code' => 'Kód',
+ 'is_default' => 'Predvolený',
+ 'is_default_help' => 'Predvolený jazyk predstavuje obsah pred prekladom.',
+ 'is_enabled' => 'Aktívny',
+ 'is_enabled_help' => 'Neaktívne jazyky nebudú dostupné na front-ende.',
+ 'not_available_help' => 'Nie sú nastavené žiadne ďalšie jazyky.',
+ 'hint_locales' => 'Vytvárajte tu nové jazyky pre preklad obsahu front-endu. Predvolený jazyk predstavuje obsah pred tým, než bol preložený.',
+ 'reorder_title' => 'Zmeniť poradie jazykov',
+ 'sort_order' => 'Smer zoradenia',
+ ],
+ 'messages' => [
+ 'title' => 'Preložiť správy',
+ 'description' => 'Aktualizovať správy',
+ 'clear_cache_link' => 'Vymazať vyrovnávaciu pamäť',
+ 'clear_cache_loading' => 'Čistenie vyrovnávacej pamäte aplikácie...',
+ 'clear_cache_success' => 'Vyrovnávacia pamäť aplikácie vyčistená!',
+ 'clear_cache_hint' => 'Možno budete musieť kliknúť Vymazať vyrovnávaciu pamäť aby sa zmeny prejavili na front-ende.',
+ 'scan_messages_link' => 'Vyhľadávanie správ',
+ 'scan_messages_begin_scan' => 'Začať vyhľadávanie',
+ 'scan_messages_loading' => 'Vyhľadávanie nových správ...',
+ 'scan_messages_success' => 'Vyhľadávanie nových správ úspešne ukončené!',
+ 'scan_messages_hint' => 'Kliknutie na Vyhľadávanie správ skontroluje súbory aktívnej témy a nájde nové správy na preklad.',
+ 'scan_messages_process' => 'Tento proces vyhľadí v aktívnej téme správy, ktoré môžu byť preložené.',
+ 'scan_messages_process_limitations' => 'Niektoré správy nemusia byť zachytené a objavia sa po ich prvom použití.',
+ 'scan_messages_purge_label' => 'Najprv vyčistiť všetky správy',
+ 'scan_messages_purge_help' => 'Ak zaškrtnuté zmaže všetky správy pred vykonaním vyhľadávania.',
+ 'scan_messages_purge_confirm' => 'Naozaj chcete odstrániť všetky správy? Toto sa nedá vrátiť späť!',
+ 'hint_translate' => 'Tu môžete preložiť správy používané na front-ende, polia sa ukladajú automaticky.',
+ 'hide_translated' => 'Skryť preložené',
+ 'export_messages_link' => 'Exportovať správy',
+ 'import_messages_link' => 'Importovať správy',
+ ],
+];
diff --git a/plugins/rainlab/translate/lang/sl/lang.php b/plugins/rainlab/translate/lang/sl/lang.php
new file mode 100644
index 00000000..db99d8e5
--- /dev/null
+++ b/plugins/rainlab/translate/lang/sl/lang.php
@@ -0,0 +1,59 @@
+ [
+ 'name' => 'Večjezičnost',
+ 'description' => 'Podpora za večjezične spletne strani.',
+ 'tab' => 'Večjezičnost',
+ 'manage_locales' => 'Upravljanje jezikov',
+ 'manage_messages' => 'Upravljanje besedil',
+ ],
+ 'locale_picker' => [
+ 'component_name' => 'Izbirnik jezikov',
+ 'component_description' => 'Prikaže spustni seznam jezikov na spletni strani.',
+ ],
+ 'alternate_hreflang' => [
+ 'component_name' => 'Jezikovne alternative',
+ 'component_description' => 'Vstavi jezikovne alternative za stran kot elemente "hreflang".',
+ ],
+ 'locale' => [
+ 'title' => 'Upravljanje jezikov',
+ 'update_title' => 'Posodobitev jezika',
+ 'create_title' => 'Ustvari nov jezik',
+ 'select_label' => 'Izberi jezik',
+ 'default_suffix' => 'privzeto',
+ 'unset_default' => '":locale" je že privzeti jezik in ga ni možno nastaviti za ne-privzetega.',
+ 'delete_default' => '":locale" je privzeti jezik in se ga ne da izbrisati.',
+ 'disabled_default' => '":locale" je onemogočen jezik in ga ni možno nastaviti za privzetega.',
+ 'name' => 'Ime',
+ 'code' => 'Koda',
+ 'is_default' => 'Privzeto',
+ 'is_default_help' => 'Privzeti jezik predstavlja vsebino pred prevodom.',
+ 'is_enabled' => 'Omogočeno',
+ 'is_enabled_help' => 'Onemogočeni jeziki na spletni strani ne bodo na voljo.',
+ 'not_available_help' => 'Ni nastavljenih drugih jezikov.',
+ 'hint_locales' => 'Tukaj lahko ustvarite nove jezike za prevajanje vsebine. Privzeti jezik predstavlja vsebino, preden je prevedena.',
+ 'reorder_title' => 'Spremeni vrstni red jezikov',
+ 'sort_order' => 'Vrstni red',
+ ],
+ 'messages' => [
+ 'title' => 'Prevajanje besedil',
+ 'description' => 'Urejanje prevodov besedil.',
+ 'clear_cache_link' => 'Počisti predpomnilnik',
+ 'clear_cache_loading' => 'Praznenje predpomnilnika aplikacije...',
+ 'clear_cache_success' => 'Predpomnilnik aplikacije je uspešno izpraznjen!',
+ 'clear_cache_hint' => 'Morda boste morali klikniti Počisti predpomnilnik za ogled sprememb na spletni strani.',
+ 'scan_messages_link' => 'Skeniraj besedila',
+ 'scan_messages_begin_scan' => 'Začni skeniranje',
+ 'scan_messages_loading' => 'Skeniram za nova besedila...',
+ 'scan_messages_success' => 'Datoteke aktivne teme so bile uspešno skenirane!',
+ 'scan_messages_hint' => 'Klik na Skeniraj besedila bo preveril datoteke aktivne teme, če vsebujejo morebitna besedila za prevode.',
+ 'scan_messages_process' => 'Ta postopek bo poskusil skenirati aktivno temo za besedila, ki jih je mogoče prevesti.',
+ 'scan_messages_process_limitations' => 'Nekatera besedila morda ne bodo prikazana in se bodo prikazala šele po prvi uporabi.',
+ 'scan_messages_purge_label' => 'Najprej izbriši vsa besedila',
+ 'scan_messages_purge_help' => 'Če je označeno, bodo pred skeniranjem izbrisana vsa besedila, vključno z njihovimi prevodi!',
+ 'scan_messages_purge_confirm' => 'Ali ste prepričani, da želite izbrisati vsa besedila? Tega ukaza ni mogoče razveljaviti!',
+ 'hint_translate' => 'Tukaj lahko prevedete besedila, uporabljena na spletni strani. Vrednosti v poljih se shranijo samodejno.',
+ 'hide_translated' => 'Skrij prevedena besedila',
+ 'export_messages_link' => 'Izvozi besedila',
+ 'import_messages_link' => 'Uvozi besedila',
+ ],
+];
diff --git a/plugins/rainlab/translate/lang/tr/lang.php b/plugins/rainlab/translate/lang/tr/lang.php
new file mode 100644
index 00000000..5a201475
--- /dev/null
+++ b/plugins/rainlab/translate/lang/tr/lang.php
@@ -0,0 +1,64 @@
+ [
+ 'name' => 'Çeviri',
+ 'description' => 'Çoklu dil destekli websiteleri oluşturmanızı sağlar.',
+ 'tab' => 'Çeviri',
+ 'manage_locales' => 'Dilleri yönet',
+ 'manage_messages' => 'Çevirileri yönet',
+ ],
+ 'locale_picker' => [
+ 'component_name' => 'Çoklu Dil Seçimi',
+ 'component_description' => 'Sitenizin dilini değiştirebileceğiniz diller listesini gösterir.',
+ ],
+ 'alternate_hreflang' => [
+ 'component_name' => 'Alternatif hrefLang elemanları',
+ 'component_description' => 'Sayfa için dil alternatiflerini hreflang elemanları olarak ekler.',
+ ],
+ 'locale' => [
+ 'title' => 'Dilleri yönet',
+ 'update_title' => 'Dili güncelle',
+ 'create_title' => 'Dil ekle',
+ 'select_label' => 'Dil seç',
+ 'default_suffix' => 'ön tanımlı',
+ 'unset_default' => '":locale" zaten ön tanımlı olarak seçili.',
+ 'delete_default' => '":locale" ön tanımlı olarak seçilmiş olduğu için silinemez.',
+ 'disabled_default' => '":locale" pasifleştirilmiş olduğu için ön tanımlı yapılamaz.',
+ 'name' => 'Dil İsmi',
+ 'code' => 'Dil Kodu',
+ 'is_default' => 'Ön tanımlı',
+ 'is_default_help' => 'Ön tanımlı seçilen dil, sitenin orjinal içeriğini belirtmektedir.',
+ 'is_enabled' => 'Aktif',
+ 'is_enabled_help' => 'Pasifleştirilen diller site ön yüzünde görüntülenmez.',
+ 'not_available_help' => 'Başka dil ayarı yok.',
+ 'hint_locales' => 'Ön yüz çevirilerini yapmak için buradan dil ekleyebilirsiniz. Ön tanımlı seçilen dil, sitenin orjinal içeriğini belirtmektedir.',
+ 'reorder_title' => 'Dilleri sırala',
+ 'sort_order' => 'Sıralama',
+ ],
+ 'messages' => [
+ 'title' => 'Metinleri çevir',
+ 'description' => 'Metinler ve çevirileri',
+ 'clear_cache_link' => 'Önbelleği temizle',
+ 'clear_cache_loading' => 'Önbellek temizleniyor..',
+ 'clear_cache_success' => 'Önbellek temizlendi!',
+ 'clear_cache_hint' => 'Yaptığınız çeviriler site ön yüzünde görünmüyorsa Önbelleği temizle butonuna tıklayabilirsiniz.',
+ 'scan_messages_link' => 'Yeni metinleri tara',
+ 'scan_messages_begin_scan' => 'Taramaya başla',
+ 'scan_messages_loading' => 'Yeni metinler taranıyor...',
+ 'scan_messages_success' => 'Tema dosyaları tarandı!',
+ 'scan_messages_hint' => 'Yeni metinleri tara butonuna tıklayarak tema içerisine yeni eklediğiniz metinleri de çevirebilirsiniz.',
+ 'scan_messages_process' => 'Bu işlem tema dosyaları içerisindeki çevrilecek metinleri tarar.',
+ 'scan_messages_process_limitations' => 'Bazı metinler yakalanamayabilir veya ilk kez kullanımdan sonra görüntülenebilir.',
+ 'scan_messages_purge_label' => 'Önce tüm eski çevirileri sil',
+ 'scan_messages_purge_help' => 'Eğer seçerseniz şimdiye kadar yaptığınız tüm çeviriler silinecek, tekrar çevirmeniz gerekecektir.',
+ 'scan_messages_purge_confirm' => 'Tüm çevirileri silmek istediğinize emin misiniz? Bu işlem geri alınamaz!',
+ 'scan_messages_purge_deleted_label' => 'Tarama tamamlandıktan sonra eksik çevirileri temizle',
+ 'scan_messages_purge_deleted_help' => 'İşaretlerseniz, tarama tamamlandıktan sonra tarayıcının bulamadığı tüm metinler (diğer dil çevirileri de dahil olmak üzere) silinecektir. Bu işlem geri alınamaz!',
+ 'hint_translate' => 'Bu kısımda site ön yüzünde görüntülenecek çeviri metinlerini bulabilirsiniz, çeviri yaptıktan sonra bir işlem yapmanıza gerek yoktur, hepsi otomatik kaydedilecek.',
+ 'hide_translated' => 'Çevrilen metinleri gizle',
+ 'export_messages_link' => 'Metinleri Dışa Aktar',
+ 'import_messages_link' => 'Metinleri İçe Aktar',
+ 'not_found' => 'Bulunamadı',
+ 'found_help' => 'Tarama sırasında herhangi bir hata oluşup oluşmadığı.',
+ 'found_title' => 'Tarama hataları',
+ ],
+];
diff --git a/plugins/rainlab/translate/lang/zh-cn/lang.php b/plugins/rainlab/translate/lang/zh-cn/lang.php
new file mode 100644
index 00000000..9ed1aa06
--- /dev/null
+++ b/plugins/rainlab/translate/lang/zh-cn/lang.php
@@ -0,0 +1,64 @@
+ [
+ 'name' => '翻译',
+ 'description' => '启用多语言网站。',
+ 'tab' => '翻译',
+ 'manage_locales' => '管理语言环境',
+ 'manage_messages' => '管理消息',
+ ],
+ 'locale_picker' => [
+ 'component_name' => '语言环境选择器',
+ 'component_description' => '显示用于选择前端语言的下拉列表。',
+ ],
+ 'alternate_hreflang' => [
+ 'component_name' => '备用 hrefLang 元素',
+ 'component_description' => '将页面的语言替代项作为 hreflang 元素注入'
+ ],
+ 'locale' => [
+ 'title' => '管理语言',
+ 'update_title' => '更新语言',
+ 'create_title' => '创建语言',
+ 'select_label' => '选择语言',
+ 'default_suffix' => '默认',
+ 'unset_default' => '":locale" 已经是默认值,不能取消设置为默认值。',
+ 'delete_default' => '":locale" 是默认值,不能删除。',
+ 'disabled_default' => '":locale" 已禁用且不能设置为默认值。',
+ 'name' => '名称',
+ 'code' => '代码',
+ 'is_default' => '默认',
+ 'is_default_help' => '默认语言代表翻译前的内容。',
+ 'is_enabled' => '启用',
+ 'is_enabled_help' => '禁用的语言在前端将不可用。',
+ 'not_available_help' => '没有设置其他语言。',
+ 'hint_locales' => '在此处创建用于翻译前端内容的新语言。默认语言代表翻译之前的内容。',
+ 'reorder_title' => '重新排序语言',
+ 'sort_order' => '排序顺序',
+ ],
+ 'messages' => [
+ 'title' => '翻译消息',
+ 'description' => '更新消息',
+ 'clear_cache_link' => '清除缓存',
+ 'clear_cache_loading' => '清除应用程序缓存...',
+ 'clear_cache_success' => '清除应用缓存成功!',
+ 'clear_cache_hint' => '您可能需要点击清除缓存 才能查看前端的更改。',
+ 'scan_messages_link' => '扫描消息',
+ 'scan_messages_begin_scan' => '开始扫描',
+ 'scan_messages_loading' => '正在扫描新消息...',
+ 'scan_messages_success' => '已成功扫描主题模板文件!',
+ 'scan_messages_hint' => '点击扫描消息 将检查活动主题文件中是否有任何要翻译的新消息。',
+ 'scan_messages_process' => '此进程将尝试扫描活动主题以查找可翻译的消息。',
+ 'scan_messages_process_limitations' => '有些消息可能无法捕获,只有在第一次使用后才会出现。',
+ 'scan_messages_purge_label' => '先清除所有消息',
+ 'scan_messages_purge_help' => '如果选中,这将在执行扫描之前删除所有消息,包括它们的翻译。',
+ 'scan_messages_purge_confirm' => '您确定要删除所有消息吗?这不能被撤消!',
+ 'scan_messages_purge_deleted_label' => '扫描后清除丢失的消息',
+ 'scan_messages_purge_deleted_help' => '如果选中,在扫描完成后,扫描器未找到的任何消息,包括它们的翻译,都将被删除。这不能被撤消!',
+ 'hint_translate' => '这里可以翻译前端使用的消息,字段会自动保存。',
+ 'hide_translated' => '隐藏翻译',
+ 'export_messages_link' => '导出消息',
+ 'import_messages_link' => '导入消息',
+ 'not_found' => '未找到',
+ 'found_help' => '扫描过程中是否发生任何错误。',
+ 'found_title' => '扫描错误',
+ ],
+];
diff --git a/plugins/rainlab/translate/models/Attribute.php b/plugins/rainlab/translate/models/Attribute.php
new file mode 100644
index 00000000..a6e7a412
--- /dev/null
+++ b/plugins/rainlab/translate/models/Attribute.php
@@ -0,0 +1,18 @@
+ []
+ ];
+}
diff --git a/plugins/rainlab/translate/models/Locale.php b/plugins/rainlab/translate/models/Locale.php
new file mode 100644
index 00000000..938643c2
--- /dev/null
+++ b/plugins/rainlab/translate/models/Locale.php
@@ -0,0 +1,218 @@
+ 'required',
+ 'name' => 'required',
+ ];
+
+ public $timestamps = false;
+
+ /**
+ * @var array Object cache of self, by code.
+ */
+ protected static $cacheByCode = [];
+
+ /**
+ * @var array A cache of enabled locales.
+ */
+ protected static $cacheListEnabled;
+
+ /**
+ * @var array A cache of available locales.
+ */
+ protected static $cacheListAvailable;
+
+ /**
+ * @var self Default locale cache.
+ */
+ protected static $defaultLocale;
+
+ public function afterCreate()
+ {
+ if ($this->is_default) {
+ $this->makeDefault();
+ }
+ }
+
+ public function beforeDelete()
+ {
+ if ($this->is_default) {
+ throw new ApplicationException(Lang::get('rainlab.translate::lang.locale.delete_default', ['locale'=>$this->name]));
+ }
+ }
+
+ public function beforeUpdate()
+ {
+ if ($this->isDirty('is_default')) {
+ $this->makeDefault();
+
+ if (!$this->is_default) {
+ throw new ValidationException(['is_default' => Lang::get('rainlab.translate::lang.locale.unset_default', ['locale'=>$this->name])]);
+ }
+ }
+ }
+
+ /**
+ * Makes this model the default
+ * @return void
+ */
+ public function makeDefault()
+ {
+ if (!$this->is_enabled) {
+ throw new ValidationException(['is_enabled' => Lang::get('rainlab.translate::lang.locale.disabled_default', ['locale'=>$this->name])]);
+ }
+
+ $this->newQuery()->where('id', $this->id)->update(['is_default' => true]);
+ $this->newQuery()->where('id', '<>', $this->id)->update(['is_default' => false]);
+ }
+
+ /**
+ * Returns the default locale defined.
+ * @return self
+ */
+ public static function getDefault()
+ {
+ if (self::$defaultLocale !== null) {
+ return self::$defaultLocale;
+ }
+
+ if ($forceDefault = Config::get('rainlab.translate::forceDefaultLocale')) {
+ $locale = new self;
+ $locale->name = $locale->code = $forceDefault;
+ $locale->is_default = $locale->is_enabled = true;
+ return self::$defaultLocale = $locale;
+ }
+
+ return self::$defaultLocale = self::where('is_default', true)
+ ->remember(1440, 'rainlab.translate.defaultLocale')
+ ->first()
+ ;
+ }
+
+ /**
+ * Locate a locale table by its code, cached.
+ * @param string $code
+ * @return Model
+ */
+ public static function findByCode($code = null)
+ {
+ if (!$code) {
+ return null;
+ }
+
+ if (isset(self::$cacheByCode[$code])) {
+ return self::$cacheByCode[$code];
+ }
+
+ return self::$cacheByCode[$code] = self::whereCode($code)->first();
+ }
+
+ /**
+ * Scope for checking if model is enabled
+ * @param Builder $query
+ * @return Builder
+ */
+ public function scopeIsEnabled($query)
+ {
+ return $query->where('is_enabled', true);
+ }
+
+ /**
+ * Scope for ordering the locales
+ * @param Builder $query
+ * @return Builder
+ */
+ public function scopeOrder($query)
+ {
+ return $query
+ ->orderBy('sort_order', 'asc')
+ ;
+ }
+
+ /**
+ * Returns true if there are at least 2 locales available.
+ * @return boolean
+ */
+ public static function isAvailable()
+ {
+ return count(self::listAvailable()) > 1;
+ }
+
+ /**
+ * Lists available locales, used on the back-end.
+ * @return array
+ */
+ public static function listAvailable()
+ {
+ if (self::$cacheListAvailable) {
+ return self::$cacheListAvailable;
+ }
+
+ return self::$cacheListAvailable = self::order()->pluck('name', 'code')->all();
+ }
+
+ /**
+ * Lists the enabled locales, used on the front-end.
+ * @return array
+ */
+ public static function listEnabled()
+ {
+ if (self::$cacheListEnabled) {
+ return self::$cacheListEnabled;
+ }
+
+ $expiresAt = now()->addMinutes(1440);
+ $isEnabled = Cache::remember('rainlab.translate.locales', $expiresAt, function() {
+ return self::isEnabled()->order()->pluck('name', 'code')->all();
+ });
+
+ return self::$cacheListEnabled = $isEnabled;
+ }
+
+ /**
+ * Returns true if the supplied locale is valid.
+ * @return boolean
+ */
+ public static function isValid($locale)
+ {
+ $languages = array_keys(Locale::listEnabled());
+
+ return in_array($locale, $languages);
+ }
+
+ /**
+ * Clears all cache keys used by this model
+ * @return void
+ */
+ public static function clearCache()
+ {
+ Cache::forget('rainlab.translate.locales');
+ Cache::forget('rainlab.translate.defaultLocale');
+ self::$cacheListEnabled = null;
+ self::$cacheListAvailable = null;
+ self::$cacheByCode = [];
+ }
+}
diff --git a/plugins/rainlab/translate/models/MLFile.php b/plugins/rainlab/translate/models/MLFile.php
new file mode 100644
index 00000000..d3f04c3f
--- /dev/null
+++ b/plugins/rainlab/translate/models/MLFile.php
@@ -0,0 +1,24 @@
+getFormFields() as $id => $field) {
+ if (!empty($field['translatable'])) {
+ $this->translatable[] = $id;
+ }
+ }
+ }
+}
diff --git a/plugins/rainlab/translate/models/Message.php b/plugins/rainlab/translate/models/Message.php
new file mode 100644
index 00000000..e468585e
--- /dev/null
+++ b/plugins/rainlab/translate/models/Message.php
@@ -0,0 +1,311 @@
+forLocale(Lang::getLocale());
+ }
+
+ /**
+ * Gets a message for a given locale, or the default.
+ * @param string $locale
+ * @return string
+ */
+ public function forLocale($locale = null, $default = null)
+ {
+ if ($locale === null) {
+ $locale = self::DEFAULT_LOCALE;
+ }
+
+ if (!array_key_exists($locale, $this->message_data)) {
+ // search parent locale (e.g. en-US -> en) before returning default
+ list($locale) = explode('-', $locale);
+ }
+
+ if (array_key_exists($locale, $this->message_data)) {
+ return $this->message_data[$locale];
+ }
+
+ return $default;
+ }
+
+ /**
+ * Writes a translated message to a locale.
+ * @param string $locale
+ * @param string $message
+ * @return void
+ */
+ public function toLocale($locale = null, $message = null)
+ {
+ if ($locale === null) {
+ return;
+ }
+
+ $data = $this->message_data;
+ $data[$locale] = $message;
+
+ if (!$message) {
+ unset($data[$locale]);
+ }
+
+ $this->message_data = $data;
+ $this->save();
+ }
+
+ /**
+ * Creates or finds an untranslated message string.
+ * @param string $messageId
+ * @param string $locale
+ * @return string
+ */
+ public static function get($messageId, $locale = null)
+ {
+ $locale = $locale ?: self::$locale;
+ if (!$locale) {
+ return $messageId;
+ }
+
+ $messageCode = static::makeMessageCode($messageId);
+
+ /*
+ * Found in cache
+ */
+ if (array_key_exists($locale . $messageCode, self::$cache)) {
+ return self::$cache[$locale . $messageCode];
+ }
+
+ /*
+ * Uncached item
+ */
+ $item = static::firstOrNew([
+ 'code' => $messageCode
+ ]);
+
+ /*
+ * Create a default entry
+ */
+ if (!$item->exists) {
+ $data = [static::DEFAULT_LOCALE => $messageId];
+ $item->message_data = $item->message_data ?: $data;
+ $item->save();
+ }
+
+ /*
+ * Schedule new cache and go
+ */
+ $msg = $item->forLocale($locale, $messageId);
+ self::$cache[$locale . $messageCode] = $msg;
+ self::$hasNew = true;
+
+ return $msg;
+ }
+
+ /**
+ * Import an array of messages. Only known messages are imported.
+ * @param array $messages
+ * @param string $locale
+ * @return void
+ */
+ public static function importMessages($messages, $locale = null)
+ {
+ self::importMessageCodes(array_combine($messages, $messages), $locale);
+ }
+
+ /**
+ * Import an array of messages. Only known messages are imported.
+ * @param array $messages
+ * @param string $locale
+ * @return void
+ */
+ public static function importMessageCodes($messages, $locale = null)
+ {
+ if ($locale === null) {
+ $locale = static::DEFAULT_LOCALE;
+ }
+
+ $existingIds = [];
+
+ foreach ($messages as $code => $message) {
+ // Ignore empties
+ if (!strlen(trim($message))) {
+ continue;
+ }
+
+ $code = static::makeMessageCode($code);
+
+ $item = static::firstOrNew([
+ 'code' => $code
+ ]);
+
+ // Do not import non-default messages that do not exist
+ if (!$item->exists && $locale != static::DEFAULT_LOCALE) {
+ continue;
+ }
+
+ $messageData = $item->exists || $item->message_data ? $item->message_data : [];
+
+ // Do not overwrite existing translations.
+ if (isset($messageData[$locale])) {
+ $existingIds[] = $item->id;
+ continue;
+ }
+
+ $messageData[$locale] = $message;
+
+ $item->message_data = $messageData;
+ $item->found = true;
+
+ $item->save();
+ }
+
+ // Set all messages found by the scanner as found
+ self::whereIn('id', $existingIds)->update(['found' => true]);
+ }
+
+ /**
+ * Looks up and translates a message by its string.
+ * @param string $messageId
+ * @param array $params
+ * @param string $locale
+ * @return string
+ */
+ public static function trans($messageId, $params = [], $locale = null)
+ {
+ $msg = static::get($messageId, $locale);
+
+ $params = array_build($params, function($key, $value){
+ return [':'.$key, e($value)];
+ });
+
+ $msg = strtr($msg, $params);
+
+ return $msg;
+ }
+
+ /**
+ * Looks up and translates a message by its string WITHOUT escaping params.
+ * @param string $messageId
+ * @param array $params
+ * @param string $locale
+ * @return string
+ */
+ public static function transRaw($messageId, $params = [], $locale = null)
+ {
+ $msg = static::get($messageId, $locale);
+
+ $params = array_build($params, function($key, $value){
+ return [':'.$key, $value];
+ });
+
+ $msg = strtr($msg, $params);
+
+ return $msg;
+ }
+
+ /**
+ * Set the caching context, the page url.
+ * @param string $locale
+ * @param string $url
+ */
+ public static function setContext($locale, $url = null)
+ {
+ if (!strlen($url)) {
+ $url = '/';
+ }
+
+ self::$url = $url;
+ self::$locale = $locale;
+
+ if ($cached = Cache::get(static::makeCacheKey())) {
+ self::$cache = (array) $cached;
+ }
+ }
+
+ /**
+ * Save context messages to cache.
+ * @return void
+ */
+ public static function saveToCache()
+ {
+ if (!self::$hasNew || !self::$url || !self::$locale) {
+ return;
+ }
+
+ $expiresAt = now()->addMinutes(Config::get('rainlab.translate::cacheTimeout', 1440));
+ Cache::put(static::makeCacheKey(), self::$cache, $expiresAt);
+ }
+
+ /**
+ * Creates a cache key for storing context messages.
+ * @return string
+ */
+ protected static function makeCacheKey()
+ {
+ return 'translation.'.self::$locale.self::$url;
+ }
+
+ /**
+ * Creates a sterile key for a message.
+ * @param string $messageId
+ * @return string
+ */
+ protected static function makeMessageCode($messageId)
+ {
+ $separator = '.';
+
+ // Convert all dashes/underscores into separator
+ $messageId = preg_replace('!['.preg_quote('_').'|'.preg_quote('-').']+!u', $separator, $messageId);
+
+ // Remove all characters that are not the separator, letters, numbers, or whitespace.
+ $messageId = preg_replace('![^'.preg_quote($separator).'\pL\pN\s]+!u', '', mb_strtolower($messageId));
+
+ // Replace all separator characters and whitespace by a single separator
+ $messageId = preg_replace('!['.preg_quote($separator).'\s]+!u', $separator, $messageId);
+
+ return Str::limit(trim($messageId, $separator), 250);
+ }
+}
diff --git a/plugins/rainlab/translate/models/MessageExport.php b/plugins/rainlab/translate/models/MessageExport.php
new file mode 100644
index 00000000..415e88d2
--- /dev/null
+++ b/plugins/rainlab/translate/models/MessageExport.php
@@ -0,0 +1,52 @@
+map(function ($message) use ($columns) {
+ $data = $message->message_data;
+ // Add code to data to simplify algorithm
+ $data[self::CODE_COLUMN_NAME] = $message->code;
+
+ $result = [];
+ foreach ($columns as $column) {
+ $result[$column] = isset($data[$column]) ? $data[$column] : '';
+ }
+ return $result;
+ })->toArray();
+ }
+
+ /**
+ * getColumns
+ *
+ * code, default column + all existing locales
+ *
+ * @return array
+ */
+ public static function getColumns()
+ {
+ return array_merge([
+ self::CODE_COLUMN_NAME => self::CODE_COLUMN_NAME,
+ Message::DEFAULT_LOCALE => self::DEFAULT_COLUMN_NAME,
+ ], Locale::lists(self::CODE_COLUMN_NAME, self::CODE_COLUMN_NAME));
+ }
+}
diff --git a/plugins/rainlab/translate/models/MessageImport.php b/plugins/rainlab/translate/models/MessageImport.php
new file mode 100644
index 00000000..3657727b
--- /dev/null
+++ b/plugins/rainlab/translate/models/MessageImport.php
@@ -0,0 +1,70 @@
+ 'required'
+ ];
+
+ /**
+ * Import the message data from a csv with the following schema:
+ *
+ * code | en | de | fr
+ * -------------------------------
+ * title | Title | Titel | Titre
+ * name | Name | Name | Prénom
+ * ...
+ *
+ * The code column is required and must not be empty.
+ *
+ * Note: Messages with an existing code are not removed/touched if the import
+ * doesn't contain this code. As a result you can incrementally update the
+ * messages by just adding the new codes and messages to the csv.
+ *
+ * @param $results
+ * @param null $sessionKey
+ */
+ public function importData($results, $sessionKey = null)
+ {
+ $codeName = MessageExport::CODE_COLUMN_NAME;
+ $defaultName = Message::DEFAULT_LOCALE;
+
+ foreach ($results as $index => $result) {
+ try {
+ if (isset($result[$codeName]) && !empty($result[$codeName])) {
+ $code = $result[$codeName];
+
+ // Modify result to match the expected message_data schema
+ unset($result[$codeName]);
+
+ $message = Message::firstOrNew(['code' => $code]);
+
+ // Create empty array, if $message is new
+ $message->message_data = $message->message_data ?: [];
+
+ if (!isset($message->message_data[$defaultName])) {
+ $default = (isset($result[$defaultName]) && !empty($result[$defaultName])) ? $result[$defaultName] : $code;
+ $result[$defaultName] = $default;
+ }
+
+ $message->message_data = array_merge($message->message_data, $result);
+
+ if ($message->exists) {
+ $this->logUpdated();
+ } else {
+ $this->logCreated();
+ }
+
+ $message->save();
+ } else {
+ $this->logSkipped($index, 'No code provided');
+ }
+ } catch (\Exception $exception) {
+ $this->logError($index, $exception->getMessage());
+ }
+ }
+ }
+}
diff --git a/plugins/rainlab/translate/models/locale/columns.yaml b/plugins/rainlab/translate/models/locale/columns.yaml
new file mode 100644
index 00000000..602eae29
--- /dev/null
+++ b/plugins/rainlab/translate/models/locale/columns.yaml
@@ -0,0 +1,26 @@
+# ===================================
+# Column Definitions
+# ===================================
+
+columns:
+ name:
+ label: rainlab.translate::lang.locale.name
+ searchable: yes
+
+ code:
+ label: rainlab.translate::lang.locale.code
+ searchable: yes
+
+ is_default:
+ label: rainlab.translate::lang.locale.is_default
+ type: switch
+
+ is_enabled:
+ label: rainlab.translate::lang.locale.is_enabled
+ type: switch
+ invisible: true
+
+ sort_order:
+ label: rainlab.translate::lang.locale.sort_order
+ type: number
+ invisible: true
diff --git a/plugins/rainlab/translate/models/locale/fields.yaml b/plugins/rainlab/translate/models/locale/fields.yaml
new file mode 100644
index 00000000..dbda60d8
--- /dev/null
+++ b/plugins/rainlab/translate/models/locale/fields.yaml
@@ -0,0 +1,24 @@
+# ===================================
+# Field Definitions
+# ===================================
+
+fields:
+ name:
+ label: rainlab.translate::lang.locale.name
+ span: auto
+
+ code:
+ label: rainlab.translate::lang.locale.code
+ span: auto
+
+ is_enabled:
+ label: rainlab.translate::lang.locale.is_enabled
+ type: checkbox
+ comment: rainlab.translate::lang.locale.is_enabled_help
+ span: auto
+
+ is_default:
+ label: rainlab.translate::lang.locale.is_default
+ type: checkbox
+ comment: rainlab.translate::lang.locale.is_default_help
+ span: auto
diff --git a/plugins/rainlab/translate/phpunit.xml b/plugins/rainlab/translate/phpunit.xml
new file mode 100644
index 00000000..1fb393c9
--- /dev/null
+++ b/plugins/rainlab/translate/phpunit.xml
@@ -0,0 +1,23 @@
+
+
+
+
+ ./tests
+
+
+
+
+
+
+
+
diff --git a/plugins/rainlab/translate/routes.php b/plugins/rainlab/translate/routes.php
new file mode 100644
index 00000000..66f247bb
--- /dev/null
+++ b/plugins/rainlab/translate/routes.php
@@ -0,0 +1,42 @@
+handleLocaleRoute();
+ if (!$locale) {
+ return;
+ }
+
+ /*
+ * Register routes
+ */
+ Route::group(['prefix' => $locale, 'middleware' => 'web'], function () {
+ Route::any('{slug?}', 'Cms\Classes\CmsController@run')->where('slug', '(.*)?');
+ });
+
+ Route::any($locale, 'Cms\Classes\CmsController@run')->middleware('web');
+
+ /*
+ * Ensure Url::action() retains the localized URL
+ * by re-registering the route after the CMS.
+ */
+ Event::listen('cms.route', function () use ($locale) {
+ Route::group(['prefix' => $locale, 'middleware' => 'web'], function () {
+ Route::any('{slug?}', 'Cms\Classes\CmsController@run')->where('slug', '(.*)?');
+ });
+ });
+});
+
+/*
+ * Save any used messages to the contextual cache.
+ */
+App::after(function ($request) {
+ if (class_exists('RainLab\Translate\Models\Message')) {
+ Message::saveToCache();
+ }
+});
diff --git a/plugins/rainlab/translate/tests/fixtures/classes/Feature.php b/plugins/rainlab/translate/tests/fixtures/classes/Feature.php
new file mode 100644
index 00000000..eabe9872
--- /dev/null
+++ b/plugins/rainlab/translate/tests/fixtures/classes/Feature.php
@@ -0,0 +1,24 @@
+ true], 'states'];
+
+ /**
+ * @var string The database table used by the model.
+ */
+ public $table = 'translate_test_countries';
+
+ /**
+ * @var array Guarded fields
+ */
+ protected $guarded = [];
+
+ /**
+ * @var array Jsonable fields
+ */
+ protected $jsonable = ['states'];
+}
diff --git a/plugins/rainlab/translate/tests/fixtures/themes/test/.gitignore b/plugins/rainlab/translate/tests/fixtures/themes/test/.gitignore
new file mode 100644
index 00000000..d6b7ef32
--- /dev/null
+++ b/plugins/rainlab/translate/tests/fixtures/themes/test/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/plugins/rainlab/translate/tests/unit/behaviors/TranslatableCmsObjectTest.php b/plugins/rainlab/translate/tests/unit/behaviors/TranslatableCmsObjectTest.php
new file mode 100644
index 00000000..12286564
--- /dev/null
+++ b/plugins/rainlab/translate/tests/unit/behaviors/TranslatableCmsObjectTest.php
@@ -0,0 +1,110 @@
+themePath = __DIR__ . '/../../fixtures/themes/test';
+
+ $this->seedSampleSourceAndData();
+ }
+
+ public function tearDown(): void
+ {
+ $this->cleanUp();
+ }
+
+ protected function cleanUp()
+ {
+ @unlink($this->themePath.'/features/winning.htm');
+ @unlink($this->themePath.'/features-fr/winning.htm');
+ File::deleteDirectory($this->themePath.'/features');
+ File::deleteDirectory($this->themePath.'/features-fr');
+ }
+
+ protected function seedSampleSourceAndData()
+ {
+ $datasource = new FileDatasource($this->themePath, new Filesystem);
+ $resolver = new Resolver(['theme1' => $datasource]);
+ $resolver->setDefaultDatasource('theme1');
+ Model::setDatasourceResolver($resolver);
+
+ LocaleModel::unguard();
+
+ LocaleModel::firstOrCreate([
+ 'code' => 'fr',
+ 'name' => 'French',
+ 'is_enabled' => 1
+ ]);
+
+ LocaleModel::reguard();
+
+ $this->recycleSampleData();
+ }
+
+ protected function recycleSampleData()
+ {
+ $this->cleanUp();
+
+ FeatureModel::create([
+ 'fileName' => 'winning.htm',
+ 'settings' => ['title' => 'Hash tag winning'],
+ 'markup' => 'Awww yiss',
+ ]);
+ }
+
+ public function testGetTranslationValue()
+ {
+ $obj = FeatureModel::first();
+
+ $this->assertEquals('Awww yiss', $obj->markup);
+
+ $obj->translateContext('fr');
+
+ $this->assertEquals('Awww yiss', $obj->markup);
+ }
+
+ public function testGetTranslationValueNoFallback()
+ {
+ $obj = FeatureModel::first();
+
+ $this->assertEquals('Awww yiss', $obj->markup);
+
+ $obj->noFallbackLocale()->translateContext('fr');
+
+ $this->assertEquals(null, $obj->markup);
+ }
+
+ public function testSetTranslationValue()
+ {
+ $this->recycleSampleData();
+
+ $obj = FeatureModel::first();
+ $obj->markup = 'Aussie';
+ $obj->save();
+
+ $obj->translateContext('fr');
+ $obj->markup = 'Australie';
+ $obj->save();
+
+ $obj = FeatureModel::first();
+ $this->assertEquals('Aussie', $obj->markup);
+
+ $obj->translateContext('fr');
+ $this->assertEquals('Australie', $obj->markup);
+ }
+
+}
diff --git a/plugins/rainlab/translate/tests/unit/behaviors/TranslatableModelTest.php b/plugins/rainlab/translate/tests/unit/behaviors/TranslatableModelTest.php
new file mode 100644
index 00000000..f33509d8
--- /dev/null
+++ b/plugins/rainlab/translate/tests/unit/behaviors/TranslatableModelTest.php
@@ -0,0 +1,202 @@
+seedSampleTableAndData();
+ }
+
+ protected function seedSampleTableAndData()
+ {
+ if (Schema::hasTable('translate_test_countries')) {
+ return;
+ }
+
+ Model::unguard();
+
+ Schema::create('translate_test_countries', function($table)
+ {
+ $table->engine = 'InnoDB';
+ $table->increments('id');
+ $table->string('name')->nullable();
+ $table->string('code')->nullable();
+ $table->text('states')->nullable();
+ $table->timestamps();
+ });
+
+ LocaleModel::firstOrCreate([
+ 'code' => 'fr',
+ 'name' => 'French',
+ 'is_enabled' => 1
+ ]);
+
+ $this->recycleSampleData();
+
+ Model::reguard();
+ }
+
+ protected function recycleSampleData()
+ {
+ CountryModel::truncate();
+
+ CountryModel::create([
+ 'name' => 'Australia',
+ 'code' => 'AU',
+ 'states' => ['NSW', 'ACT', 'QLD'],
+ ]);
+ }
+
+ public function testGetTranslationValue()
+ {
+ $obj = CountryModel::first();
+
+ $this->assertEquals('Australia', $obj->name);
+ $this->assertEquals(['NSW', 'ACT', 'QLD'], $obj->states);
+
+ $obj->translateContext('fr');
+
+ $this->assertEquals('Australia', $obj->name);
+ }
+
+ public function testGetTranslationValueNoFallback()
+ {
+ $obj = CountryModel::first();
+
+ $this->assertEquals('Australia', $obj->name);
+
+ $obj->noFallbackLocale()->translateContext('fr');
+
+ $this->assertEquals(null, $obj->name);
+ }
+
+ public function testSetTranslationValue()
+ {
+ $this->recycleSampleData();
+
+ $obj = CountryModel::first();
+ $obj->name = 'Aussie';
+ $obj->states = ['VIC', 'SA', 'NT'];
+ $obj->save();
+
+ $obj->translateContext('fr');
+ $obj->name = 'Australie';
+ $obj->states = ['a', 'b', 'c'];
+ $obj->save();
+
+ $obj = CountryModel::first();
+ $this->assertEquals('Aussie', $obj->name);
+ $this->assertEquals(['VIC', 'SA', 'NT'], $obj->states);
+
+ $obj->translateContext('fr');
+ $this->assertEquals('Australie', $obj->name);
+ $this->assertEquals(['a', 'b', 'c'], $obj->states);
+ }
+
+ public function testGetTranslationValueEagerLoading()
+ {
+ $this->recycleSampleData();
+
+ $obj = CountryModel::first();
+ $obj->translateContext('fr');
+ $obj->name = 'Australie';
+ $obj->states = ['a', 'b', 'c'];
+ $obj->save();
+
+ $objList = CountryModel::with([
+ 'translations'
+ ])->get();
+
+ $obj = $objList[0];
+ $this->assertEquals('Australia', $obj->name);
+ $this->assertEquals(['NSW', 'ACT', 'QLD'], $obj->states);
+
+ $obj->translateContext('fr');
+ $this->assertEquals('Australie', $obj->name);
+ $this->assertEquals(['a', 'b', 'c'], $obj->states);
+ }
+
+ public function testTranslateWhere()
+ {
+ $this->recycleSampleData();
+
+ $obj = CountryModel::first();
+
+ $obj->translateContext('fr');
+ $obj->name = 'Australie';
+ $obj->save();
+
+ $this->assertEquals(0, CountryModel::transWhere('name', 'Australie')->count());
+
+ Translator::instance()->setLocale('fr');
+ $this->assertEquals(1, CountryModel::transWhere('name', 'Australie')->count());
+
+ Translator::instance()->setLocale('en');
+ }
+
+ public function testTranslateOrderBy()
+ {
+ $this->recycleSampleData();
+
+ $obj = CountryModel::first();
+
+ $obj->translateContext('fr');
+ $obj->name = 'Australie';
+ $obj->save();
+
+ $obj = CountryModel::create([
+ 'name' => 'Germany',
+ 'code' => 'DE'
+ ]);
+
+ $obj->translateContext('fr');
+ $obj->name = 'Allemagne';
+ $obj->save();
+
+ $res = CountryModel::transOrderBy('name')->get()->pluck('name');
+ $this->assertEquals(['Australia', 'Germany'], $res->toArray());
+
+ Translator::instance()->setLocale('fr');
+ $res = CountryModel::transOrderBy('name')->get()->pluck('name');
+ $this->assertEquals(['Allemagne', 'Australie'], $res->toArray());
+
+ Translator::instance()->setLocale('en');
+ }
+
+ public function testGetTranslationValueEagerLoadingWithMorphMap()
+ {
+ Relation::morphMap([
+ 'morph.key' => CountryModel::class,
+ ]);
+
+ $this->recycleSampleData();
+
+ $obj = CountryModel::first();
+ $obj->translateContext('fr');
+ $obj->name = 'Australie';
+ $obj->states = ['a', 'b', 'c'];
+ $obj->save();
+
+ $objList = CountryModel::with([
+ 'translations'
+ ])->get();
+
+ $obj = $objList[0];
+ $this->assertEquals('Australia', $obj->name);
+ $this->assertEquals(['NSW', 'ACT', 'QLD'], $obj->states);
+
+ $obj->translateContext('fr');
+ $this->assertEquals('Australie', $obj->name);
+ $this->assertEquals(['a', 'b', 'c'], $obj->states);
+ }
+}
diff --git a/plugins/rainlab/translate/tests/unit/behaviors/TranslatablePageTest.php b/plugins/rainlab/translate/tests/unit/behaviors/TranslatablePageTest.php
new file mode 100644
index 00000000..0bbfa448
--- /dev/null
+++ b/plugins/rainlab/translate/tests/unit/behaviors/TranslatablePageTest.php
@@ -0,0 +1,67 @@
+themePath = __DIR__ . '/../../fixtures/themes/test';
+
+ $datasource = new FileDatasource($this->themePath, new Filesystem);
+ $resolver = new Resolver(['theme1' => $datasource]);
+ $resolver->setDefaultDatasource('theme1');
+ Model::setDatasourceResolver($resolver);
+
+ TranslatablePage::extend(function($page) {
+ if (!$page->isClassExtendedWith('RainLab\Translate\Behaviors\TranslatablePage')) {
+ $page->addDynamicProperty('translatable', ['title']);
+ $page->extendClassWith('RainLab\Translate\Behaviors\TranslatablePage');
+ }
+ });
+
+ }
+
+ public function tearDown(): void
+ {
+ File::deleteDirectory($this->themePath.'/pages');
+ }
+
+ public function testUseFallback()
+ {
+ $page = TranslatablePage::create([
+ 'fileName' => 'translatable',
+ 'title' => 'english title',
+ 'url' => '/test',
+ ]);
+ $page->translateContext('fr');
+ $this->assertEquals('english title', $page->title);
+ $page->noFallbackLocale()->translateContext('fr');
+ $this->assertEquals(null, $page->title);
+ }
+
+ public function testAlternateLocale()
+ {
+ $page = TranslatablePage::create([
+ 'fileName' => 'translatable',
+ 'title' => 'english title',
+ 'url' => '/test',
+ ]);
+ $page->setAttributeTranslated('title', 'titre francais', 'fr');
+ $title_en = $page->title;
+ $this->assertEquals('english title', $title_en);
+ $page->translateContext('fr');
+ $title_fr = $page->title;
+ $this->assertEquals('titre francais', $title_fr);
+ }
+}
diff --git a/plugins/rainlab/translate/tests/unit/models/ExportMessageTest.php b/plugins/rainlab/translate/tests/unit/models/ExportMessageTest.php
new file mode 100644
index 00000000..76262c9b
--- /dev/null
+++ b/plugins/rainlab/translate/tests/unit/models/ExportMessageTest.php
@@ -0,0 +1,98 @@
+exportData([]);
+
+ $this->assertEquals($expected, $result);
+ }
+
+ public function testCanHandleNoColumn()
+ {
+ $exportModel = new MessageExport();
+ $this->createMessages();
+ $expected = [[], []];
+
+ $result = $exportModel->exportData([]);
+
+ $this->assertEquals($expected, $result);
+ }
+
+ public function testExportSomeColumns()
+ {
+ $exportMode = new MessageExport();
+ $this->createMessages();
+ $expected = [
+ ['code' => 'hello'],
+ ['code' => 'bye'],
+ ];
+
+ $result = $exportMode->exportData(['code']);
+
+ $this->assertEquals($expected, $result);
+ }
+
+ public function testExportAllColumns()
+ {
+ $exportMode = new MessageExport();
+ $this->createMessages();
+ $expected = [
+ ['code' => 'hello', 'de' => 'Hallo, Welt', 'en' => 'Hello, World'],
+ ['code' => 'bye', 'de' => 'Auf Wiedersehen', 'en' => 'Goodbye'],
+ ];
+
+ $result = $exportMode->exportData(['code', 'de', 'en']);
+
+ $this->assertEquals($expected, $result);
+ }
+
+ public function testCanHandleNonExistingColumns()
+ {
+ $exportMode = new MessageExport();
+ $this->createMessages();
+ $expected = [
+ ['dummy' => ''],
+ ['dummy' => ''],
+ ];
+
+ $result = $exportMode->exportData(['dummy']);
+
+ $this->assertEquals($expected, $result);
+ }
+
+ private function createMessages()
+ {
+ Message::create([
+ 'code' => 'hello', 'message_data' => ['de' => 'Hallo, Welt', 'en' => 'Hello, World']
+ ]);
+ Message::create([
+ 'code' => 'bye', 'message_data' => ['de' => 'Auf Wiedersehen', 'en' => 'Goodbye']
+ ]);
+ }
+
+ public function testGetColumns()
+ {
+ Locale::unguard();
+ Locale::create(['code' => 'de', 'name' => 'German', 'is_enabled' => true]);
+
+ $columns = MessageExport::getColumns();
+
+ $this->assertEquals([
+ MessageExport::CODE_COLUMN_NAME => MessageExport::CODE_COLUMN_NAME,
+ Message::DEFAULT_LOCALE => MessageExport::DEFAULT_COLUMN_NAME,
+ 'en' => 'en',
+ 'de' => 'de',
+ ], $columns);
+ }
+}
diff --git a/plugins/rainlab/translate/tests/unit/models/ImportMessageTest.php b/plugins/rainlab/translate/tests/unit/models/ImportMessageTest.php
new file mode 100644
index 00000000..8b9c02ab
--- /dev/null
+++ b/plugins/rainlab/translate/tests/unit/models/ImportMessageTest.php
@@ -0,0 +1,95 @@
+importData($data);
+
+ $stats = $messageImport->getResultStats();
+ $this->assertEquals(false, $stats->hasMessages);
+ }
+
+ public function testCreateMessage()
+ {
+ $messageImport = new MessageImport();
+ $data = [
+ ['code' => 'new', 'de' => 'Neu', 'en' => 'new']
+ ];
+
+ $messageImport->importData($data);
+
+ $stats = $messageImport->getResultStats();
+ $this->assertEquals(1, $stats->created);
+ $this->assertEquals(0, $stats->updated);
+ $this->assertEquals(0, $stats->skippedCount);
+ $this->assertEquals(false, $stats->hasMessages);
+ }
+
+ public function testUpdateMessage()
+ {
+ $messageImport = new MessageImport();
+ Message::create(['code' => 'update', 'message_data' => ['en' => 'update', 'de' => 'aktualisieren']]);
+ $data = [
+ ['code' => 'update', 'de' => 'Neu 2', 'en' => 'new 2']
+ ];
+ $expected = [
+ Message::DEFAULT_LOCALE => 'update', 'de' => 'Neu 2', 'en' => 'new 2'
+ ];
+
+ $messageImport->importData($data);
+
+ $stats = $messageImport->getResultStats();
+ $this->assertEquals(0, $stats->created);
+ $this->assertEquals(1, $stats->updated);
+ $this->assertEquals(0, $stats->skippedCount);
+ $this->assertEquals(false, $stats->hasMessages);
+ $updatedMessage = Message::whereCode('update')->first();
+ $this->assertEquals($expected, $updatedMessage->message_data);
+ }
+
+ public function testMissingCodeIsSkipped()
+ {
+ $messageImport = new MessageImport();
+ $data = [
+ ['de' => 'Neu 2', 'en' => 'new 2']
+ ];
+
+ $messageImport->importData($data);
+
+ $stats = $messageImport->getResultStats();
+ $this->assertEquals(0, $stats->created);
+ $this->assertEquals(0, $stats->updated);
+ $this->assertEquals(1, $stats->skippedCount);
+ $this->assertEquals(true, $stats->hasMessages);
+ $this->assertEquals(Message::count(), 0);
+ }
+
+ public function testDefaultLocaleIsImported()
+ {
+ $messageImport = new MessageImport();
+ $data = [
+ ['code' => 'test.me', 'x' => 'foo bar', 'de' => 'Neu 2', 'en' => 'new 2']
+ ];
+
+ $messageImport->importData($data);
+
+ $stats = $messageImport->getResultStats();
+ $this->assertEquals(1, $stats->created);
+ $this->assertEquals(0, $stats->updated);
+ $this->assertEquals(0, $stats->skippedCount);
+ $this->assertEquals(false, $stats->hasMessages);
+ $this->assertEquals(Message::count(), 1);
+
+ $message = Message::where('code', 'test.me')->first();
+
+ $this->assertEquals('foo bar', $message->message_data['x']);
+ }
+}
diff --git a/plugins/rainlab/translate/tests/unit/models/MessageTest.php b/plugins/rainlab/translate/tests/unit/models/MessageTest.php
new file mode 100644
index 00000000..32a33eb3
--- /dev/null
+++ b/plugins/rainlab/translate/tests/unit/models/MessageTest.php
@@ -0,0 +1,61 @@
+assertNotNull(Message::whereCode('hello.world')->first());
+ $this->assertNotNull(Message::whereCode('hello.piñata')->first());
+
+ Message::truncate();
+ }
+
+ public function testMakeMessageCode()
+ {
+ $this->assertEquals('hello.world', Message::makeMessageCode('hello world'));
+ $this->assertEquals('hello.world', Message::makeMessageCode(' hello world '));
+ $this->assertEquals('hello.world', Message::makeMessageCode('hello-world'));
+ $this->assertEquals('hello.world', Message::makeMessageCode('hello--world'));
+
+ // casing
+ $this->assertEquals('hello.world', Message::makeMessageCode('Hello World'));
+ $this->assertEquals('hello.world', Message::makeMessageCode('Hello World!'));
+
+ // underscores
+ $this->assertEquals('hello.world', Message::makeMessageCode('hello_world'));
+ $this->assertEquals('hello.world', Message::makeMessageCode('hello__world'));
+
+ // length limit
+ $veryLongString = str_repeat("10 charstr", 30);
+ $this->assertTrue(strlen($veryLongString) > 250);
+ $this->assertEquals(253, strlen(Message::makeMessageCode($veryLongString)));
+ $this->assertStringEndsWith('...', Message::makeMessageCode($veryLongString));
+
+ // unicode characters
+ // brrowered some test cases from Stringy, the library Laravel's
+ // `slug()` function depends on
+ // https://github.com/danielstjules/Stringy/blob/master/tests/CommonTest.php
+ $this->assertEquals('fòô.bàř', Message::makeMessageCode('fòô bàř'));
+ $this->assertEquals('ťéśţ', Message::makeMessageCode(' ŤÉŚŢ '));
+ $this->assertEquals('φ.ź.3', Message::makeMessageCode('φ = ź = 3'));
+ $this->assertEquals('перевірка', Message::makeMessageCode('перевірка'));
+ $this->assertEquals('лысая.гора', Message::makeMessageCode('лысая гора'));
+ $this->assertEquals('щука', Message::makeMessageCode('щука'));
+ $this->assertEquals('foo.漢字', Message::makeMessageCode('foo 漢字')); // Chinese
+ $this->assertEquals('xin.chào.thế.giới', Message::makeMessageCode('xin chào thế giới'));
+ $this->assertEquals('xin.chào.thế.giới', Message::makeMessageCode('XIN CHÀO THẾ GIỚI'));
+ $this->assertEquals('đấm.phát.chết.luôn', Message::makeMessageCode('đấm phát chết luôn'));
+ $this->assertEquals('foo', Message::makeMessageCode('foo ')); // no-break space (U+00A0)
+ $this->assertEquals('foo', Message::makeMessageCode('foo ')); // spaces U+2000 to U+200A
+ $this->assertEquals('foo', Message::makeMessageCode('foo ')); // narrow no-break space (U+202F)
+ $this->assertEquals('foo', Message::makeMessageCode('foo ')); // medium mathematical space (U+205F)
+ $this->assertEquals('foo', Message::makeMessageCode('foo ')); // ideographic space (U+3000)
+ }
+}
diff --git a/plugins/rainlab/translate/traits/MLControl.php b/plugins/rainlab/translate/traits/MLControl.php
new file mode 100644
index 00000000..cbef3974
--- /dev/null
+++ b/plugins/rainlab/translate/traits/MLControl.php
@@ -0,0 +1,279 @@
+defaultLocale = Locale::getDefault();
+ $this->isAvailable = Locale::isAvailable();
+ }
+
+ /**
+ * getParentViewPath returns the parent control's view path
+ *
+ * @return string
+ */
+ protected function getParentViewPath()
+ {
+ // return base_path().'/modules/backend/formwidgets/parentcontrol/partials';
+ }
+
+ /**
+ * getParentAssetPath returns the parent control's asset path
+ *
+ * @return string
+ */
+ protected function getParentAssetPath()
+ {
+ // return '/modules/backend/formwidgets/parentcontrol/assets';
+ }
+
+ /**
+ * actAsParent swaps the asset & view paths with the parent control's to
+ * act as the parent control
+ *
+ * @param boolean $switch Defaults to true, determines whether to act as the parent or revert to current
+ */
+ protected function actAsParent($switch = true)
+ {
+ if ($switch) {
+ $this->originalAssetPath = $this->assetPath;
+ $this->originalViewPath = $this->viewPath;
+ $this->assetPath = $this->getParentAssetPath();
+ $this->viewPath = $this->getParentViewPath();
+ }
+ else {
+ $this->assetPath = $this->originalAssetPath;
+ $this->viewPath = $this->originalViewPath;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function renderFallbackField()
+ {
+ return $this->makeMLPartial('fallback_field');
+ }
+
+ /**
+ * makeMLPartial is used by child classes to render in context of this view path.
+ * @param string $partial The view to load.
+ * @param array $params Parameter variables to pass to the view.
+ * @return string The view contents.
+ */
+ public function makeMLPartial($partial, $params = [])
+ {
+ $oldViewPath = $this->viewPath;
+ $this->viewPath = $this->guessViewPathFrom(__TRAIT__, '/partials');
+ $result = $this->makePartial($partial, $params);
+ $this->viewPath = $oldViewPath;
+
+ return $result;
+ }
+
+ /**
+ * prepareLocaleVars prepares the list data
+ */
+ public function prepareLocaleVars()
+ {
+ $this->vars['defaultLocale'] = $this->defaultLocale;
+ $this->vars['locales'] = Locale::listAvailable();
+ $this->vars['field'] = $this->makeRenderFormField();
+ }
+
+ /**
+ * loadLocaleAssets loads assets specific to ML Controls
+ */
+ public function loadLocaleAssets()
+ {
+ $this->addJs('/plugins/rainlab/translate/assets/js/multilingual.js', 'RainLab.Translate');
+ $this->addCss('/plugins/rainlab/translate/assets/css/multilingual.css', 'RainLab.Translate');
+
+ if (!class_exists('System')) {
+ $this->addCss('/plugins/rainlab/translate/assets/css/multilingual-v1.css', 'RainLab.Translate');
+ }
+ }
+
+ /**
+ * getLocaleValue returns a translated value for a given locale.
+ * @param string $locale
+ * @return string
+ */
+ public function getLocaleValue($locale)
+ {
+ $key = $this->valueFrom ?: $this->fieldName;
+
+ /*
+ * Get the translated values from the model
+ */
+ $studKey = Str::studly(implode(' ', HtmlHelper::nameToArray($key)));
+ $mutateMethod = 'get'.$studKey.'AttributeTranslated';
+
+ if ($this->objectMethodExists($this->model, $mutateMethod)) {
+ $value = $this->model->$mutateMethod($locale);
+ }
+ elseif ($this->objectMethodExists($this->model, 'getAttributeTranslated') && $this->defaultLocale->code != $locale) {
+ $value = $this->model->noFallbackLocale()->getAttributeTranslated($key, $locale);
+ }
+ else {
+ $value = $this->formField->value;
+ }
+
+ return $value;
+ }
+
+ /**
+ * makeRenderFormField if translation is unavailable, render the original field type (text).
+ */
+ protected function makeRenderFormField()
+ {
+ if ($this->isAvailable) {
+ return $this->formField;
+ }
+
+ $field = clone $this->formField;
+ $field->type = $this->getFallbackType();
+
+ return $field;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getLocaleSaveValue($value)
+ {
+ $localeData = $this->getLocaleSaveData();
+ $key = $this->valueFrom ?: $this->fieldName;
+
+ /*
+ * Set the translated values to the model
+ */
+ $studKey = Str::studly(implode(' ', HtmlHelper::nameToArray($key)));
+ $mutateMethod = 'set'.$studKey.'AttributeTranslated';
+
+ if ($this->objectMethodExists($this->model, $mutateMethod)) {
+ foreach ($localeData as $locale => $value) {
+ $this->model->$mutateMethod($value, $locale);
+ }
+ }
+ elseif ($this->objectMethodExists($this->model, 'setAttributeTranslated')) {
+ foreach ($localeData as $locale => $value) {
+ $this->model->setAttributeTranslated($key, $value, $locale);
+ }
+ }
+
+ return array_get($localeData, $this->defaultLocale->code, $value);
+ }
+
+ /**
+ * getLocaleSaveData returns an array of translated values for this field
+ * @return array
+ */
+ public function getLocaleSaveData()
+ {
+ $values = [];
+ $data = post('RLTranslate');
+
+ if (!is_array($data)) {
+ return $values;
+ }
+
+ $fieldName = implode('.', HtmlHelper::nameToArray($this->fieldName));
+ $isJson = $this->isLocaleFieldJsonable();
+
+ foreach ($data as $locale => $_data) {
+ $value = array_get($_data, $fieldName);
+ $values[$locale] = $isJson ? json_decode($value, true) : $value;
+ }
+
+ return $values;
+ }
+
+ /**
+ * getFallbackType returns the fallback field type.
+ * @return string
+ */
+ public function getFallbackType()
+ {
+ return defined('static::FALLBACK_TYPE') ? static::FALLBACK_TYPE : 'text';
+ }
+
+ /**
+ * isLocaleFieldJsonable returns true if widget is a repeater, or the field is specified
+ * as jsonable in the model.
+ * @return bool
+ */
+ public function isLocaleFieldJsonable()
+ {
+ if (
+ $this instanceof \Backend\FormWidgets\Repeater ||
+ $this instanceof \Backend\FormWidgets\NestedForm
+ ) {
+ return true;
+ }
+
+ if ($this instanceof \Media\FormWidgets\MediaFinder && $this->maxItems !== 1) {
+ return true;
+ }
+
+ if (
+ method_exists($this->model, 'isJsonable') &&
+ $this->model->isJsonable($this->fieldName)
+ ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * objectMethodExists is an internal helper for method existence checks.
+ *
+ * @param object $object
+ * @param string $method
+ * @return boolean
+ */
+ protected function objectMethodExists($object, $method)
+ {
+ if (method_exists($object, 'methodExists')) {
+ return $object->methodExists($method);
+ }
+
+ return method_exists($object, $method);
+ }
+}
diff --git a/plugins/rainlab/translate/traits/mlcontrol/partials/_fallback_field.htm b/plugins/rainlab/translate/traits/mlcontrol/partials/_fallback_field.htm
new file mode 100644
index 00000000..0ffd1abc
--- /dev/null
+++ b/plugins/rainlab/translate/traits/mlcontrol/partials/_fallback_field.htm
@@ -0,0 +1,3 @@
+
+ = $this->makePartial('~/modules/backend/widgets/form/partials/_field_'.$field->type.'.htm', ['field' => $field]) ?>
+
\ No newline at end of file
diff --git a/plugins/rainlab/translate/traits/mlcontrol/partials/_locale_selector.htm b/plugins/rainlab/translate/traits/mlcontrol/partials/_locale_selector.htm
new file mode 100644
index 00000000..fb2c0b6e
--- /dev/null
+++ b/plugins/rainlab/translate/traits/mlcontrol/partials/_locale_selector.htm
@@ -0,0 +1,18 @@
+
+
diff --git a/plugins/rainlab/translate/traits/mlcontrol/partials/_locale_values.htm b/plugins/rainlab/translate/traits/mlcontrol/partials/_locale_values.htm
new file mode 100644
index 00000000..ae24007a
--- /dev/null
+++ b/plugins/rainlab/translate/traits/mlcontrol/partials/_locale_values.htm
@@ -0,0 +1,15 @@
+
+ $name): ?>
+ getLocaleValue($code);
+ $value = $this->isLocaleFieldJsonable() ? json_encode($value) : $value;
+ if (is_array($value)) $value = array_first($value);
+ ?>
+ getAttributes() ?>
+ />
+
diff --git a/plugins/rainlab/translate/updates/builder_table_update_rainlab_translate_locales.php b/plugins/rainlab/translate/updates/builder_table_update_rainlab_translate_locales.php
new file mode 100644
index 00000000..0c1f3191
--- /dev/null
+++ b/plugins/rainlab/translate/updates/builder_table_update_rainlab_translate_locales.php
@@ -0,0 +1,44 @@
+integer('sort_order')->default(0);
+ });
+ }
+
+ $locales = Locale::all();
+ foreach($locales as $locale) {
+ $locale->sort_order = $locale->id;
+ $locale->save();
+ }
+ }
+
+ public function down()
+ {
+ if (!Schema::hasTable(self::TABLE_NAME)) {
+ return;
+ }
+
+ if (Schema::hasColumn(self::TABLE_NAME, 'sort_order')) {
+ Schema::table(self::TABLE_NAME, function($table)
+ {
+ $table->dropColumn(['sort_order']);
+ });
+ }
+ }
+}
diff --git a/plugins/rainlab/translate/updates/create_attributes_table.php b/plugins/rainlab/translate/updates/create_attributes_table.php
new file mode 100644
index 00000000..7dc5b96b
--- /dev/null
+++ b/plugins/rainlab/translate/updates/create_attributes_table.php
@@ -0,0 +1,27 @@
+engine = 'InnoDB';
+ $table->increments('id');
+ $table->string('locale')->index();
+ $table->string('model_id')->index()->nullable();
+ $table->string('model_type')->index()->nullable();
+ $table->mediumText('attribute_data')->nullable();
+ });
+ }
+
+ public function down()
+ {
+ Schema::dropIfExists('rainlab_translate_attributes');
+ }
+
+}
diff --git a/plugins/rainlab/translate/updates/create_indexes_table.php b/plugins/rainlab/translate/updates/create_indexes_table.php
new file mode 100644
index 00000000..2e90ecec
--- /dev/null
+++ b/plugins/rainlab/translate/updates/create_indexes_table.php
@@ -0,0 +1,28 @@
+engine = 'InnoDB';
+ $table->increments('id');
+ $table->string('locale')->index();
+ $table->string('model_id')->index()->nullable();
+ $table->string('model_type')->index()->nullable();
+ $table->string('item')->nullable()->index();
+ $table->mediumText('value')->nullable();
+ });
+ }
+
+ public function down()
+ {
+ Schema::dropIfExists('rainlab_translate_indexes');
+ }
+
+}
diff --git a/plugins/rainlab/translate/updates/create_locales_table.php b/plugins/rainlab/translate/updates/create_locales_table.php
new file mode 100644
index 00000000..4d24e9ee
--- /dev/null
+++ b/plugins/rainlab/translate/updates/create_locales_table.php
@@ -0,0 +1,27 @@
+engine = 'InnoDB';
+ $table->increments('id');
+ $table->string('code')->index();
+ $table->string('name')->index()->nullable();
+ $table->boolean('is_default')->default(0);
+ $table->boolean('is_enabled')->default(0);
+ });
+ }
+
+ public function down()
+ {
+ Schema::dropIfExists('rainlab_translate_locales');
+ }
+
+}
diff --git a/plugins/rainlab/translate/updates/create_messages_table.php b/plugins/rainlab/translate/updates/create_messages_table.php
new file mode 100644
index 00000000..25324058
--- /dev/null
+++ b/plugins/rainlab/translate/updates/create_messages_table.php
@@ -0,0 +1,25 @@
+engine = 'InnoDB';
+ $table->increments('id');
+ $table->string('code')->index()->nullable();
+ $table->mediumText('message_data')->nullable();
+ });
+ }
+
+ public function down()
+ {
+ Schema::dropIfExists('rainlab_translate_messages');
+ }
+
+}
diff --git a/plugins/rainlab/translate/updates/migrate_morphed_attributes.php b/plugins/rainlab/translate/updates/migrate_morphed_attributes.php
new file mode 100644
index 00000000..10b9b07f
--- /dev/null
+++ b/plugins/rainlab/translate/updates/migrate_morphed_attributes.php
@@ -0,0 +1,32 @@
+getTable();
+ foreach (Relation::$morphMap as $alias => $class) {
+ Db::table($table)->where('model_type', $class)->update(['model_type' => $alias]);
+ }
+ }
+
+ public function down()
+ {
+ $table = (new Attribute())->getTable();
+ foreach (Relation::$morphMap as $alias => $class) {
+ Db::table($table)->where('model_type', $alias)->update(['model_type' => $class]);
+ }
+ }
+}
diff --git a/plugins/rainlab/translate/updates/migrate_morphed_indexes.php b/plugins/rainlab/translate/updates/migrate_morphed_indexes.php
new file mode 100644
index 00000000..29300f3e
--- /dev/null
+++ b/plugins/rainlab/translate/updates/migrate_morphed_indexes.php
@@ -0,0 +1,32 @@
+ $class) {
+ Db::table($this->table)->where('model_type', $class)->update(['model_type' => $alias]);
+ }
+ }
+
+ public function down()
+ {
+ foreach (Relation::$morphMap as $alias => $class) {
+ Db::table($this->table)->where('model_type', $alias)->update(['model_type' => $class]);
+ }
+ }
+}
diff --git a/plugins/rainlab/translate/updates/seed_all_tables.php b/plugins/rainlab/translate/updates/seed_all_tables.php
new file mode 100644
index 00000000..31576810
--- /dev/null
+++ b/plugins/rainlab/translate/updates/seed_all_tables.php
@@ -0,0 +1,21 @@
+ 'en',
+ 'name' => 'English',
+ 'is_default' => true,
+ 'is_enabled' => true
+ ]);
+ }
+ }
+
+}
diff --git a/plugins/rainlab/translate/updates/update_messages_table.php b/plugins/rainlab/translate/updates/update_messages_table.php
new file mode 100644
index 00000000..5d9955bc
--- /dev/null
+++ b/plugins/rainlab/translate/updates/update_messages_table.php
@@ -0,0 +1,37 @@
+boolean('found')->default(1);
+ });
+ }
+ }
+
+ public function down()
+ {
+ if (!Schema::hasTable(self::TABLE_NAME)) {
+ return;
+ }
+
+ if (Schema::hasColumn(self::TABLE_NAME, 'found')) {
+ Schema::table(self::TABLE_NAME, function($table)
+ {
+ $table->dropColumn(['found']);
+ });
+ }
+ }
+}
diff --git a/plugins/rainlab/translate/updates/version.yaml b/plugins/rainlab/translate/updates/version.yaml
new file mode 100644
index 00000000..49d3b30a
--- /dev/null
+++ b/plugins/rainlab/translate/updates/version.yaml
@@ -0,0 +1,98 @@
+v1.0.1:
+ - First version of Translate
+ - create_messages_table.php
+ - create_attributes_table.php
+ - create_locales_table.php
+v1.0.2: Languages and Messages can now be deleted.
+v1.0.3: Minor updates for latest October release.
+v1.0.4: Locale cache will clear when updating a language.
+v1.0.5: Add Spanish language and fix plugin config.
+v1.0.6: Minor improvements to the code.
+v1.0.7: Fixes major bug where translations are skipped entirely!
+v1.0.8: Minor bug fixes.
+v1.0.9: Fixes an issue where newly created models lose their translated values.
+v1.0.10: Minor fix for latest build.
+v1.0.11: Fix multilingual rich editor when used in stretch mode.
+v1.1.0: Introduce compatibility with RainLab.Pages plugin.
+v1.1.1: Minor UI fix to the language picker.
+v1.1.2: Add support for translating Static Content files.
+v1.1.3: Improved support for the multilingual rich editor.
+v1.1.4: Adds new multilingual markdown editor.
+v1.1.5: Minor update to the multilingual control API.
+v1.1.6: Minor improvements in the message editor.
+v1.1.7: Fixes bug not showing content when first loading multilingual textarea controls.
+v1.2.0: CMS pages now support translating the URL.
+v1.2.1: Minor update in the rich editor and code editor language control position.
+v1.2.2: Static Pages now support translating the URL.
+v1.2.3: Fixes Rich Editor when inserting a page link.
+v1.2.4:
+ - Translatable attributes can now be declared as indexes.
+ - create_indexes_table.php
+v1.2.5: Adds new multilingual repeater form widget.
+v1.2.6: Fixes repeater usage with static pages plugin.
+v1.2.7: Fixes placeholder usage with static pages plugin.
+v1.2.8: Improvements to code for latest October build compatibility.
+v1.2.9: Fixes context for translated strings when used with Static Pages.
+v1.2.10: Minor UI fix to the multilingual repeater.
+v1.2.11: Fixes translation not working with partials loaded via AJAX.
+v1.2.12: Add support for translating the new grouped repeater feature.
+v1.3.0: Added search to the translate messages page.
+v1.3.1:
+ - Added reordering to languages
+ - builder_table_update_rainlab_translate_locales.php
+ - seed_all_tables.php
+v1.3.2: Improved compatibility with RainLab.Pages, added ability to scan Mail Messages for translatable variables.
+v1.3.3: Fix to the locale picker session handling in Build 420 onwards.
+v1.3.4: Add alternate hreflang elements and adds prefixDefaultLocale setting.
+v1.3.5: Fix MLRepeater bug when switching locales.
+v1.3.6: Fix Middleware to use the prefixDefaultLocale setting introduced in 1.3.4
+v1.3.7: Fix config reference in LocaleMiddleware
+v1.3.8: Keep query string when switching locales
+v1.4.0: Add importer and exporter for messages
+v1.4.1: Updated Hungarian translation. Added Arabic translation. Fixed issue where default texts are overwritten by import. Fixed issue where the language switcher for repeater fields would overlap with the first repeater row.
+v1.4.2: Add multilingual MediaFinder
+v1.4.3: "!!! Please update OctoberCMS to Build 444 before updating this plugin. Added ability to translate CMS Pages fields (e.g. title, description, meta-title, meta-description)"
+v1.4.4: Minor improvements to compatibility with Laravel framework.
+v1.4.5: Fixed issue when using the language switcher
+v1.5.0: Compatibility fix with Build 451
+v1.6.0: Make File Upload widget properties translatable. Merge Repeater core changes into MLRepeater widget. Add getter method to retrieve original translate data.
+v1.6.1: Add ability for models to provide translated computed data, add option to disable locale prefix routing
+v1.6.2: Implement localeUrl filter, add per-locale theme configuration support
+v1.6.3: Add eager loading for translations, restore support for accessors & mutators
+v1.6.4: Fixes PHP 7.4 compatibility
+v1.6.5: Fixes compatibility issue when other plugins use a custom model morph map
+v1.6.6:
+ - Introduce migration to patch existing translations using morph map
+ - migrate_morphed_attributes.php
+v1.6.7:
+ - Introduce migration to patch existing indexes using morph map
+ - migrate_morphed_indexes.php
+v1.6.8: Add support for transOrderBy; Add translation support for ThemeData; Update russian localization.
+v1.6.9: Clear Static Page menu cache after saving the model; CSS fix for Text/Textarea input fields language selector.
+v1.6.10:
+ - Add option to purge deleted messages when scanning messages, Add Scan error column on Messages page, Fix translations that were lost when clicking locale twice while holding ctrl key, Fix error with nested fields default locale value, Escape Message translate params value.
+ - update_messages_table.php
+v1.7.0: "!!! Breaking change for the Message::trans() method (params are now escaped), fix message translation documentation, fix string translation key for scan errors column header."
+v1.7.1: Fix YAML issue with previous tag/release.
+v1.7.2: Fix regex when "|_" filter is followed by another filter, Try locale without country before returning default translation, Allow exporting default locale, Fire 'rainlab.translate.themeScanner.afterScan' event in the theme scanner for extendability.
+v1.7.3: Make plugin ready for Laravel 6 update, Add support for translating RainLab.Pages MenuItem properties (requires RainLab.Pages v1.3.6), Restore multilingual button position for textarea, Fix translatableAttributes.
+v1.7.4: Faster version of transWhere, Mail templates/views can now be localized, Fix messages table layout on mobile, Fix scopeTransOrderBy duplicates, Polish localization updates, Turkish localization updates, Add Greek language localization.
+v1.8.0: Adds initial support for October v2.0
+v1.8.1: Minor bugfix
+v1.8.2: Fixes translated file models and theme data for v2.0. The parent model must implement translatable behavior for their related file models to be translated.
+v1.8.4: Fixes the multilingual mediafinder to work with the media module.
+v1.8.6: Fixes invisible checkboxes when scanning for messages.
+v1.8.7: Fixes Markdown editor translation.
+v1.8.8: Fixes Laravel compatibility in custom Repeater.
+v1.9.0: Restores ability to translate URLs with CMS Editor in October v2.0
+v1.9.1: Minor styling improvements
+v1.9.2: Fixes issue creating new content in CMS Editor
+v1.9.3: Improves support when using child themes
+v1.10.0: Adds new multilingual nested form widget. Adds withFallbackLocale method.
+v1.10.1: Improve support with October v2.0
+v1.10.2: Improve support with October v2.2
+v1.10.3: Multilingual control improvements
+v1.10.4: Improve media finder support with October v2.2
+v1.10.5: Fixes media finder when only 1 locale is available
+v1.11.0: Update to latest Media Finder changes in October v2.2
+v1.11.1: Improve support with October v3.0
diff --git a/plugins/site21/fields/Plugin.php b/plugins/site21/fields/Plugin.php
new file mode 100644
index 00000000..b1057be6
--- /dev/null
+++ b/plugins/site21/fields/Plugin.php
@@ -0,0 +1,169 @@
+ e(trans('site21.fields::lang.plugin.name')),
+ 'description' => e(trans('site21.fields::lang.plugin.description')),
+ 'author' => 'Site21',
+ 'icon' => 'icon-leaf',
+ 'homepage' => 'https://site21.ru'
+ ];
+ }
+
+ public function register()
+ {
+
+ }
+
+ public function boot()
+ {
+ Event::listen('backend.menu.extendItems', function($manager) {
+ $manager->addSideMenuItems('Lovata.Shopaholic', 'shopaholic-menu-main', [
+ 'fields' => [
+ 'code' => 'fields',
+ 'label' => e(trans('site21.fields::lang.menu.label')),
+ 'icon' => 'icon-tags',
+ 'url' => Backend::url('site21/fields/fields'),
+ 'order' => 550,
+ 'permissions' => [
+ 'site21.fields.add_field_permission',
+ ],
+ ],
+ ]);
+ });
+ if (Schema::hasTable('site21_shopaholic_fields')) {
+ $models = [
+ Product::class,
+ Category::class,
+ Offer::class,
+ Brand::class,
+ ];
+ foreach($models as $module) {
+ $model = new $module;
+ $model->extend(function ($model) {
+ $module = (new Field)->getModuleTable($model->table, 'key');
+ if(!$module) {
+ return;
+ }
+ $query = Field::where('module', $module)->where('active', 1)->select('slug','settings');
+ $fields = $query->get();
+ if($fields) {
+ $array = [];
+ foreach($fields as $field) {
+ $model->fillable[] = $field->slug;
+ }
+ }
+
+ // добавляем кэшируемые поля
+ $cached = $query->where('settings->cached', '1')->pluck('slug')->toArray();
+ if($cached) {
+ $model->addCachedField($cached);
+ }
+
+ // добавляем переводимые поля
+ $array = [];
+ foreach($fields as $field) {
+ if($field->settings['translatable'] == 1) {
+ $array[] = $field->slug;
+ }
+ }
+ if($array) {
+ $model->translatable = array_merge($model->translatable,$array);
+ }
+ });
+
+ Event::listen('backend.form.extendFieldsBefore', function($widget) {
+ if ($widget->isNested || $widget->alias != 'form') {
+ return;
+ }
+ $module = (new Field)->getModuleTable($widget->model->table, 'key');
+ if(!$module) {
+ return;
+ }
+ // Получаем все кастомные поля только для обходимого модуля
+ $fields = Field::where('module', $module)
+ ->where('active', 1)
+ ->select('name', 'slug', 'type', 'tab', 'span', 'size', 'comment')
+ ->get();
+
+ if($fields) {
+ foreach($fields as $field) {
+ $widget->tabs['fields'][$field->slug] = [
+ 'label' => $field->name,
+ 'type' => $field->type,
+ 'tab' => $field->tab,
+ 'span' => $field->span,
+ 'size' => $field->size,
+ 'comment' => $field->comment
+ ];
+ }
+ }
+ });
+
+ Event::listen('backend.list.extendColumns', function ($listWidget) {
+
+ $module = (new Field)->getModuleTable($listWidget->model->table, 'key');
+ if(!$module) {
+ return;
+ }
+
+ $fields = Field::where('module', $module)->where('active', 1)->where('settings->inlist', '1')->select('slug', 'name', 'type')->get();
+
+ if($fields) {
+ $columns = [];
+ foreach($fields as $field) {
+ $columns = [
+ $field->slug => [
+ 'label' => $field->name,
+ 'type' => $field->type,
+ ]
+ ];
+ }
+ }
+ $listWidget->addColumns($columns);
+ });
+ }
+ }
+
+ }
+
+ public function registerComponents()
+ {
+ return []; // Remove this line to activate
+
+ return [
+ 'Site21\Fields\Components\MyComponent' => 'myComponent',
+ ];
+ }
+
+ public function registerPermissions()
+ {
+ return [
+ 'site21.fields.add_field_permission' => [
+ 'tab' => e(trans('site21.fields::lang.plugin.name')),
+ 'label' => e(trans('site21.fields::lang.permissions.add'))
+ ],
+ ];
+ }
+
+
+}
diff --git a/plugins/site21/fields/README.md b/plugins/site21/fields/README.md
new file mode 100644
index 00000000..ff8a9548
--- /dev/null
+++ b/plugins/site21/fields/README.md
@@ -0,0 +1,28 @@
+# oc-shopaholic-fields
+Простой плагин добавления в админку плагина Shopaholic для OctoberCMS своих текстовых полей.
+
+Поддерживает RainLab.Translate
+
+Подробная информация о том как разработан плагин описана на странице "[Shopaholic добавить поля](https://site21.ru/blog/shopaholic-add-fields)"
+
+На данные момент можно добавить следующие типы полей:
+- text
+- textarea
+
+В следующие модули Shopaholic:
+- товары
+- торговые предложения
+- бренды
+- категории
+
+## Todo:
+Плагин будет поддеживаться и разваваться.
+В следующей версии добавлю возможность добавления:
+- выбора таба куда выводить поле
+- select
+- checkbox
+- radio и т.д.
+
+
+
+[Мой блог об OctoberCMS](https://site21.ru/blog/tag/octobercms)
diff --git a/plugins/site21/fields/composer.json b/plugins/site21/fields/composer.json
new file mode 100644
index 00000000..62c1f11b
--- /dev/null
+++ b/plugins/site21/fields/composer.json
@@ -0,0 +1,10 @@
+{
+ "name": "site21/fields-plugin",
+ "type": "october-plugin",
+ "description": "A simple plugin for adding custom fields to Shopaholic admin panel (category, brand, offer or product)",
+ "require": {
+ "lovata/shopaholic-plugin": "^1.30",
+ "lovata/toolbox-plugin": "^1.33",
+ "composer/installers": "~1.0"
+ }
+}
diff --git a/plugins/site21/fields/controllers/Fields.php b/plugins/site21/fields/controllers/Fields.php
new file mode 100644
index 00000000..254cc695
--- /dev/null
+++ b/plugins/site21/fields/controllers/Fields.php
@@ -0,0 +1,39 @@
+
+
+ = e(trans('site21.fields::lang.btn.new_field')) ?>
+
+
+
+ = e(trans('site21.fields::lang.btn.delete_selected')) ?>
+
+
diff --git a/plugins/site21/fields/controllers/fields/config_form.yaml b/plugins/site21/fields/controllers/fields/config_form.yaml
new file mode 100644
index 00000000..2dc78a5d
--- /dev/null
+++ b/plugins/site21/fields/controllers/fields/config_form.yaml
@@ -0,0 +1,31 @@
+# ===================================
+# Form Behavior Config
+# ===================================
+
+# Record name
+name: 'site21.fields::lang.fields.name'
+
+# Model Form Field configuration
+form: $/site21/fields/models/field/fields.yaml
+
+# Model Class name
+modelClass: Site21\Fields\Models\Field
+
+# Default redirect location
+defaultRedirect: site21/fields/fields
+
+# Create page
+create:
+ title: backend::lang.form.create_title
+ redirect: site21/fields/fields/update/:id
+ redirectClose: site21/fields/fields
+
+# Update page
+update:
+ title: backend::lang.form.update_title
+ redirect: site21/fields/fields
+ redirectClose: site21/fields/fields
+
+# Preview page
+preview:
+ title: backend::lang.form.preview_title
diff --git a/plugins/site21/fields/controllers/fields/config_list.yaml b/plugins/site21/fields/controllers/fields/config_list.yaml
new file mode 100644
index 00000000..a4021ea3
--- /dev/null
+++ b/plugins/site21/fields/controllers/fields/config_list.yaml
@@ -0,0 +1,47 @@
+# ===================================
+# List Behavior Config
+# ===================================
+
+# Model List Column configuration
+list: $/site21/fields/models/field/columns.yaml
+
+# Model Class name
+modelClass: Site21\Fields\Models\Field
+
+# List Title
+title: 'site21.fields::lang.fields.manage'
+
+# Link URL for each record
+recordUrl: site21/fields/fields/update/:id
+
+# Message to display if the list is empty
+noRecordsMessage: backend::lang.list.no_records
+
+# Records to display per page
+recordsPerPage: 20
+
+# Display page numbers with pagination, disable to improve performance
+showPageNumbers: true
+
+# Displays the list column set up button
+showSetup: true
+
+# Displays the sorting link on each column
+showSorting: true
+
+# Default sorting column
+# defaultSort:
+# column: created_at
+# direction: desc
+
+# Display checkboxes next to each record
+showCheckboxes: true
+
+# Toolbar widget configuration
+toolbar:
+ # Partial for toolbar buttons
+ buttons: list_toolbar
+
+ # Search widget configuration
+ search:
+ prompt: backend::lang.list.search_prompt
diff --git a/plugins/site21/fields/controllers/fields/create.htm b/plugins/site21/fields/controllers/fields/create.htm
new file mode 100644
index 00000000..e0608618
--- /dev/null
+++ b/plugins/site21/fields/controllers/fields/create.htm
@@ -0,0 +1,48 @@
+
+
+
+
+fatalError): ?>
+
+ = Form::open(['class' => 'layout']) ?>
+
+
+ = $this->formRender() ?>
+
+
+
+
+ = Form::close() ?>
+
+
+
+ = e($this->fatalError) ?>
+ Return to fields list
+
+
diff --git a/plugins/site21/fields/controllers/fields/index.htm b/plugins/site21/fields/controllers/fields/index.htm
new file mode 100644
index 00000000..766877d9
--- /dev/null
+++ b/plugins/site21/fields/controllers/fields/index.htm
@@ -0,0 +1,2 @@
+
+= $this->listRender() ?>
diff --git a/plugins/site21/fields/controllers/fields/preview.htm b/plugins/site21/fields/controllers/fields/preview.htm
new file mode 100644
index 00000000..701f259a
--- /dev/null
+++ b/plugins/site21/fields/controllers/fields/preview.htm
@@ -0,0 +1,19 @@
+
+
+ Fields
+ = e($this->pageTitle) ?>
+
+
+
+fatalError): ?>
+
+
+ = $this->formRenderPreview() ?>
+
+
+
+
+ = e($this->fatalError) ?>
+ Return to fields list
+
+
diff --git a/plugins/site21/fields/controllers/fields/update.htm b/plugins/site21/fields/controllers/fields/update.htm
new file mode 100644
index 00000000..a94ac02e
--- /dev/null
+++ b/plugins/site21/fields/controllers/fields/update.htm
@@ -0,0 +1,56 @@
+
+
+
+
+fatalError): ?>
+
+ = Form::open(['class' => 'layout']) ?>
+
+
+ = $this->formRender() ?>
+
+
+
+
+ = Form::close() ?>
+
+
+
+ = e($this->fatalError) ?>
+ Return to fields list
+
+
diff --git a/plugins/site21/fields/lang/en/lang.php b/plugins/site21/fields/lang/en/lang.php
new file mode 100644
index 00000000..9244ec02
--- /dev/null
+++ b/plugins/site21/fields/lang/en/lang.php
@@ -0,0 +1,81 @@
+ [
+ 'name' => 'Additional fields for Shopaholic',
+ 'description' => 'Free plugin extending the functionality of the Shopaholic component',
+ ],
+ 'menu' => [
+ 'label' => 'Fields',
+ ],
+ 'form' => [
+ 'list_title' => 'Additional fields',
+ 'create' => 'Create',
+ 'create_and_close' => 'Create and close',
+ 'saving' => 'The field is saved',
+ 'cancel' => 'Cancel',
+ 'delete_sure' => 'Are you sure you want to delete this field?',
+ 'deleting' => 'Removed',
+ 'save' => 'Save',
+ 'save_and_close' => 'Save and close',
+ ],
+ 'btn' => [
+ 'new_field' => 'New field',
+ 'delete_selected' => 'Delete selected',
+ 'delete_selected_sure' => 'Are you sure you want to delete these fields?',
+ 'saving' => 'Persists ...',
+ 'save' => 'Save',
+ 'save_and_close' => 'Save and Close',
+ ],
+ 'fields' => [
+ 'name' => 'field',
+ 'manage' => 'Manage fields',
+ 'tab' => 'Other',
+ ],
+ 'field' => [
+ 'name' => 'Field name',
+ 'slug' => 'Slug',
+ 'type' => 'Field type',
+ 'module' => 'Module',
+ 'options' => 'Options',
+ 'tab' => 'Tab',
+ 'active' => 'Active field',
+ 'comment' => 'Comment',
+ 'span' => 'Span',
+ 'size' => 'Size',
+ 'translatable' => 'Translatable',
+ 'cached' => 'Cached',
+ 'inlist' => 'Add column to List widget',
+ ],
+ 'type' => [
+ 'text' => 'Text',
+ 'textarea' => 'Textarea',
+ 'dropdown' => 'Dropdown',
+ 'checkbox' => 'Checkbox',
+ 'radio' => 'Radio',
+ 'number' => 'Integer',
+ 'email' => 'E-mail',
+ 'switch' => 'Switch',
+ ],
+ 'module' => [
+ 'product' => 'Product',
+ 'offer' => 'Offer',
+ 'category' => 'Category',
+ 'brand' => 'Brand',
+ ],
+ 'active' => [
+ 'on' => 'Yea',
+ 'off' => 'No',
+ 'comment' => 'If disabled, it is not displayed in forms',
+ ],
+ 'tab' => [
+ 'settings' => 'Main settings',
+ 'additional' => 'Additional',
+ ],
+ 'comments' => [
+ 'comment' => 'Description, displayed under the field',
+ 'size' => 'Sets the field size for fields where possible, for example, for textarea',
+ 'translatable' => 'For Translate plugin',
+ ],
+ 'permissions' => [
+ 'add' => 'Add additional fields to Shopaholic',
+ ],
+];
\ No newline at end of file
diff --git a/plugins/site21/fields/lang/ru/lang.php b/plugins/site21/fields/lang/ru/lang.php
new file mode 100644
index 00000000..e40dfa44
--- /dev/null
+++ b/plugins/site21/fields/lang/ru/lang.php
@@ -0,0 +1,81 @@
+ [
+ 'name' => 'Дополнительные поля для Shopaholic',
+ 'description' => 'Бесплатный плагин расширяющий функционал компонента Shopaholic',
+ ],
+ 'menu' => [
+ 'label' => 'Доп. поля',
+ ],
+ 'form' => [
+ 'list_title' => 'Дополнительные поля',
+ 'create' => 'Создать',
+ 'create_and_close' => 'Создать и закрыть',
+ 'saving' => 'Поле сохраняется',
+ 'cancel' => 'Отмена',
+ 'delete_sure' => 'Уверены что хотите удалить это поле?',
+ 'deleting' => 'Удаляется',
+ 'save' => 'Сохранить',
+ 'save_and_close' => 'Сохранить и закрыть',
+ ],
+ 'btn' => [
+ 'new_field' => 'Новое поле',
+ 'delete_selected' => 'Удалить выбранное',
+ 'delete_selected_sure' => 'Вы уверены что хотите удалить данные поля?',
+ 'saving' => 'Сохраняется...',
+ 'save' => 'Сохранить',
+ 'save_and_close' => 'Сохранить и Закрыть',
+ ],
+ 'fields' => [
+ 'name' => 'поля',
+ 'manage' => 'Управление доп. полями',
+ 'tab' => 'Разное',
+ ],
+ 'field' => [
+ 'name' => 'Название поля',
+ 'slug' => 'Слаг',
+ 'type' => 'Тип поля',
+ 'module' => 'Модуль',
+ 'options' => 'Опции',
+ 'tab' => 'Таб',
+ 'active' => 'Активное поле',
+ 'comment' => 'Комментарий',
+ 'span' => 'Выравнивание',
+ 'size' => 'Размер',
+ 'translatable' => 'Переводимый',
+ 'cached' => 'Кэшируемый',
+ 'inlist' => 'Добавить колонку в список',
+ ],
+ 'type' => [
+ 'text' => 'Текстовое поле',
+ 'textarea' => 'Текстовая область',
+ 'dropdown' => 'Выпадающий список',
+ 'checkbox' => 'Чекбокс',
+ 'radio' => 'Радио',
+ 'number' => 'Целое число',
+ 'email' => 'E-mail',
+ 'switch' => 'Переключатель',
+ ],
+ 'module' => [
+ 'product' => 'Товар',
+ 'offer' => 'Торговое предложение',
+ 'category' => 'Категория',
+ 'brand' => 'Брэнд',
+ ],
+ 'active' => [
+ 'on' => 'Да',
+ 'off' => 'Нет',
+ 'comment' => 'Если выключено, то не выводится в формах',
+ ],
+ 'tab' => [
+ 'settings' => 'Настройки',
+ 'additional' => 'Дополнительные',
+ ],
+ 'comments' => [
+ 'comment' => 'Описание, выводится под полем',
+ 'size' => 'Задает размер поля для полей, где это возможно, например, для textarea',
+ 'translatable' => 'Для плагина Translate',
+ ],
+ 'permissions' => [
+ 'add' => 'Добалять дополнительные поля в Shopaholic',
+ ],
+];
\ No newline at end of file
diff --git a/plugins/site21/fields/models/Field.php b/plugins/site21/fields/models/Field.php
new file mode 100644
index 00000000..08c06060
--- /dev/null
+++ b/plugins/site21/fields/models/Field.php
@@ -0,0 +1,195 @@
+ 'required',
+ 'slug' => 'required|unique:site21_shopaholic_fields',
+ 'type' => 'required',
+ 'module' => 'required',
+ 'tab' => 'required',
+ ];
+
+ /**
+ * @var array Attributes to be cast to native types
+ */
+ protected $casts = [];
+
+ /**
+ * @var array Attributes to be cast to JSON
+ */
+ protected $jsonable = ['settings'];
+
+ /**
+ * @var array Attributes to be appended to the API representation of the model (ex. toArray())
+ */
+ protected $appends = [];
+
+ /**
+ * @var array Attributes to be removed from the API representation of the model (ex. toArray())
+ */
+ protected $hidden = [];
+
+ /**
+ * @var array Attributes to be cast to Argon (Carbon) instances
+ */
+ protected $dates = [
+ 'created_at',
+ 'updated_at'
+ ];
+
+ /**
+ * @var array Relations
+ */
+ public $hasOne = [];
+ public $hasMany = [];
+ public $hasOneThrough = [];
+ public $hasManyThrough = [];
+ public $belongsTo = [];
+ public $belongsToMany = [];
+ public $morphTo = [];
+ public $morphOne = [];
+ public $morphMany = [];
+ public $attachOne = [];
+ public $attachMany = [];
+
+
+
+ function getModuleTable($module, $get = FALSE){
+ $moduleTable = [
+ 'product' => 'lovata_shopaholic_products',
+ 'offer' => 'lovata_shopaholic_offers',
+ 'category' => 'lovata_shopaholic_categories',
+ 'brand' => 'lovata_shopaholic_brands',
+ ];
+ if($get == 'key') {
+ $obTable = array_keys($moduleTable, $module);
+ } else {
+ $obTable = $moduleTable[$module];
+ }
+ return $obTable;
+ }
+
+ public function afterCreate()
+ {
+ $obTable = $this->getModuleTable($this->module);
+ if (!Schema::hasTable($obTable) || Schema::hasColumn($obTable, $this->slug)) {
+ return;
+ }
+
+ Schema::table($obTable, function ($table) {
+ $type = [
+ 'text' => 'string',
+ 'textarea' => 'text',
+ 'number' => 'integer',
+ 'email' => 'string',
+ 'switch' => 'string',
+ ];
+ $obType = $type[$this->type];
+ $table->$obType($this->slug)->nullable();
+ });
+ }
+
+
+ public function afterDelete()
+ {
+ Schema::table($this->getModuleTable($this->module), function ($table) {
+ $table->dropColumn($this->slug);
+ });
+ }
+
+ public function afterUpdate()
+ {
+ if ($this->slug != $this->original['slug']) {
+ Schema::table($this->getModuleTable($this->module), function ($table) {
+ $table->renameColumn($this->original['slug'], $this->slug);
+ });
+ }
+ }
+
+
+ public function getTypeOptions() {
+ return [
+ 'text' => 'site21.fields::lang.type.text',
+ 'textarea' => 'site21.fields::lang.type.textarea',
+ 'number' => 'site21.fields::lang.type.number',
+ 'email' => 'site21.fields::lang.type.email',
+ 'switch' => 'site21.fields::lang.type.switch',
+ ];
+ }
+
+ public function getModuleOptions() {
+ return [
+ 'product' => 'site21.fields::lang.module.product',
+ 'offer' => 'site21.fields::lang.module.offer',
+ 'category' => 'site21.fields::lang.module.category',
+ 'brand' => 'site21.fields::lang.module.brand'
+ ];
+ }
+
+ public function getTabOptions($value, $data)
+ {
+ $module = isset($data->module) ? $data->module : key($this->getModuleOptions());
+ $options = [
+ 0 => 'site21.fields::lang.fields.tab',
+ 'lovata.toolbox::lang.tab.settings' => 'lovata.toolbox::lang.tab.settings',
+ 'lovata.toolbox::lang.tab.description' => 'lovata.toolbox::lang.tab.description',
+ 'lovata.toolbox::lang.tab.images' => 'lovata.toolbox::lang.tab.images',
+ ];
+ if($module) {
+ if ($module == 'product') {
+ $options['lovata.shopaholic::lang.tab.offer'] = 'lovata.shopaholic::lang.tab.offer';
+ }
+ elseif ($module == 'offer') {
+ $options['lovata.shopaholic::lang.tab.price'] = 'lovata.shopaholic::lang.tab.price';
+ }
+ }
+ return $options;
+ }
+
+ public function getSpanOptions() {
+ return [
+ 'full' => 'full',
+ 'auto' => 'auto',
+ 'left' => 'left',
+ 'right' => 'right'
+ ];
+ }
+
+ public function getSizeOptions() {
+ return [
+ 'tiny' => 'tiny',
+ 'small' => 'small',
+ 'large' => 'large',
+ 'huge' => 'huge',
+ 'giant' => 'giant'
+ ];
+ }
+
+}
diff --git a/plugins/site21/fields/models/field/columns.yaml b/plugins/site21/fields/models/field/columns.yaml
new file mode 100644
index 00000000..11aad9e4
--- /dev/null
+++ b/plugins/site21/fields/models/field/columns.yaml
@@ -0,0 +1,30 @@
+# ===================================
+# List Column Definitions
+# ===================================
+
+columns:
+ id:
+ label: ID
+ searchable: true
+
+ name:
+ label: 'site21.fields::lang.field.name'
+ searchable: true
+
+ slug:
+ label: 'site21.fields::lang.field.slug'
+ searchable: true
+
+ type:
+ label: 'site21.fields::lang.field.type'
+ searchable: false
+
+ module:
+ label: 'site21.fields::lang.field.module'
+ searchable: false
+
+ active:
+ label: 'site21.fields::lang.field.active'
+ searchable: false
+
+
diff --git a/plugins/site21/fields/models/field/fields.yaml b/plugins/site21/fields/models/field/fields.yaml
new file mode 100644
index 00000000..b66b0ba7
--- /dev/null
+++ b/plugins/site21/fields/models/field/fields.yaml
@@ -0,0 +1,113 @@
+# ===================================
+# Form Field Definitions
+# ===================================
+
+fields:
+ id:
+ label: ID
+ disabled: true
+ hidden: true
+
+ active:
+ label: 'site21.fields::lang.field.active'
+ span: left
+ type: switch
+ default: 1
+ comment: 'site21.fields::lang.active.comment'
+ on: 'site21.fields::lang.active.on'
+ off: 'site21.fields::lang.active.off'
+
+ settings[translatable]:
+ label: 'site21.fields::lang.field.translatable'
+ span: right
+ type: switch
+ default: 0
+ tab: 'site21.fields::lang.tab.additional'
+ comment: 'site21.fields::lang.comments.translatable'
+ on: 'site21.fields::lang.active.on'
+ off: 'site21.fields::lang.active.off'
+
+ name:
+ label: 'site21.fields::lang.field.name'
+ type: text
+ span: left
+ required: 1
+
+ slug:
+ label: 'site21.fields::lang.field.slug'
+ span: right
+ required: 1
+ preset:
+ field: name
+ type: slug
+ type: text
+
+tabs:
+ fields:
+ type:
+ label: 'site21.fields::lang.field.type'
+ span: left
+ required: 1
+ type: dropdown
+ default: text
+ context:
+ - create
+ tab: 'site21.fields::lang.tab.settings'
+
+
+ module:
+ label: 'site21.fields::lang.field.module'
+ span: right
+ required: 1
+ type: dropdown
+ default: product
+ context:
+ - create
+ tab: 'site21.fields::lang.tab.settings'
+
+ tab:
+ label: 'site21.fields::lang.field.tab'
+ span: auto
+ type: dropdown
+ default: 'site21.fields::lang.fields.tab'
+ dependsOn: module
+ tab: 'site21.fields::lang.tab.settings'
+
+ comment:
+ label: 'site21.fields::lang.field.comment'
+ span: auto
+ tab: 'site21.fields::lang.tab.additional'
+ comment: 'site21.fields::lang.comments.comment'
+
+ span:
+ label: 'site21.fields::lang.field.span'
+ span: auto
+ type: dropdown
+ default: auto
+ tab: 'site21.fields::lang.tab.additional'
+
+ size:
+ label: 'site21.fields::lang.field.size'
+ span: auto
+ type: dropdown
+ default: tiny
+ tab: 'site21.fields::lang.tab.additional'
+ comment: 'site21.fields::lang.comments.size'
+
+ settings[cached]:
+ label: 'site21.fields::lang.field.cached'
+ span: auto
+ type: switch
+ default: 1
+ tab: 'site21.fields::lang.tab.additional'
+ on: 'site21.fields::lang.active.on'
+ off: 'site21.fields::lang.active.off'
+
+ settings[inlist]:
+ label: 'site21.fields::lang.field.inlist'
+ span: auto
+ type: switch
+ default: 0
+ tab: 'site21.fields::lang.tab.additional'
+ on: 'site21.fields::lang.active.on'
+ off: 'site21.fields::lang.active.off'
\ No newline at end of file
diff --git a/plugins/site21/fields/updates/add_module_field.php b/plugins/site21/fields/updates/add_module_field.php
new file mode 100644
index 00000000..6c90ac59
--- /dev/null
+++ b/plugins/site21/fields/updates/add_module_field.php
@@ -0,0 +1,35 @@
+string('module')->default(0);
+ });
+ }
+
+ public function down()
+ {
+ if (!Schema::hasTable(self::TABLE_NAME) || !Schema::hasColumn(self::TABLE_NAME, 'module')) {
+ return;
+ }
+
+ Schema::table(self::TABLE_NAME, function (Blueprint $obTable)
+ {
+ $obTable->dropColumn(['module']);
+ });
+ }
+}
\ No newline at end of file
diff --git a/plugins/site21/fields/updates/add_settings_field.php b/plugins/site21/fields/updates/add_settings_field.php
new file mode 100644
index 00000000..91d62b96
--- /dev/null
+++ b/plugins/site21/fields/updates/add_settings_field.php
@@ -0,0 +1,35 @@
+json('settings')->nullable();
+ });
+ }
+
+ public function down()
+ {
+ if (!Schema::hasTable(self::TABLE_NAME) || !Schema::hasColumn(self::TABLE_NAME, 'settings')) {
+ return;
+ }
+
+ Schema::table(self::TABLE_NAME, function (Blueprint $obTable)
+ {
+ $obTable->dropColumn(['settings']);
+ });
+ }
+}
\ No newline at end of file
diff --git a/plugins/site21/fields/updates/add_span_size_comment.php b/plugins/site21/fields/updates/add_span_size_comment.php
new file mode 100644
index 00000000..b96b8750
--- /dev/null
+++ b/plugins/site21/fields/updates/add_span_size_comment.php
@@ -0,0 +1,42 @@
+string('span')->nullable();
+ $obTable->string('size')->nullable();
+ $obTable->string('comment')->nullable();
+ });
+ }
+
+ public function down()
+ {
+ if (!Schema::hasTable(self::TABLE_NAME) || !Schema::hasColumn(self::TABLE_NAME, 'span') || !Schema::hasColumn(self::TABLE_NAME, 'size') || !Schema::hasColumn(self::TABLE_NAME, 'comment')) {
+ return;
+ }
+
+ Schema::table(self::TABLE_NAME, function (Blueprint $obTable)
+ {
+ $obTable->dropColumn(['span', 'size', 'comment']);
+ });
+ }
+}
\ No newline at end of file
diff --git a/plugins/site21/fields/updates/add_tab_field.php b/plugins/site21/fields/updates/add_tab_field.php
new file mode 100644
index 00000000..c59768a5
--- /dev/null
+++ b/plugins/site21/fields/updates/add_tab_field.php
@@ -0,0 +1,35 @@
+string('tab')->nullable();
+ });
+ }
+
+ public function down()
+ {
+ if (!Schema::hasTable(self::TABLE_NAME) || !Schema::hasColumn(self::TABLE_NAME, 'tab')) {
+ return;
+ }
+
+ Schema::table(self::TABLE_NAME, function (Blueprint $obTable)
+ {
+ $obTable->dropColumn(['tab']);
+ });
+ }
+}
\ No newline at end of file
diff --git a/plugins/site21/fields/updates/create_fields_table.php b/plugins/site21/fields/updates/create_fields_table.php
new file mode 100644
index 00000000..07b2052e
--- /dev/null
+++ b/plugins/site21/fields/updates/create_fields_table.php
@@ -0,0 +1,26 @@
+engine = 'InnoDB';
+ $table->increments('id');
+ $table->string('name');
+ $table->string('slug');
+ $table->string('type');
+ $table->boolean('active')->default(true);
+ $table->timestamps();
+ });
+ }
+
+ public function down()
+ {
+ Schema::dropIfExists('site21_shopaholic_fields');
+ }
+}
diff --git a/plugins/site21/fields/updates/version.yaml b/plugins/site21/fields/updates/version.yaml
new file mode 100644
index 00000000..337fca79
--- /dev/null
+++ b/plugins/site21/fields/updates/version.yaml
@@ -0,0 +1,31 @@
+1.0.1: First version of Fields
+1.0.2:
+ - Create table with Fields
+ - create_fields_table.php
+1.0.3:
+ - Add module field to table
+ - add_module_field.php
+1.0.4:
+ - Add tab field to table
+ - add_tab_field.
+1.0.5:
+ - Add span, size and comments fields to table
+ - add_span_size_comment.php
+1.0.5.1:
+ - Fixed bug in version.yaml
+1.0.5.2:
+ - Fixed bug in version.yaml
+1.0.6:
+ - Fixed bug with tab in version.yaml
+ - add_tab_field.php
+1.0.7:
+ - Add permission to add
+1.0.7.1:
+ - Error fixing in add_span_size_comment.php
+1.0.8:
+ - Add support for the Translate plugin. Add cached fields
+ - add_settings_field.php
+1.0.9:
+ - Ability to add columns to List widget
+1.0.9.1:
+ - Fixed one error
\ No newline at end of file
diff --git a/themes/30coffe/assets/css/bootstrap.min.css b/themes/30coffe/assets/css/bootstrap.min.css
new file mode 100644
index 00000000..5d19d826
--- /dev/null
+++ b/themes/30coffe/assets/css/bootstrap.min.css
@@ -0,0 +1,6 @@
+@charset "UTF-8";/*!
+ * Bootstrap v5.1.3 (https://getbootstrap.com/)
+ * Copyright 2011-2021 The Bootstrap Authors
+ * Copyright 2011-2021 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
+ */:root{--bs-blue:#0d6efd;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#d63384;--bs-red:#dc3545;--bs-orange:#fd7e14;--bs-yellow:#ffc107;--bs-green:#198754;--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-white:#fff;--bs-gray:#6c757d;--bs-gray-dark:#343a40;--bs-gray-100:#f8f9fa;--bs-gray-200:#e9ecef;--bs-gray-300:#dee2e6;--bs-gray-400:#ced4da;--bs-gray-500:#adb5bd;--bs-gray-600:#6c757d;--bs-gray-700:#495057;--bs-gray-800:#343a40;--bs-gray-900:#212529;--bs-primary:#0d6efd;--bs-secondary:#6c757d;--bs-success:#198754;--bs-info:#0dcaf0;--bs-warning:#ffc107;--bs-danger:#dc3545;--bs-light:#f8f9fa;--bs-dark:#212529;--bs-primary-rgb:13,110,253;--bs-secondary-rgb:108,117,125;--bs-success-rgb:25,135,84;--bs-info-rgb:13,202,240;--bs-warning-rgb:255,193,7;--bs-danger-rgb:220,53,69;--bs-light-rgb:248,249,250;--bs-dark-rgb:33,37,41;--bs-white-rgb:255,255,255;--bs-black-rgb:0,0,0;--bs-body-color-rgb:33,37,41;--bs-body-bg-rgb:255,255,255;--bs-font-sans-serif:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--bs-font-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--bs-gradient:linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));--bs-body-font-family:var(--bs-font-sans-serif);--bs-body-font-size:1rem;--bs-body-font-weight:400;--bs-body-line-height:1.5;--bs-body-color:#212529;--bs-body-bg:#fff}*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){.h1,h1{font-size:2.5rem}}.h2,h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){.h2,h2{font-size:2rem}}.h3,h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){.h3,h3{font-size:1.75rem}}.h4,h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){.h4,h4{font-size:1.5rem}}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}.small,small{font-size:.875em}.mark,mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:var(--bs-font-monospace);font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::-webkit-file-upload-button{font:inherit}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-6{font-size:2.5rem}}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:.875em;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-1rem;margin-bottom:1rem;font-size:.875em;color:#6c757d}.blockquote-footer::before{content:"— "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:.875em;color:#6c757d}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{width:100%;padding-right:var(--bs-gutter-x,.75rem);padding-left:var(--bs-gutter-x,.75rem);margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}@media (min-width:1400px){.container,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{max-width:1320px}}.row{--bs-gutter-x:1.5rem;--bs-gutter-y:0;display:flex;flex-wrap:wrap;margin-top:calc(-1 * var(--bs-gutter-y));margin-right:calc(-.5 * var(--bs-gutter-x));margin-left:calc(-.5 * var(--bs-gutter-x))}.row>*{flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-top:var(--bs-gutter-y)}.col{flex:1 0 0%}.row-cols-auto>*{flex:0 0 auto;width:auto}.row-cols-1>*{flex:0 0 auto;width:100%}.row-cols-2>*{flex:0 0 auto;width:50%}.row-cols-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-4>*{flex:0 0 auto;width:25%}.row-cols-5>*{flex:0 0 auto;width:20%}.row-cols-6>*{flex:0 0 auto;width:16.6666666667%}.col-auto{flex:0 0 auto;width:auto}.col-1{flex:0 0 auto;width:8.33333333%}.col-2{flex:0 0 auto;width:16.66666667%}.col-3{flex:0 0 auto;width:25%}.col-4{flex:0 0 auto;width:33.33333333%}.col-5{flex:0 0 auto;width:41.66666667%}.col-6{flex:0 0 auto;width:50%}.col-7{flex:0 0 auto;width:58.33333333%}.col-8{flex:0 0 auto;width:66.66666667%}.col-9{flex:0 0 auto;width:75%}.col-10{flex:0 0 auto;width:83.33333333%}.col-11{flex:0 0 auto;width:91.66666667%}.col-12{flex:0 0 auto;width:100%}.offset-1{margin-left:8.33333333%}.offset-2{margin-left:16.66666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.33333333%}.offset-5{margin-left:41.66666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.33333333%}.offset-8{margin-left:66.66666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.33333333%}.offset-11{margin-left:91.66666667%}.g-0,.gx-0{--bs-gutter-x:0}.g-0,.gy-0{--bs-gutter-y:0}.g-1,.gx-1{--bs-gutter-x:0.25rem}.g-1,.gy-1{--bs-gutter-y:0.25rem}.g-2,.gx-2{--bs-gutter-x:0.5rem}.g-2,.gy-2{--bs-gutter-y:0.5rem}.g-3,.gx-3{--bs-gutter-x:1rem}.g-3,.gy-3{--bs-gutter-y:1rem}.g-4,.gx-4{--bs-gutter-x:1.5rem}.g-4,.gy-4{--bs-gutter-y:1.5rem}.g-5,.gx-5{--bs-gutter-x:3rem}.g-5,.gy-5{--bs-gutter-y:3rem}@media (min-width:576px){.col-sm{flex:1 0 0%}.row-cols-sm-auto>*{flex:0 0 auto;width:auto}.row-cols-sm-1>*{flex:0 0 auto;width:100%}.row-cols-sm-2>*{flex:0 0 auto;width:50%}.row-cols-sm-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-sm-4>*{flex:0 0 auto;width:25%}.row-cols-sm-5>*{flex:0 0 auto;width:20%}.row-cols-sm-6>*{flex:0 0 auto;width:16.6666666667%}.col-sm-auto{flex:0 0 auto;width:auto}.col-sm-1{flex:0 0 auto;width:8.33333333%}.col-sm-2{flex:0 0 auto;width:16.66666667%}.col-sm-3{flex:0 0 auto;width:25%}.col-sm-4{flex:0 0 auto;width:33.33333333%}.col-sm-5{flex:0 0 auto;width:41.66666667%}.col-sm-6{flex:0 0 auto;width:50%}.col-sm-7{flex:0 0 auto;width:58.33333333%}.col-sm-8{flex:0 0 auto;width:66.66666667%}.col-sm-9{flex:0 0 auto;width:75%}.col-sm-10{flex:0 0 auto;width:83.33333333%}.col-sm-11{flex:0 0 auto;width:91.66666667%}.col-sm-12{flex:0 0 auto;width:100%}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.33333333%}.offset-sm-2{margin-left:16.66666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.33333333%}.offset-sm-5{margin-left:41.66666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.33333333%}.offset-sm-8{margin-left:66.66666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.33333333%}.offset-sm-11{margin-left:91.66666667%}.g-sm-0,.gx-sm-0{--bs-gutter-x:0}.g-sm-0,.gy-sm-0{--bs-gutter-y:0}.g-sm-1,.gx-sm-1{--bs-gutter-x:0.25rem}.g-sm-1,.gy-sm-1{--bs-gutter-y:0.25rem}.g-sm-2,.gx-sm-2{--bs-gutter-x:0.5rem}.g-sm-2,.gy-sm-2{--bs-gutter-y:0.5rem}.g-sm-3,.gx-sm-3{--bs-gutter-x:1rem}.g-sm-3,.gy-sm-3{--bs-gutter-y:1rem}.g-sm-4,.gx-sm-4{--bs-gutter-x:1.5rem}.g-sm-4,.gy-sm-4{--bs-gutter-y:1.5rem}.g-sm-5,.gx-sm-5{--bs-gutter-x:3rem}.g-sm-5,.gy-sm-5{--bs-gutter-y:3rem}}@media (min-width:768px){.col-md{flex:1 0 0%}.row-cols-md-auto>*{flex:0 0 auto;width:auto}.row-cols-md-1>*{flex:0 0 auto;width:100%}.row-cols-md-2>*{flex:0 0 auto;width:50%}.row-cols-md-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-md-4>*{flex:0 0 auto;width:25%}.row-cols-md-5>*{flex:0 0 auto;width:20%}.row-cols-md-6>*{flex:0 0 auto;width:16.6666666667%}.col-md-auto{flex:0 0 auto;width:auto}.col-md-1{flex:0 0 auto;width:8.33333333%}.col-md-2{flex:0 0 auto;width:16.66666667%}.col-md-3{flex:0 0 auto;width:25%}.col-md-4{flex:0 0 auto;width:33.33333333%}.col-md-5{flex:0 0 auto;width:41.66666667%}.col-md-6{flex:0 0 auto;width:50%}.col-md-7{flex:0 0 auto;width:58.33333333%}.col-md-8{flex:0 0 auto;width:66.66666667%}.col-md-9{flex:0 0 auto;width:75%}.col-md-10{flex:0 0 auto;width:83.33333333%}.col-md-11{flex:0 0 auto;width:91.66666667%}.col-md-12{flex:0 0 auto;width:100%}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.33333333%}.offset-md-2{margin-left:16.66666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.33333333%}.offset-md-5{margin-left:41.66666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.33333333%}.offset-md-8{margin-left:66.66666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.33333333%}.offset-md-11{margin-left:91.66666667%}.g-md-0,.gx-md-0{--bs-gutter-x:0}.g-md-0,.gy-md-0{--bs-gutter-y:0}.g-md-1,.gx-md-1{--bs-gutter-x:0.25rem}.g-md-1,.gy-md-1{--bs-gutter-y:0.25rem}.g-md-2,.gx-md-2{--bs-gutter-x:0.5rem}.g-md-2,.gy-md-2{--bs-gutter-y:0.5rem}.g-md-3,.gx-md-3{--bs-gutter-x:1rem}.g-md-3,.gy-md-3{--bs-gutter-y:1rem}.g-md-4,.gx-md-4{--bs-gutter-x:1.5rem}.g-md-4,.gy-md-4{--bs-gutter-y:1.5rem}.g-md-5,.gx-md-5{--bs-gutter-x:3rem}.g-md-5,.gy-md-5{--bs-gutter-y:3rem}}@media (min-width:992px){.col-lg{flex:1 0 0%}.row-cols-lg-auto>*{flex:0 0 auto;width:auto}.row-cols-lg-1>*{flex:0 0 auto;width:100%}.row-cols-lg-2>*{flex:0 0 auto;width:50%}.row-cols-lg-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-lg-4>*{flex:0 0 auto;width:25%}.row-cols-lg-5>*{flex:0 0 auto;width:20%}.row-cols-lg-6>*{flex:0 0 auto;width:16.6666666667%}.col-lg-auto{flex:0 0 auto;width:auto}.col-lg-1{flex:0 0 auto;width:8.33333333%}.col-lg-2{flex:0 0 auto;width:16.66666667%}.col-lg-3{flex:0 0 auto;width:25%}.col-lg-4{flex:0 0 auto;width:33.33333333%}.col-lg-5{flex:0 0 auto;width:41.66666667%}.col-lg-6{flex:0 0 auto;width:50%}.col-lg-7{flex:0 0 auto;width:58.33333333%}.col-lg-8{flex:0 0 auto;width:66.66666667%}.col-lg-9{flex:0 0 auto;width:75%}.col-lg-10{flex:0 0 auto;width:83.33333333%}.col-lg-11{flex:0 0 auto;width:91.66666667%}.col-lg-12{flex:0 0 auto;width:100%}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.33333333%}.offset-lg-2{margin-left:16.66666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.33333333%}.offset-lg-5{margin-left:41.66666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.33333333%}.offset-lg-8{margin-left:66.66666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.33333333%}.offset-lg-11{margin-left:91.66666667%}.g-lg-0,.gx-lg-0{--bs-gutter-x:0}.g-lg-0,.gy-lg-0{--bs-gutter-y:0}.g-lg-1,.gx-lg-1{--bs-gutter-x:0.25rem}.g-lg-1,.gy-lg-1{--bs-gutter-y:0.25rem}.g-lg-2,.gx-lg-2{--bs-gutter-x:0.5rem}.g-lg-2,.gy-lg-2{--bs-gutter-y:0.5rem}.g-lg-3,.gx-lg-3{--bs-gutter-x:1rem}.g-lg-3,.gy-lg-3{--bs-gutter-y:1rem}.g-lg-4,.gx-lg-4{--bs-gutter-x:1.5rem}.g-lg-4,.gy-lg-4{--bs-gutter-y:1.5rem}.g-lg-5,.gx-lg-5{--bs-gutter-x:3rem}.g-lg-5,.gy-lg-5{--bs-gutter-y:3rem}}@media (min-width:1200px){.col-xl{flex:1 0 0%}.row-cols-xl-auto>*{flex:0 0 auto;width:auto}.row-cols-xl-1>*{flex:0 0 auto;width:100%}.row-cols-xl-2>*{flex:0 0 auto;width:50%}.row-cols-xl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xl-4>*{flex:0 0 auto;width:25%}.row-cols-xl-5>*{flex:0 0 auto;width:20%}.row-cols-xl-6>*{flex:0 0 auto;width:16.6666666667%}.col-xl-auto{flex:0 0 auto;width:auto}.col-xl-1{flex:0 0 auto;width:8.33333333%}.col-xl-2{flex:0 0 auto;width:16.66666667%}.col-xl-3{flex:0 0 auto;width:25%}.col-xl-4{flex:0 0 auto;width:33.33333333%}.col-xl-5{flex:0 0 auto;width:41.66666667%}.col-xl-6{flex:0 0 auto;width:50%}.col-xl-7{flex:0 0 auto;width:58.33333333%}.col-xl-8{flex:0 0 auto;width:66.66666667%}.col-xl-9{flex:0 0 auto;width:75%}.col-xl-10{flex:0 0 auto;width:83.33333333%}.col-xl-11{flex:0 0 auto;width:91.66666667%}.col-xl-12{flex:0 0 auto;width:100%}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.33333333%}.offset-xl-2{margin-left:16.66666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.33333333%}.offset-xl-5{margin-left:41.66666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.33333333%}.offset-xl-8{margin-left:66.66666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.33333333%}.offset-xl-11{margin-left:91.66666667%}.g-xl-0,.gx-xl-0{--bs-gutter-x:0}.g-xl-0,.gy-xl-0{--bs-gutter-y:0}.g-xl-1,.gx-xl-1{--bs-gutter-x:0.25rem}.g-xl-1,.gy-xl-1{--bs-gutter-y:0.25rem}.g-xl-2,.gx-xl-2{--bs-gutter-x:0.5rem}.g-xl-2,.gy-xl-2{--bs-gutter-y:0.5rem}.g-xl-3,.gx-xl-3{--bs-gutter-x:1rem}.g-xl-3,.gy-xl-3{--bs-gutter-y:1rem}.g-xl-4,.gx-xl-4{--bs-gutter-x:1.5rem}.g-xl-4,.gy-xl-4{--bs-gutter-y:1.5rem}.g-xl-5,.gx-xl-5{--bs-gutter-x:3rem}.g-xl-5,.gy-xl-5{--bs-gutter-y:3rem}}@media (min-width:1400px){.col-xxl{flex:1 0 0%}.row-cols-xxl-auto>*{flex:0 0 auto;width:auto}.row-cols-xxl-1>*{flex:0 0 auto;width:100%}.row-cols-xxl-2>*{flex:0 0 auto;width:50%}.row-cols-xxl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xxl-4>*{flex:0 0 auto;width:25%}.row-cols-xxl-5>*{flex:0 0 auto;width:20%}.row-cols-xxl-6>*{flex:0 0 auto;width:16.6666666667%}.col-xxl-auto{flex:0 0 auto;width:auto}.col-xxl-1{flex:0 0 auto;width:8.33333333%}.col-xxl-2{flex:0 0 auto;width:16.66666667%}.col-xxl-3{flex:0 0 auto;width:25%}.col-xxl-4{flex:0 0 auto;width:33.33333333%}.col-xxl-5{flex:0 0 auto;width:41.66666667%}.col-xxl-6{flex:0 0 auto;width:50%}.col-xxl-7{flex:0 0 auto;width:58.33333333%}.col-xxl-8{flex:0 0 auto;width:66.66666667%}.col-xxl-9{flex:0 0 auto;width:75%}.col-xxl-10{flex:0 0 auto;width:83.33333333%}.col-xxl-11{flex:0 0 auto;width:91.66666667%}.col-xxl-12{flex:0 0 auto;width:100%}.offset-xxl-0{margin-left:0}.offset-xxl-1{margin-left:8.33333333%}.offset-xxl-2{margin-left:16.66666667%}.offset-xxl-3{margin-left:25%}.offset-xxl-4{margin-left:33.33333333%}.offset-xxl-5{margin-left:41.66666667%}.offset-xxl-6{margin-left:50%}.offset-xxl-7{margin-left:58.33333333%}.offset-xxl-8{margin-left:66.66666667%}.offset-xxl-9{margin-left:75%}.offset-xxl-10{margin-left:83.33333333%}.offset-xxl-11{margin-left:91.66666667%}.g-xxl-0,.gx-xxl-0{--bs-gutter-x:0}.g-xxl-0,.gy-xxl-0{--bs-gutter-y:0}.g-xxl-1,.gx-xxl-1{--bs-gutter-x:0.25rem}.g-xxl-1,.gy-xxl-1{--bs-gutter-y:0.25rem}.g-xxl-2,.gx-xxl-2{--bs-gutter-x:0.5rem}.g-xxl-2,.gy-xxl-2{--bs-gutter-y:0.5rem}.g-xxl-3,.gx-xxl-3{--bs-gutter-x:1rem}.g-xxl-3,.gy-xxl-3{--bs-gutter-y:1rem}.g-xxl-4,.gx-xxl-4{--bs-gutter-x:1.5rem}.g-xxl-4,.gy-xxl-4{--bs-gutter-y:1.5rem}.g-xxl-5,.gx-xxl-5{--bs-gutter-x:3rem}.g-xxl-5,.gy-xxl-5{--bs-gutter-y:3rem}}.table{--bs-table-bg:transparent;--bs-table-accent-bg:transparent;--bs-table-striped-color:#212529;--bs-table-striped-bg:rgba(0, 0, 0, 0.05);--bs-table-active-color:#212529;--bs-table-active-bg:rgba(0, 0, 0, 0.1);--bs-table-hover-color:#212529;--bs-table-hover-bg:rgba(0, 0, 0, 0.075);width:100%;margin-bottom:1rem;color:#212529;vertical-align:top;border-color:#dee2e6}.table>:not(caption)>*>*{padding:.5rem .5rem;background-color:var(--bs-table-bg);border-bottom-width:1px;box-shadow:inset 0 0 0 9999px var(--bs-table-accent-bg)}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table>:not(:first-child){border-top:2px solid currentColor}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem .25rem}.table-bordered>:not(caption)>*{border-width:1px 0}.table-bordered>:not(caption)>*>*{border-width:0 1px}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-borderless>:not(:first-child){border-top-width:0}.table-striped>tbody>tr:nth-of-type(odd)>*{--bs-table-accent-bg:var(--bs-table-striped-bg);color:var(--bs-table-striped-color)}.table-active{--bs-table-accent-bg:var(--bs-table-active-bg);color:var(--bs-table-active-color)}.table-hover>tbody>tr:hover>*{--bs-table-accent-bg:var(--bs-table-hover-bg);color:var(--bs-table-hover-color)}.table-primary{--bs-table-bg:#cfe2ff;--bs-table-striped-bg:#c5d7f2;--bs-table-striped-color:#000;--bs-table-active-bg:#bacbe6;--bs-table-active-color:#000;--bs-table-hover-bg:#bfd1ec;--bs-table-hover-color:#000;color:#000;border-color:#bacbe6}.table-secondary{--bs-table-bg:#e2e3e5;--bs-table-striped-bg:#d7d8da;--bs-table-striped-color:#000;--bs-table-active-bg:#cbccce;--bs-table-active-color:#000;--bs-table-hover-bg:#d1d2d4;--bs-table-hover-color:#000;color:#000;border-color:#cbccce}.table-success{--bs-table-bg:#d1e7dd;--bs-table-striped-bg:#c7dbd2;--bs-table-striped-color:#000;--bs-table-active-bg:#bcd0c7;--bs-table-active-color:#000;--bs-table-hover-bg:#c1d6cc;--bs-table-hover-color:#000;color:#000;border-color:#bcd0c7}.table-info{--bs-table-bg:#cff4fc;--bs-table-striped-bg:#c5e8ef;--bs-table-striped-color:#000;--bs-table-active-bg:#badce3;--bs-table-active-color:#000;--bs-table-hover-bg:#bfe2e9;--bs-table-hover-color:#000;color:#000;border-color:#badce3}.table-warning{--bs-table-bg:#fff3cd;--bs-table-striped-bg:#f2e7c3;--bs-table-striped-color:#000;--bs-table-active-bg:#e6dbb9;--bs-table-active-color:#000;--bs-table-hover-bg:#ece1be;--bs-table-hover-color:#000;color:#000;border-color:#e6dbb9}.table-danger{--bs-table-bg:#f8d7da;--bs-table-striped-bg:#eccccf;--bs-table-striped-color:#000;--bs-table-active-bg:#dfc2c4;--bs-table-active-color:#000;--bs-table-hover-bg:#e5c7ca;--bs-table-hover-color:#000;color:#000;border-color:#dfc2c4}.table-light{--bs-table-bg:#f8f9fa;--bs-table-striped-bg:#ecedee;--bs-table-striped-color:#000;--bs-table-active-bg:#dfe0e1;--bs-table-active-color:#000;--bs-table-hover-bg:#e5e6e7;--bs-table-hover-color:#000;color:#000;border-color:#dfe0e1}.table-dark{--bs-table-bg:#212529;--bs-table-striped-bg:#2c3034;--bs-table-striped-color:#fff;--bs-table-active-bg:#373b3e;--bs-table-active-color:#fff;--bs-table-hover-bg:#323539;--bs-table-hover-color:#fff;color:#fff;border-color:#373b3e}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media (max-width:575.98px){.table-responsive-sm{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:767.98px){.table-responsive-md{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:991.98px){.table-responsive-lg{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1199.98px){.table-responsive-xl{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1399.98px){.table-responsive-xxl{overflow-x:auto;-webkit-overflow-scrolling:touch}}.form-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem}.form-text{margin-top:.25rem;font-size:.875em;color:#6c757d}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:#212529;background-color:#fff;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-control::-webkit-date-and-time-value{height:1.5em}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}.form-control::-webkit-file-upload-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;-webkit-transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}.form-control::file-selector-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::-webkit-file-upload-button{-webkit-transition:none;transition:none}.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:#dde0e3}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:#dde0e3}.form-control::-webkit-file-upload-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;-webkit-transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::-webkit-file-upload-button{-webkit-transition:none;transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:#dde0e3}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + .75rem + 2px)}textarea.form-control-sm{min-height:calc(1.5em + .5rem + 2px)}textarea.form-control-lg{min-height:calc(1.5em + 1rem + 2px)}.form-control-color{width:3rem;height:auto;padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{height:1.5em;border-radius:.25rem}.form-control-color::-webkit-color-swatch{height:1.5em;border-radius:.25rem}.form-select{display:block;width:100%;padding:.375rem 2.25rem .375rem .75rem;-moz-padding-start:calc(0.75rem - 3px);font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right .75rem center;background-size:16px 12px;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-select{transition:none}}.form-select:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:.75rem;background-image:none}.form-select:disabled{background-color:#e9ecef}.form-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #212529}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem;border-radius:.2rem}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem;border-radius:.3rem}.form-check{display:block;min-height:1.5rem;padding-left:1.5em;margin-bottom:.125rem}.form-check .form-check-input{float:left;margin-left:-1.5em}.form-check-input{width:1em;height:1em;margin-top:.25em;vertical-align:top;background-color:#fff;background-repeat:no-repeat;background-position:center;background-size:contain;border:1px solid rgba(0,0,0,.25);-webkit-appearance:none;-moz-appearance:none;appearance:none;-webkit-print-color-adjust:exact;color-adjust:exact}.form-check-input[type=checkbox]{border-radius:.25em}.form-check-input[type=radio]{border-radius:50%}.form-check-input:active{filter:brightness(90%)}.form-check-input:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-check-input:checked{background-color:#0d6efd;border-color:#0d6efd}.form-check-input:checked[type=checkbox]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate{background-color:#0d6efd;border-color:#0d6efd;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")}.form-check-input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{opacity:.5}.form-switch{padding-left:2.5em}.form-switch .form-check-input{width:2em;margin-left:-2.5em;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");background-position:left center;border-radius:2em;transition:background-position .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2386b7fe'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:right center;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.form-check-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.btn-check:disabled+.btn,.btn-check[disabled]+.btn{pointer-events:none;filter:none;opacity:.65}.form-range{width:100%;height:1.5rem;padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#0d6efd;border:0;border-radius:1rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#b6d4fe}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.form-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#0d6efd;border:0;border-radius:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-range::-moz-range-thumb{-moz-transition:none;transition:none}}.form-range::-moz-range-thumb:active{background-color:#b6d4fe}.form-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.form-range:disabled::-moz-range-thumb{background-color:#adb5bd}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-select{height:calc(3.5rem + 2px);line-height:1.25}.form-floating>label{position:absolute;top:0;left:0;height:100%;padding:1rem .75rem;pointer-events:none;border:1px solid transparent;transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out}@media (prefers-reduced-motion:reduce){.form-floating>label{transition:none}}.form-floating>.form-control{padding:1rem .75rem}.form-floating>.form-control::-moz-placeholder{color:transparent}.form-floating>.form-control::placeholder{color:transparent}.form-floating>.form-control:not(:-moz-placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:not(:-moz-placeholder-shown)~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-select~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:-webkit-autofill~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-select{position:relative;flex:1 1 auto;width:1%;min-width:0}.input-group>.form-control:focus,.input-group>.form-select:focus{z-index:3}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:3}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-lg>.btn,.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.input-group-sm>.btn,.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3),.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu){border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4),.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:-1px;border-top-left-radius:0;border-bottom-left-radius:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#198754}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:rgba(25,135,84,.9);border-radius:.25rem}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:#198754;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-valid,.was-validated .form-select:valid{border-color:#198754}.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"],.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-valid:focus,.was-validated .form-select:valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.form-check-input.is-valid,.was-validated .form-check-input:valid{border-color:#198754}.form-check-input.is-valid:checked,.was-validated .form-check-input:valid:checked{background-color:#198754}.form-check-input.is-valid:focus,.was-validated .form-check-input:valid:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#198754}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.input-group .form-control.is-valid,.input-group .form-select.is-valid,.was-validated .input-group .form-control:valid,.was-validated .input-group .form-select:valid{z-index:1}.input-group .form-control.is-valid:focus,.input-group .form-select.is-valid:focus,.was-validated .input-group .form-control:valid:focus,.was-validated .input-group .form-select:valid:focus{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#dc3545;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-invalid,.was-validated .form-select:invalid{border-color:#dc3545}.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"],.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-invalid:focus,.was-validated .form-select:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.form-check-input.is-invalid,.was-validated .form-check-input:invalid{border-color:#dc3545}.form-check-input.is-invalid:checked,.was-validated .form-check-input:invalid:checked{background-color:#dc3545}.form-check-input.is-invalid:focus,.was-validated .form-check-input:invalid:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.input-group .form-control.is-invalid,.input-group .form-select.is-invalid,.was-validated .input-group .form-control:invalid,.was-validated .input-group .form-select:invalid{z-index:2}.input-group .form-control.is-invalid:focus,.input-group .form-select.is-invalid:focus,.was-validated .input-group .form-control:invalid:focus,.was-validated .input-group .form-select:invalid:focus{z-index:3}.btn{display:inline-block;font-weight:400;line-height:1.5;color:#212529;text-align:center;text-decoration:none;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#212529}.btn-check:focus+.btn,.btn:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.btn.disabled,.btn:disabled,fieldset:disabled .btn{pointer-events:none;opacity:.65}.btn-primary{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-primary:hover{color:#fff;background-color:#0b5ed7;border-color:#0a58ca}.btn-check:focus+.btn-primary,.btn-primary:focus{color:#fff;background-color:#0b5ed7;border-color:#0a58ca;box-shadow:0 0 0 .25rem rgba(49,132,253,.5)}.btn-check:active+.btn-primary,.btn-check:checked+.btn-primary,.btn-primary.active,.btn-primary:active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0a58ca;border-color:#0a53be}.btn-check:active+.btn-primary:focus,.btn-check:checked+.btn-primary:focus,.btn-primary.active:focus,.btn-primary:active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(49,132,253,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5c636a;border-color:#565e64}.btn-check:focus+.btn-secondary,.btn-secondary:focus{color:#fff;background-color:#5c636a;border-color:#565e64;box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-check:active+.btn-secondary,.btn-check:checked+.btn-secondary,.btn-secondary.active,.btn-secondary:active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#565e64;border-color:#51585e}.btn-check:active+.btn-secondary:focus,.btn-check:checked+.btn-secondary:focus,.btn-secondary.active:focus,.btn-secondary:active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-success{color:#fff;background-color:#198754;border-color:#198754}.btn-success:hover{color:#fff;background-color:#157347;border-color:#146c43}.btn-check:focus+.btn-success,.btn-success:focus{color:#fff;background-color:#157347;border-color:#146c43;box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-check:active+.btn-success,.btn-check:checked+.btn-success,.btn-success.active,.btn-success:active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#146c43;border-color:#13653f}.btn-check:active+.btn-success:focus,.btn-check:checked+.btn-success:focus,.btn-success.active:focus,.btn-success:active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#198754;border-color:#198754}.btn-info{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-info:hover{color:#000;background-color:#31d2f2;border-color:#25cff2}.btn-check:focus+.btn-info,.btn-info:focus{color:#000;background-color:#31d2f2;border-color:#25cff2;box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-check:active+.btn-info,.btn-check:checked+.btn-info,.btn-info.active,.btn-info:active,.show>.btn-info.dropdown-toggle{color:#000;background-color:#3dd5f3;border-color:#25cff2}.btn-check:active+.btn-info:focus,.btn-check:checked+.btn-info:focus,.btn-info.active:focus,.btn-info:active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-info.disabled,.btn-info:disabled{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-warning{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#000;background-color:#ffca2c;border-color:#ffc720}.btn-check:focus+.btn-warning,.btn-warning:focus{color:#000;background-color:#ffca2c;border-color:#ffc720;box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-check:active+.btn-warning,.btn-check:checked+.btn-warning,.btn-warning.active,.btn-warning:active,.show>.btn-warning.dropdown-toggle{color:#000;background-color:#ffcd39;border-color:#ffc720}.btn-check:active+.btn-warning:focus,.btn-check:checked+.btn-warning:focus,.btn-warning.active:focus,.btn-warning:active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#bb2d3b;border-color:#b02a37}.btn-check:focus+.btn-danger,.btn-danger:focus{color:#fff;background-color:#bb2d3b;border-color:#b02a37;box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-check:active+.btn-danger,.btn-check:checked+.btn-danger,.btn-danger.active,.btn-danger:active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#b02a37;border-color:#a52834}.btn-check:active+.btn-danger:focus,.btn-check:checked+.btn-danger:focus,.btn-danger.active:focus,.btn-danger:active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-light{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:focus+.btn-light,.btn-light:focus{color:#000;background-color:#f9fafb;border-color:#f9fafb;box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-check:active+.btn-light,.btn-check:checked+.btn-light,.btn-light.active,.btn-light:active,.show>.btn-light.dropdown-toggle{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:active+.btn-light:focus,.btn-check:checked+.btn-light:focus,.btn-light.active:focus,.btn-light:active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-light.disabled,.btn-light:disabled{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-dark{color:#fff;background-color:#212529;border-color:#212529}.btn-dark:hover{color:#fff;background-color:#1c1f23;border-color:#1a1e21}.btn-check:focus+.btn-dark,.btn-dark:focus{color:#fff;background-color:#1c1f23;border-color:#1a1e21;box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-check:active+.btn-dark,.btn-check:checked+.btn-dark,.btn-dark.active,.btn-dark:active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1a1e21;border-color:#191c1f}.btn-check:active+.btn-dark:focus,.btn-check:checked+.btn-dark:focus,.btn-dark.active:focus,.btn-dark:active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#212529;border-color:#212529}.btn-outline-primary{color:#0d6efd;border-color:#0d6efd}.btn-outline-primary:hover{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-check:focus+.btn-outline-primary,.btn-outline-primary:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.5)}.btn-check:active+.btn-outline-primary,.btn-check:checked+.btn-outline-primary,.btn-outline-primary.active,.btn-outline-primary.dropdown-toggle.show,.btn-outline-primary:active{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-check:active+.btn-outline-primary:focus,.btn-check:checked+.btn-outline-primary:focus,.btn-outline-primary.active:focus,.btn-outline-primary.dropdown-toggle.show:focus,.btn-outline-primary:active:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#0d6efd;background-color:transparent}.btn-outline-secondary{color:#6c757d;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:focus+.btn-outline-secondary,.btn-outline-secondary:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-check:active+.btn-outline-secondary,.btn-check:checked+.btn-outline-secondary,.btn-outline-secondary.active,.btn-outline-secondary.dropdown-toggle.show,.btn-outline-secondary:active{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:active+.btn-outline-secondary:focus,.btn-check:checked+.btn-outline-secondary:focus,.btn-outline-secondary.active:focus,.btn-outline-secondary.dropdown-toggle.show:focus,.btn-outline-secondary:active:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-success{color:#198754;border-color:#198754}.btn-outline-success:hover{color:#fff;background-color:#198754;border-color:#198754}.btn-check:focus+.btn-outline-success,.btn-outline-success:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-check:active+.btn-outline-success,.btn-check:checked+.btn-outline-success,.btn-outline-success.active,.btn-outline-success.dropdown-toggle.show,.btn-outline-success:active{color:#fff;background-color:#198754;border-color:#198754}.btn-check:active+.btn-outline-success:focus,.btn-check:checked+.btn-outline-success:focus,.btn-outline-success.active:focus,.btn-outline-success.dropdown-toggle.show:focus,.btn-outline-success:active:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#198754;background-color:transparent}.btn-outline-info{color:#0dcaf0;border-color:#0dcaf0}.btn-outline-info:hover{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:focus+.btn-outline-info,.btn-outline-info:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-check:active+.btn-outline-info,.btn-check:checked+.btn-outline-info,.btn-outline-info.active,.btn-outline-info.dropdown-toggle.show,.btn-outline-info:active{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:active+.btn-outline-info:focus,.btn-check:checked+.btn-outline-info:focus,.btn-outline-info.active:focus,.btn-outline-info.dropdown-toggle.show:focus,.btn-outline-info:active:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#0dcaf0;background-color:transparent}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:focus+.btn-outline-warning,.btn-outline-warning:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-check:active+.btn-outline-warning,.btn-check:checked+.btn-outline-warning,.btn-outline-warning.active,.btn-outline-warning.dropdown-toggle.show,.btn-outline-warning:active{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:active+.btn-outline-warning:focus,.btn-check:checked+.btn-outline-warning:focus,.btn-outline-warning.active:focus,.btn-outline-warning.dropdown-toggle.show:focus,.btn-outline-warning:active:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:focus+.btn-outline-danger,.btn-outline-danger:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-check:active+.btn-outline-danger,.btn-check:checked+.btn-outline-danger,.btn-outline-danger.active,.btn-outline-danger.dropdown-toggle.show,.btn-outline-danger:active{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:active+.btn-outline-danger:focus,.btn-check:checked+.btn-outline-danger:focus,.btn-outline-danger.active:focus,.btn-outline-danger.dropdown-toggle.show:focus,.btn-outline-danger:active:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:focus+.btn-outline-light,.btn-outline-light:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-check:active+.btn-outline-light,.btn-check:checked+.btn-outline-light,.btn-outline-light.active,.btn-outline-light.dropdown-toggle.show,.btn-outline-light:active{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:active+.btn-outline-light:focus,.btn-check:checked+.btn-outline-light:focus,.btn-outline-light.active:focus,.btn-outline-light.dropdown-toggle.show:focus,.btn-outline-light:active:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-dark{color:#212529;border-color:#212529}.btn-outline-dark:hover{color:#fff;background-color:#212529;border-color:#212529}.btn-check:focus+.btn-outline-dark,.btn-outline-dark:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-check:active+.btn-outline-dark,.btn-check:checked+.btn-outline-dark,.btn-outline-dark.active,.btn-outline-dark.dropdown-toggle.show,.btn-outline-dark:active{color:#fff;background-color:#212529;border-color:#212529}.btn-check:active+.btn-outline-dark:focus,.btn-check:checked+.btn-outline-dark:focus,.btn-outline-dark.active:focus,.btn-outline-dark.dropdown-toggle.show:focus,.btn-outline-dark:active:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#212529;background-color:transparent}.btn-link{font-weight:400;color:#0d6efd;text-decoration:underline}.btn-link:hover{color:#0a58ca}.btn-link.disabled,.btn-link:disabled{color:#6c757d}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.collapsing.collapse-horizontal{width:0;height:auto;transition:width .35s ease}@media (prefers-reduced-motion:reduce){.collapsing.collapse-horizontal{transition:none}}.dropdown,.dropend,.dropstart,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;z-index:1000;display:none;min-width:10rem;padding:.5rem 0;margin:0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu[data-bs-popper]{top:100%;left:0;margin-top:.125rem}.dropdown-menu-start{--bs-position:start}.dropdown-menu-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-end{--bs-position:end}.dropdown-menu-end[data-bs-popper]{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-start{--bs-position:start}.dropdown-menu-sm-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-sm-end{--bs-position:end}.dropdown-menu-sm-end[data-bs-popper]{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-start{--bs-position:start}.dropdown-menu-md-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-md-end{--bs-position:end}.dropdown-menu-md-end[data-bs-popper]{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-start{--bs-position:start}.dropdown-menu-lg-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-lg-end{--bs-position:end}.dropdown-menu-lg-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-start{--bs-position:start}.dropdown-menu-xl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xl-end{--bs-position:end}.dropdown-menu-xl-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1400px){.dropdown-menu-xxl-start{--bs-position:start}.dropdown-menu-xxl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xxl-end{--bs-position:end}.dropdown-menu-xxl-end[data-bs-popper]{right:0;left:auto}}.dropup .dropdown-menu[data-bs-popper]{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropend .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropend .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-toggle::after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropstart .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropstart .dropdown-toggle::after{display:none}.dropstart .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropstart .dropdown-toggle:empty::after{margin-left:0}.dropstart .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid rgba(0,0,0,.15)}.dropdown-item{display:block;width:100%;padding:.25rem 1rem;clear:both;font-weight:400;color:#212529;text-align:inherit;text-decoration:none;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#1e2125;background-color:#e9ecef}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#0d6efd}.dropdown-item.disabled,.dropdown-item:disabled{color:#adb5bd;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1rem;color:#212529}.dropdown-menu-dark{color:#dee2e6;background-color:#343a40;border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item{color:#dee2e6}.dropdown-menu-dark .dropdown-item:focus,.dropdown-menu-dark .dropdown-item:hover{color:#fff;background-color:rgba(255,255,255,.15)}.dropdown-menu-dark .dropdown-item.active,.dropdown-menu-dark .dropdown-item:active{color:#fff;background-color:#0d6efd}.dropdown-menu-dark .dropdown-item.disabled,.dropdown-menu-dark .dropdown-item:disabled{color:#adb5bd}.dropdown-menu-dark .dropdown-divider{border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item-text{color:#dee2e6}.dropdown-menu-dark .dropdown-header{color:#adb5bd}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;flex:1 1 auto}.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:nth-child(n+3),.btn-group>:not(.btn-check)+.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropstart .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;align-items:flex-start;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn~.btn{border-top-left-radius:0;border-top-right-radius:0}.nav{display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem;color:#0d6efd;text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}@media (prefers-reduced-motion:reduce){.nav-link{transition:none}}.nav-link:focus,.nav-link:hover{color:#0a58ca}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-link{margin-bottom:-1px;background:0 0;border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6;isolation:isolate}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{background:0 0;border:0;border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#0d6efd}.nav-fill .nav-item,.nav-fill>.nav-link{flex:1 1 auto;text-align:center}.nav-justified .nav-item,.nav-justified>.nav-link{flex-basis:0;flex-grow:1;text-align:center}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;padding-top:.5rem;padding-bottom:.5rem}.navbar>.container,.navbar>.container-fluid,.navbar>.container-lg,.navbar>.container-md,.navbar>.container-sm,.navbar>.container-xl,.navbar>.container-xxl{display:flex;flex-wrap:inherit;align-items:center;justify-content:space-between}.navbar-brand{padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;text-decoration:none;white-space:nowrap}.navbar-nav{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static}.navbar-text{padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{flex-basis:100%;flex-grow:1;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem;transition:box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{text-decoration:none;outline:0;box-shadow:0 0 0 .25rem}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;background-repeat:no-repeat;background-position:center;background-size:100%}.navbar-nav-scroll{max-height:var(--bs-scroll-height,75vh);overflow-y:auto}@media (min-width:576px){.navbar-expand-sm{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .offcanvas-header{display:none}.navbar-expand-sm .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-sm .offcanvas-bottom,.navbar-expand-sm .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-sm .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:768px){.navbar-expand-md{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .offcanvas-header{display:none}.navbar-expand-md .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-md .offcanvas-bottom,.navbar-expand-md .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-md .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:992px){.navbar-expand-lg{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .offcanvas-header{display:none}.navbar-expand-lg .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-lg .offcanvas-bottom,.navbar-expand-lg .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-lg .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:1200px){.navbar-expand-xl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .offcanvas-header{display:none}.navbar-expand-xl .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-xl .offcanvas-bottom,.navbar-expand-xl .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-xl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:1400px){.navbar-expand-xxl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}.navbar-expand-xxl .offcanvas-header{display:none}.navbar-expand-xxl .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-xxl .offcanvas-bottom,.navbar-expand-xxl .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-xxl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}.navbar-expand{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-expand .offcanvas-header{display:none}.navbar-expand .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand .offcanvas-bottom,.navbar-expand .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.55)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.55);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(0,0,0,.55)}.navbar-light .navbar-text a,.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.55)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.55);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,.55)}.navbar-dark .navbar-text a,.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:flex;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;padding:1rem 1rem}.card-title{margin-bottom:.5rem}.card-subtitle{margin-top:-.25rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link+.card-link{margin-left:1rem}.card-header{padding:.5rem 1rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-footer{padding:.5rem 1rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.5rem;margin-bottom:-.5rem;margin-left:-.5rem;border-bottom:0}.card-header-pills{margin-right:-.5rem;margin-left:-.5rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1rem;border-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom,.card-img-top{width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-group>.card{margin-bottom:.75rem}@media (min-width:576px){.card-group{display:flex;flex-flow:row wrap}.card-group>.card{flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.accordion-button{position:relative;display:flex;align-items:center;width:100%;padding:1rem 1.25rem;font-size:1rem;color:#212529;text-align:left;background-color:#fff;border:0;border-radius:0;overflow-anchor:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,border-radius .15s ease}@media (prefers-reduced-motion:reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:#0c63e4;background-color:#e7f1ff;box-shadow:inset 0 -1px 0 rgba(0,0,0,.125)}.accordion-button:not(.collapsed)::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%230c63e4'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");transform:rotate(-180deg)}.accordion-button::after{flex-shrink:0;width:1.25rem;height:1.25rem;margin-left:auto;content:"";background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23212529'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-size:1.25rem;transition:transform .2s ease-in-out}@media (prefers-reduced-motion:reduce){.accordion-button::after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.accordion-header{margin-bottom:0}.accordion-item{background-color:#fff;border:1px solid rgba(0,0,0,.125)}.accordion-item:first-of-type{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.accordion-item:first-of-type .accordion-button{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.accordion-item:not(:first-of-type){border-top:0}.accordion-item:last-of-type{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-item:last-of-type .accordion-button.collapsed{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.accordion-item:last-of-type .accordion-collapse{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-body{padding:1rem 1.25rem}.accordion-flush .accordion-collapse{border-width:0}.accordion-flush .accordion-item{border-right:0;border-left:0;border-radius:0}.accordion-flush .accordion-item:first-child{border-top:0}.accordion-flush .accordion-item:last-child{border-bottom:0}.accordion-flush .accordion-item .accordion-button{border-radius:0}.breadcrumb{display:flex;flex-wrap:wrap;padding:0 0;margin-bottom:1rem;list-style:none}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:.5rem;color:#6c757d;content:var(--bs-breadcrumb-divider, "https://templates.hibootstrap.com/")}.breadcrumb-item.active{color:#6c757d}.pagination{display:flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;color:#0d6efd;text-decoration:none;background-color:#fff;border:1px solid #dee2e6;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:#0a58ca;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;color:#0a58ca;background-color:#e9ecef;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.page-item:not(:first-child) .page-link{margin-left:-1px}.page-item.active .page-link{z-index:3;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;background-color:#fff;border-color:#dee2e6}.page-link{padding:.375rem .75rem}.page-item:first-child .page-link{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.35em .65em;font-size:.75em;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{position:relative;padding:1rem 1rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:1.25rem 1rem}.alert-primary{color:#084298;background-color:#cfe2ff;border-color:#b6d4fe}.alert-primary .alert-link{color:#06357a}.alert-secondary{color:#41464b;background-color:#e2e3e5;border-color:#d3d6d8}.alert-secondary .alert-link{color:#34383c}.alert-success{color:#0f5132;background-color:#d1e7dd;border-color:#badbcc}.alert-success .alert-link{color:#0c4128}.alert-info{color:#055160;background-color:#cff4fc;border-color:#b6effb}.alert-info .alert-link{color:#04414d}.alert-warning{color:#664d03;background-color:#fff3cd;border-color:#ffecb5}.alert-warning .alert-link{color:#523e02}.alert-danger{color:#842029;background-color:#f8d7da;border-color:#f5c2c7}.alert-danger .alert-link{color:#6a1a21}.alert-light{color:#636464;background-color:#fefefe;border-color:#fdfdfe}.alert-light .alert-link{color:#4f5050}.alert-dark{color:#141619;background-color:#d3d3d4;border-color:#bcbebf}.alert-dark .alert-link{color:#101214}@-webkit-keyframes progress-bar-stripes{0%{background-position-x:1rem}}@keyframes progress-bar-stripes{0%{background-position-x:1rem}}.progress{display:flex;height:1rem;overflow:hidden;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:flex;flex-direction:column;justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#0d6efd;transition:width .6s ease}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:1s linear infinite progress-bar-stripes;animation:1s linear infinite progress-bar-stripes}@media (prefers-reduced-motion:reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.list-group{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.25rem}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>li::before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.5rem 1rem;color:#212529;text-decoration:none;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media (min-width:576px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:768px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:992px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1400px){.list-group-horizontal-xxl{flex-direction:row}.list-group-horizontal-xxl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xxl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{color:#084298;background-color:#cfe2ff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#084298;background-color:#bacbe6}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#084298;border-color:#084298}.list-group-item-secondary{color:#41464b;background-color:#e2e3e5}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#41464b;background-color:#cbccce}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#41464b;border-color:#41464b}.list-group-item-success{color:#0f5132;background-color:#d1e7dd}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#0f5132;background-color:#bcd0c7}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#0f5132;border-color:#0f5132}.list-group-item-info{color:#055160;background-color:#cff4fc}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#055160;background-color:#badce3}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#055160;border-color:#055160}.list-group-item-warning{color:#664d03;background-color:#fff3cd}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#664d03;background-color:#e6dbb9}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#664d03;border-color:#664d03}.list-group-item-danger{color:#842029;background-color:#f8d7da}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#842029;background-color:#dfc2c4}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#842029;border-color:#842029}.list-group-item-light{color:#636464;background-color:#fefefe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#636464;background-color:#e5e5e5}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#636464;border-color:#636464}.list-group-item-dark{color:#141619;background-color:#d3d3d4}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#141619;background-color:#bebebf}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#141619;border-color:#141619}.btn-close{box-sizing:content-box;width:1em;height:1em;padding:.25em .25em;color:#000;background:transparent url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat;border:0;border-radius:.25rem;opacity:.5}.btn-close:hover{color:#000;text-decoration:none;opacity:.75}.btn-close:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25);opacity:1}.btn-close.disabled,.btn-close:disabled{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;opacity:.25}.btn-close-white{filter:invert(1) grayscale(100%) brightness(200%)}.toast{width:350px;max-width:100%;font-size:.875rem;pointer-events:auto;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .5rem 1rem rgba(0,0,0,.15);border-radius:.25rem}.toast.showing{opacity:0}.toast:not(.show){display:none}.toast-container{width:-webkit-max-content;width:-moz-max-content;width:max-content;max-width:100%;pointer-events:none}.toast-container>:not(:last-child){margin-bottom:.75rem}.toast-header{display:flex;align-items:center;padding:.5rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05);border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.toast-header .btn-close{margin-right:-.375rem;margin-left:.75rem}.toast-body{padding:.75rem;word-wrap:break-word}.modal{position:fixed;top:0;left:0;z-index:1055;display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;align-items:center;min-height:calc(100% - 1rem)}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1050;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:flex;flex-shrink:0;align-items:center;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.modal-header .btn-close{padding:.5rem .5rem;margin:-.5rem -.5rem -.5rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;flex:1 1 auto;padding:1rem}.modal-footer{display:flex;flex-wrap:wrap;flex-shrink:0;align-items:center;justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6;border-bottom-right-radius:calc(.3rem - 1px);border-bottom-left-radius:calc(.3rem - 1px)}.modal-footer>*{margin:.25rem}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{height:calc(100% - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen .modal-header{border-radius:0}.modal-fullscreen .modal-body{overflow-y:auto}.modal-fullscreen .modal-footer{border-radius:0}@media (max-width:575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-sm-down .modal-header{border-radius:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}.modal-fullscreen-sm-down .modal-footer{border-radius:0}}@media (max-width:767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-md-down .modal-header{border-radius:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}.modal-fullscreen-md-down .modal-footer{border-radius:0}}@media (max-width:991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-lg-down .modal-header{border-radius:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}.modal-fullscreen-lg-down .modal-footer{border-radius:0}}@media (max-width:1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xl-down .modal-header{border-radius:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}.modal-fullscreen-xl-down .modal-footer{border-radius:0}}@media (max-width:1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xxl-down .modal-header{border-radius:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}.modal-fullscreen-xxl-down .modal-footer{border-radius:0}}.tooltip{position:absolute;z-index:1080;display:block;margin:0;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .tooltip-arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .tooltip-arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[data-popper-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow,.bs-tooltip-top .tooltip-arrow{bottom:0}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before,.bs-tooltip-top .tooltip-arrow::before{top:-1px;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[data-popper-placement^=right],.bs-tooltip-end{padding:0 .4rem}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow,.bs-tooltip-end .tooltip-arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before,.bs-tooltip-end .tooltip-arrow::before{right:-1px;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[data-popper-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow,.bs-tooltip-bottom .tooltip-arrow{top:0}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before,.bs-tooltip-bottom .tooltip-arrow::before{bottom:-1px;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[data-popper-placement^=left],.bs-tooltip-start{padding:0 .4rem}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow,.bs-tooltip-start .tooltip-arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before,.bs-tooltip-start .tooltip-arrow::before{left:-1px;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1070;display:block;max-width:276px;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .popover-arrow{position:absolute;display:block;width:1rem;height:.5rem}.popover .popover-arrow::after,.popover .popover-arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow,.bs-popover-top>.popover-arrow{bottom:calc(-.5rem - 1px)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before,.bs-popover-top>.popover-arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after,.bs-popover-top>.popover-arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow,.bs-popover-end>.popover-arrow{left:calc(-.5rem - 1px);width:.5rem;height:1rem}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before,.bs-popover-end>.popover-arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after,.bs-popover-end>.popover-arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow,.bs-popover-bottom>.popover-arrow{top:calc(-.5rem - 1px)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before,.bs-popover-bottom>.popover-arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after,.bs-popover-bottom>.popover-arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[data-popper-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f0f0f0}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow,.bs-popover-start>.popover-arrow{right:calc(-.5rem - 1px);width:.5rem;height:1rem}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before,.bs-popover-start>.popover-arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after,.bs-popover-start>.popover-arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem 1rem;margin-bottom:0;font-size:1rem;background-color:#f0f0f0;border-bottom:1px solid rgba(0,0,0,.2);border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:1rem 1rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-end,.carousel-item-next:not(.carousel-item-start){transform:translateX(100%)}.active.carousel-item-start,.carousel-item-prev:not(.carousel-item-end){transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:flex;align-items:center;justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:0 0;border:0;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:2rem;height:2rem;background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%;list-style:none}.carousel-indicators [data-bs-target]{box-sizing:content-box;flex:0 1 auto;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border:0;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:#fff;text-align:center}.carousel-dark .carousel-control-next-icon,.carousel-dark .carousel-control-prev-icon{filter:invert(1) grayscale(100)}.carousel-dark .carousel-indicators [data-bs-target]{background-color:#000}.carousel-dark .carousel-caption{color:#000}@-webkit-keyframes spinner-border{to{transform:rotate(360deg)}}@keyframes spinner-border{to{transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;-webkit-animation:.75s linear infinite spinner-border;animation:.75s linear infinite spinner-border}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@-webkit-keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;background-color:currentColor;border-radius:50%;opacity:0;-webkit-animation:.75s linear infinite spinner-grow;animation:.75s linear infinite spinner-grow}.spinner-grow-sm{width:1rem;height:1rem}@media (prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{-webkit-animation-duration:1.5s;animation-duration:1.5s}}.offcanvas{position:fixed;bottom:0;z-index:1045;display:flex;flex-direction:column;max-width:100%;visibility:hidden;background-color:#fff;background-clip:padding-box;outline:0;transition:transform .3s ease-in-out}@media (prefers-reduced-motion:reduce){.offcanvas{transition:none}}.offcanvas-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.offcanvas-backdrop.fade{opacity:0}.offcanvas-backdrop.show{opacity:.5}.offcanvas-header{display:flex;align-items:center;justify-content:space-between;padding:1rem 1rem}.offcanvas-header .btn-close{padding:.5rem .5rem;margin-top:-.5rem;margin-right:-.5rem;margin-bottom:-.5rem}.offcanvas-title{margin-bottom:0;line-height:1.5}.offcanvas-body{flex-grow:1;padding:1rem 1rem;overflow-y:auto}.offcanvas-start{top:0;left:0;width:400px;border-right:1px solid rgba(0,0,0,.2);transform:translateX(-100%)}.offcanvas-end{top:0;right:0;width:400px;border-left:1px solid rgba(0,0,0,.2);transform:translateX(100%)}.offcanvas-top{top:0;right:0;left:0;height:30vh;max-height:100%;border-bottom:1px solid rgba(0,0,0,.2);transform:translateY(-100%)}.offcanvas-bottom{right:0;left:0;height:30vh;max-height:100%;border-top:1px solid rgba(0,0,0,.2);transform:translateY(100%)}.offcanvas.show{transform:none}.placeholder{display:inline-block;min-height:1em;vertical-align:middle;cursor:wait;background-color:currentColor;opacity:.5}.placeholder.btn::before{display:inline-block;content:""}.placeholder-xs{min-height:.6em}.placeholder-sm{min-height:.8em}.placeholder-lg{min-height:1.2em}.placeholder-glow .placeholder{-webkit-animation:placeholder-glow 2s ease-in-out infinite;animation:placeholder-glow 2s ease-in-out infinite}@-webkit-keyframes placeholder-glow{50%{opacity:.2}}@keyframes placeholder-glow{50%{opacity:.2}}.placeholder-wave{-webkit-mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,0.8) 75%,#000 95%);mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,0.8) 75%,#000 95%);-webkit-mask-size:200% 100%;mask-size:200% 100%;-webkit-animation:placeholder-wave 2s linear infinite;animation:placeholder-wave 2s linear infinite}@-webkit-keyframes placeholder-wave{100%{-webkit-mask-position:-200% 0%;mask-position:-200% 0%}}@keyframes placeholder-wave{100%{-webkit-mask-position:-200% 0%;mask-position:-200% 0%}}.clearfix::after{display:block;clear:both;content:""}.link-primary{color:#0d6efd}.link-primary:focus,.link-primary:hover{color:#0a58ca}.link-secondary{color:#6c757d}.link-secondary:focus,.link-secondary:hover{color:#565e64}.link-success{color:#198754}.link-success:focus,.link-success:hover{color:#146c43}.link-info{color:#0dcaf0}.link-info:focus,.link-info:hover{color:#3dd5f3}.link-warning{color:#ffc107}.link-warning:focus,.link-warning:hover{color:#ffcd39}.link-danger{color:#dc3545}.link-danger:focus,.link-danger:hover{color:#b02a37}.link-light{color:#f8f9fa}.link-light:focus,.link-light:hover{color:#f9fafb}.link-dark{color:#212529}.link-dark:focus,.link-dark:hover{color:#1a1e21}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio:100%}.ratio-4x3{--bs-aspect-ratio:75%}.ratio-16x9{--bs-aspect-ratio:56.25%}.ratio-21x9{--bs-aspect-ratio:42.8571428571%}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}@media (min-width:576px){.sticky-sm-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:768px){.sticky-md-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:992px){.sticky-lg-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:1200px){.sticky-xl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:1400px){.sticky-xxl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.hstack{display:flex;flex-direction:row;align-items:center;align-self:stretch}.vstack{display:flex;flex:1 1 auto;flex-direction:column;align-self:stretch}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.vr{display:inline-block;align-self:stretch;width:1px;min-height:1em;background-color:currentColor;opacity:.25}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.float-start{float:left!important}.float-end{float:right!important}.float-none{float:none!important}.opacity-0{opacity:0!important}.opacity-25{opacity:.25!important}.opacity-50{opacity:.5!important}.opacity-75{opacity:.75!important}.opacity-100{opacity:1!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.overflow-visible{overflow:visible!important}.overflow-scroll{overflow:scroll!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-grid{display:grid!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}.d-none{display:none!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.top-0{top:0!important}.top-50{top:50%!important}.top-100{top:100%!important}.bottom-0{bottom:0!important}.bottom-50{bottom:50%!important}.bottom-100{bottom:100%!important}.start-0{left:0!important}.start-50{left:50%!important}.start-100{left:100%!important}.end-0{right:0!important}.end-50{right:50%!important}.end-100{right:100%!important}.translate-middle{transform:translate(-50%,-50%)!important}.translate-middle-x{transform:translateX(-50%)!important}.translate-middle-y{transform:translateY(-50%)!important}.border{border:1px solid #dee2e6!important}.border-0{border:0!important}.border-top{border-top:1px solid #dee2e6!important}.border-top-0{border-top:0!important}.border-end{border-right:1px solid #dee2e6!important}.border-end-0{border-right:0!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-bottom-0{border-bottom:0!important}.border-start{border-left:1px solid #dee2e6!important}.border-start-0{border-left:0!important}.border-primary{border-color:#0d6efd!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#198754!important}.border-info{border-color:#0dcaf0!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#212529!important}.border-white{border-color:#fff!important}.border-1{border-width:1px!important}.border-2{border-width:2px!important}.border-3{border-width:3px!important}.border-4{border-width:4px!important}.border-5{border-width:5px!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.mw-100{max-width:100%!important}.vw-100{width:100vw!important}.min-vw-100{min-width:100vw!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mh-100{max-height:100%!important}.vh-100{height:100vh!important}.min-vh-100{min-height:100vh!important}.flex-fill{flex:1 1 auto!important}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-0{gap:0!important}.gap-1{gap:.25rem!important}.gap-2{gap:.5rem!important}.gap-3{gap:1rem!important}.gap-4{gap:1.5rem!important}.gap-5{gap:3rem!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.justify-content-evenly{justify-content:space-evenly!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}.order-first{order:-1!important}.order-0{order:0!important}.order-1{order:1!important}.order-2{order:2!important}.order-3{order:3!important}.order-4{order:4!important}.order-5{order:5!important}.order-last{order:6!important}.m-0{margin:0!important}.m-1{margin:.25rem!important}.m-2{margin:.5rem!important}.m-3{margin:1rem!important}.m-4{margin:1.5rem!important}.m-5{margin:3rem!important}.m-auto{margin:auto!important}.mx-0{margin-right:0!important;margin-left:0!important}.mx-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-3{margin-right:1rem!important;margin-left:1rem!important}.mx-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-5{margin-right:3rem!important;margin-left:3rem!important}.mx-auto{margin-right:auto!important;margin-left:auto!important}.my-0{margin-top:0!important;margin-bottom:0!important}.my-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-0{margin-top:0!important}.mt-1{margin-top:.25rem!important}.mt-2{margin-top:.5rem!important}.mt-3{margin-top:1rem!important}.mt-4{margin-top:1.5rem!important}.mt-5{margin-top:3rem!important}.mt-auto{margin-top:auto!important}.me-0{margin-right:0!important}.me-1{margin-right:.25rem!important}.me-2{margin-right:.5rem!important}.me-3{margin-right:1rem!important}.me-4{margin-right:1.5rem!important}.me-5{margin-right:3rem!important}.me-auto{margin-right:auto!important}.mb-0{margin-bottom:0!important}.mb-1{margin-bottom:.25rem!important}.mb-2{margin-bottom:.5rem!important}.mb-3{margin-bottom:1rem!important}.mb-4{margin-bottom:1.5rem!important}.mb-5{margin-bottom:3rem!important}.mb-auto{margin-bottom:auto!important}.ms-0{margin-left:0!important}.ms-1{margin-left:.25rem!important}.ms-2{margin-left:.5rem!important}.ms-3{margin-left:1rem!important}.ms-4{margin-left:1.5rem!important}.ms-5{margin-left:3rem!important}.ms-auto{margin-left:auto!important}.p-0{padding:0!important}.p-1{padding:.25rem!important}.p-2{padding:.5rem!important}.p-3{padding:1rem!important}.p-4{padding:1.5rem!important}.p-5{padding:3rem!important}.px-0{padding-right:0!important;padding-left:0!important}.px-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-3{padding-right:1rem!important;padding-left:1rem!important}.px-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-5{padding-right:3rem!important;padding-left:3rem!important}.py-0{padding-top:0!important;padding-bottom:0!important}.py-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-0{padding-top:0!important}.pt-1{padding-top:.25rem!important}.pt-2{padding-top:.5rem!important}.pt-3{padding-top:1rem!important}.pt-4{padding-top:1.5rem!important}.pt-5{padding-top:3rem!important}.pe-0{padding-right:0!important}.pe-1{padding-right:.25rem!important}.pe-2{padding-right:.5rem!important}.pe-3{padding-right:1rem!important}.pe-4{padding-right:1.5rem!important}.pe-5{padding-right:3rem!important}.pb-0{padding-bottom:0!important}.pb-1{padding-bottom:.25rem!important}.pb-2{padding-bottom:.5rem!important}.pb-3{padding-bottom:1rem!important}.pb-4{padding-bottom:1.5rem!important}.pb-5{padding-bottom:3rem!important}.ps-0{padding-left:0!important}.ps-1{padding-left:.25rem!important}.ps-2{padding-left:.5rem!important}.ps-3{padding-left:1rem!important}.ps-4{padding-left:1.5rem!important}.ps-5{padding-left:3rem!important}.font-monospace{font-family:var(--bs-font-monospace)!important}.fs-1{font-size:calc(1.375rem + 1.5vw)!important}.fs-2{font-size:calc(1.325rem + .9vw)!important}.fs-3{font-size:calc(1.3rem + .6vw)!important}.fs-4{font-size:calc(1.275rem + .3vw)!important}.fs-5{font-size:1.25rem!important}.fs-6{font-size:1rem!important}.fst-italic{font-style:italic!important}.fst-normal{font-style:normal!important}.fw-light{font-weight:300!important}.fw-lighter{font-weight:lighter!important}.fw-normal{font-weight:400!important}.fw-bold{font-weight:700!important}.fw-bolder{font-weight:bolder!important}.lh-1{line-height:1!important}.lh-sm{line-height:1.25!important}.lh-base{line-height:1.5!important}.lh-lg{line-height:2!important}.text-start{text-align:left!important}.text-end{text-align:right!important}.text-center{text-align:center!important}.text-decoration-none{text-decoration:none!important}.text-decoration-underline{text-decoration:underline!important}.text-decoration-line-through{text-decoration:line-through!important}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-break{word-wrap:break-word!important;word-break:break-word!important}.text-primary{--bs-text-opacity:1;color:rgba(var(--bs-primary-rgb),var(--bs-text-opacity))!important}.text-secondary{--bs-text-opacity:1;color:rgba(var(--bs-secondary-rgb),var(--bs-text-opacity))!important}.text-success{--bs-text-opacity:1;color:rgba(var(--bs-success-rgb),var(--bs-text-opacity))!important}.text-info{--bs-text-opacity:1;color:rgba(var(--bs-info-rgb),var(--bs-text-opacity))!important}.text-warning{--bs-text-opacity:1;color:rgba(var(--bs-warning-rgb),var(--bs-text-opacity))!important}.text-danger{--bs-text-opacity:1;color:rgba(var(--bs-danger-rgb),var(--bs-text-opacity))!important}.text-light{--bs-text-opacity:1;color:rgba(var(--bs-light-rgb),var(--bs-text-opacity))!important}.text-dark{--bs-text-opacity:1;color:rgba(var(--bs-dark-rgb),var(--bs-text-opacity))!important}.text-black{--bs-text-opacity:1;color:rgba(var(--bs-black-rgb),var(--bs-text-opacity))!important}.text-white{--bs-text-opacity:1;color:rgba(var(--bs-white-rgb),var(--bs-text-opacity))!important}.text-body{--bs-text-opacity:1;color:rgba(var(--bs-body-color-rgb),var(--bs-text-opacity))!important}.text-muted{--bs-text-opacity:1;color:#6c757d!important}.text-black-50{--bs-text-opacity:1;color:rgba(0,0,0,.5)!important}.text-white-50{--bs-text-opacity:1;color:rgba(255,255,255,.5)!important}.text-reset{--bs-text-opacity:1;color:inherit!important}.text-opacity-25{--bs-text-opacity:0.25}.text-opacity-50{--bs-text-opacity:0.5}.text-opacity-75{--bs-text-opacity:0.75}.text-opacity-100{--bs-text-opacity:1}.bg-primary{--bs-bg-opacity:1;background-color:rgba(var(--bs-primary-rgb),var(--bs-bg-opacity))!important}.bg-secondary{--bs-bg-opacity:1;background-color:rgba(var(--bs-secondary-rgb),var(--bs-bg-opacity))!important}.bg-success{--bs-bg-opacity:1;background-color:rgba(var(--bs-success-rgb),var(--bs-bg-opacity))!important}.bg-info{--bs-bg-opacity:1;background-color:rgba(var(--bs-info-rgb),var(--bs-bg-opacity))!important}.bg-warning{--bs-bg-opacity:1;background-color:rgba(var(--bs-warning-rgb),var(--bs-bg-opacity))!important}.bg-danger{--bs-bg-opacity:1;background-color:rgba(var(--bs-danger-rgb),var(--bs-bg-opacity))!important}.bg-light{--bs-bg-opacity:1;background-color:rgba(var(--bs-light-rgb),var(--bs-bg-opacity))!important}.bg-dark{--bs-bg-opacity:1;background-color:rgba(var(--bs-dark-rgb),var(--bs-bg-opacity))!important}.bg-black{--bs-bg-opacity:1;background-color:rgba(var(--bs-black-rgb),var(--bs-bg-opacity))!important}.bg-white{--bs-bg-opacity:1;background-color:rgba(var(--bs-white-rgb),var(--bs-bg-opacity))!important}.bg-body{--bs-bg-opacity:1;background-color:rgba(var(--bs-body-bg-rgb),var(--bs-bg-opacity))!important}.bg-transparent{--bs-bg-opacity:1;background-color:transparent!important}.bg-opacity-10{--bs-bg-opacity:0.1}.bg-opacity-25{--bs-bg-opacity:0.25}.bg-opacity-50{--bs-bg-opacity:0.5}.bg-opacity-75{--bs-bg-opacity:0.75}.bg-opacity-100{--bs-bg-opacity:1}.bg-gradient{background-image:var(--bs-gradient)!important}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;user-select:none!important}.pe-none{pointer-events:none!important}.pe-auto{pointer-events:auto!important}.rounded{border-radius:.25rem!important}.rounded-0{border-radius:0!important}.rounded-1{border-radius:.2rem!important}.rounded-2{border-radius:.25rem!important}.rounded-3{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-end{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-start{border-bottom-left-radius:.25rem!important;border-top-left-radius:.25rem!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media (min-width:576px){.float-sm-start{float:left!important}.float-sm-end{float:right!important}.float-sm-none{float:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-grid{display:grid!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}.d-sm-none{display:none!important}.flex-sm-fill{flex:1 1 auto!important}.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-sm-0{gap:0!important}.gap-sm-1{gap:.25rem!important}.gap-sm-2{gap:.5rem!important}.gap-sm-3{gap:1rem!important}.gap-sm-4{gap:1.5rem!important}.gap-sm-5{gap:3rem!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.justify-content-sm-evenly{justify-content:space-evenly!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}.order-sm-first{order:-1!important}.order-sm-0{order:0!important}.order-sm-1{order:1!important}.order-sm-2{order:2!important}.order-sm-3{order:3!important}.order-sm-4{order:4!important}.order-sm-5{order:5!important}.order-sm-last{order:6!important}.m-sm-0{margin:0!important}.m-sm-1{margin:.25rem!important}.m-sm-2{margin:.5rem!important}.m-sm-3{margin:1rem!important}.m-sm-4{margin:1.5rem!important}.m-sm-5{margin:3rem!important}.m-sm-auto{margin:auto!important}.mx-sm-0{margin-right:0!important;margin-left:0!important}.mx-sm-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-sm-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-sm-3{margin-right:1rem!important;margin-left:1rem!important}.mx-sm-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-sm-5{margin-right:3rem!important;margin-left:3rem!important}.mx-sm-auto{margin-right:auto!important;margin-left:auto!important}.my-sm-0{margin-top:0!important;margin-bottom:0!important}.my-sm-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-sm-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-sm-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-sm-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-sm-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-sm-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-sm-0{margin-top:0!important}.mt-sm-1{margin-top:.25rem!important}.mt-sm-2{margin-top:.5rem!important}.mt-sm-3{margin-top:1rem!important}.mt-sm-4{margin-top:1.5rem!important}.mt-sm-5{margin-top:3rem!important}.mt-sm-auto{margin-top:auto!important}.me-sm-0{margin-right:0!important}.me-sm-1{margin-right:.25rem!important}.me-sm-2{margin-right:.5rem!important}.me-sm-3{margin-right:1rem!important}.me-sm-4{margin-right:1.5rem!important}.me-sm-5{margin-right:3rem!important}.me-sm-auto{margin-right:auto!important}.mb-sm-0{margin-bottom:0!important}.mb-sm-1{margin-bottom:.25rem!important}.mb-sm-2{margin-bottom:.5rem!important}.mb-sm-3{margin-bottom:1rem!important}.mb-sm-4{margin-bottom:1.5rem!important}.mb-sm-5{margin-bottom:3rem!important}.mb-sm-auto{margin-bottom:auto!important}.ms-sm-0{margin-left:0!important}.ms-sm-1{margin-left:.25rem!important}.ms-sm-2{margin-left:.5rem!important}.ms-sm-3{margin-left:1rem!important}.ms-sm-4{margin-left:1.5rem!important}.ms-sm-5{margin-left:3rem!important}.ms-sm-auto{margin-left:auto!important}.p-sm-0{padding:0!important}.p-sm-1{padding:.25rem!important}.p-sm-2{padding:.5rem!important}.p-sm-3{padding:1rem!important}.p-sm-4{padding:1.5rem!important}.p-sm-5{padding:3rem!important}.px-sm-0{padding-right:0!important;padding-left:0!important}.px-sm-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-sm-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-sm-3{padding-right:1rem!important;padding-left:1rem!important}.px-sm-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-sm-5{padding-right:3rem!important;padding-left:3rem!important}.py-sm-0{padding-top:0!important;padding-bottom:0!important}.py-sm-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-sm-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-sm-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-sm-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-sm-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-sm-0{padding-top:0!important}.pt-sm-1{padding-top:.25rem!important}.pt-sm-2{padding-top:.5rem!important}.pt-sm-3{padding-top:1rem!important}.pt-sm-4{padding-top:1.5rem!important}.pt-sm-5{padding-top:3rem!important}.pe-sm-0{padding-right:0!important}.pe-sm-1{padding-right:.25rem!important}.pe-sm-2{padding-right:.5rem!important}.pe-sm-3{padding-right:1rem!important}.pe-sm-4{padding-right:1.5rem!important}.pe-sm-5{padding-right:3rem!important}.pb-sm-0{padding-bottom:0!important}.pb-sm-1{padding-bottom:.25rem!important}.pb-sm-2{padding-bottom:.5rem!important}.pb-sm-3{padding-bottom:1rem!important}.pb-sm-4{padding-bottom:1.5rem!important}.pb-sm-5{padding-bottom:3rem!important}.ps-sm-0{padding-left:0!important}.ps-sm-1{padding-left:.25rem!important}.ps-sm-2{padding-left:.5rem!important}.ps-sm-3{padding-left:1rem!important}.ps-sm-4{padding-left:1.5rem!important}.ps-sm-5{padding-left:3rem!important}.text-sm-start{text-align:left!important}.text-sm-end{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.float-md-start{float:left!important}.float-md-end{float:right!important}.float-md-none{float:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-grid{display:grid!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}.d-md-none{display:none!important}.flex-md-fill{flex:1 1 auto!important}.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-md-0{gap:0!important}.gap-md-1{gap:.25rem!important}.gap-md-2{gap:.5rem!important}.gap-md-3{gap:1rem!important}.gap-md-4{gap:1.5rem!important}.gap-md-5{gap:3rem!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.justify-content-md-evenly{justify-content:space-evenly!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}.order-md-first{order:-1!important}.order-md-0{order:0!important}.order-md-1{order:1!important}.order-md-2{order:2!important}.order-md-3{order:3!important}.order-md-4{order:4!important}.order-md-5{order:5!important}.order-md-last{order:6!important}.m-md-0{margin:0!important}.m-md-1{margin:.25rem!important}.m-md-2{margin:.5rem!important}.m-md-3{margin:1rem!important}.m-md-4{margin:1.5rem!important}.m-md-5{margin:3rem!important}.m-md-auto{margin:auto!important}.mx-md-0{margin-right:0!important;margin-left:0!important}.mx-md-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-md-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-md-3{margin-right:1rem!important;margin-left:1rem!important}.mx-md-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-md-5{margin-right:3rem!important;margin-left:3rem!important}.mx-md-auto{margin-right:auto!important;margin-left:auto!important}.my-md-0{margin-top:0!important;margin-bottom:0!important}.my-md-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-md-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-md-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-md-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-md-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-md-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-md-0{margin-top:0!important}.mt-md-1{margin-top:.25rem!important}.mt-md-2{margin-top:.5rem!important}.mt-md-3{margin-top:1rem!important}.mt-md-4{margin-top:1.5rem!important}.mt-md-5{margin-top:3rem!important}.mt-md-auto{margin-top:auto!important}.me-md-0{margin-right:0!important}.me-md-1{margin-right:.25rem!important}.me-md-2{margin-right:.5rem!important}.me-md-3{margin-right:1rem!important}.me-md-4{margin-right:1.5rem!important}.me-md-5{margin-right:3rem!important}.me-md-auto{margin-right:auto!important}.mb-md-0{margin-bottom:0!important}.mb-md-1{margin-bottom:.25rem!important}.mb-md-2{margin-bottom:.5rem!important}.mb-md-3{margin-bottom:1rem!important}.mb-md-4{margin-bottom:1.5rem!important}.mb-md-5{margin-bottom:3rem!important}.mb-md-auto{margin-bottom:auto!important}.ms-md-0{margin-left:0!important}.ms-md-1{margin-left:.25rem!important}.ms-md-2{margin-left:.5rem!important}.ms-md-3{margin-left:1rem!important}.ms-md-4{margin-left:1.5rem!important}.ms-md-5{margin-left:3rem!important}.ms-md-auto{margin-left:auto!important}.p-md-0{padding:0!important}.p-md-1{padding:.25rem!important}.p-md-2{padding:.5rem!important}.p-md-3{padding:1rem!important}.p-md-4{padding:1.5rem!important}.p-md-5{padding:3rem!important}.px-md-0{padding-right:0!important;padding-left:0!important}.px-md-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-md-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-md-3{padding-right:1rem!important;padding-left:1rem!important}.px-md-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-md-5{padding-right:3rem!important;padding-left:3rem!important}.py-md-0{padding-top:0!important;padding-bottom:0!important}.py-md-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-md-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-md-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-md-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-md-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-md-0{padding-top:0!important}.pt-md-1{padding-top:.25rem!important}.pt-md-2{padding-top:.5rem!important}.pt-md-3{padding-top:1rem!important}.pt-md-4{padding-top:1.5rem!important}.pt-md-5{padding-top:3rem!important}.pe-md-0{padding-right:0!important}.pe-md-1{padding-right:.25rem!important}.pe-md-2{padding-right:.5rem!important}.pe-md-3{padding-right:1rem!important}.pe-md-4{padding-right:1.5rem!important}.pe-md-5{padding-right:3rem!important}.pb-md-0{padding-bottom:0!important}.pb-md-1{padding-bottom:.25rem!important}.pb-md-2{padding-bottom:.5rem!important}.pb-md-3{padding-bottom:1rem!important}.pb-md-4{padding-bottom:1.5rem!important}.pb-md-5{padding-bottom:3rem!important}.ps-md-0{padding-left:0!important}.ps-md-1{padding-left:.25rem!important}.ps-md-2{padding-left:.5rem!important}.ps-md-3{padding-left:1rem!important}.ps-md-4{padding-left:1.5rem!important}.ps-md-5{padding-left:3rem!important}.text-md-start{text-align:left!important}.text-md-end{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.float-lg-start{float:left!important}.float-lg-end{float:right!important}.float-lg-none{float:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-grid{display:grid!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}.d-lg-none{display:none!important}.flex-lg-fill{flex:1 1 auto!important}.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-lg-0{gap:0!important}.gap-lg-1{gap:.25rem!important}.gap-lg-2{gap:.5rem!important}.gap-lg-3{gap:1rem!important}.gap-lg-4{gap:1.5rem!important}.gap-lg-5{gap:3rem!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.justify-content-lg-evenly{justify-content:space-evenly!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}.order-lg-first{order:-1!important}.order-lg-0{order:0!important}.order-lg-1{order:1!important}.order-lg-2{order:2!important}.order-lg-3{order:3!important}.order-lg-4{order:4!important}.order-lg-5{order:5!important}.order-lg-last{order:6!important}.m-lg-0{margin:0!important}.m-lg-1{margin:.25rem!important}.m-lg-2{margin:.5rem!important}.m-lg-3{margin:1rem!important}.m-lg-4{margin:1.5rem!important}.m-lg-5{margin:3rem!important}.m-lg-auto{margin:auto!important}.mx-lg-0{margin-right:0!important;margin-left:0!important}.mx-lg-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-lg-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-lg-3{margin-right:1rem!important;margin-left:1rem!important}.mx-lg-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-lg-5{margin-right:3rem!important;margin-left:3rem!important}.mx-lg-auto{margin-right:auto!important;margin-left:auto!important}.my-lg-0{margin-top:0!important;margin-bottom:0!important}.my-lg-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-lg-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-lg-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-lg-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-lg-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-lg-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-lg-0{margin-top:0!important}.mt-lg-1{margin-top:.25rem!important}.mt-lg-2{margin-top:.5rem!important}.mt-lg-3{margin-top:1rem!important}.mt-lg-4{margin-top:1.5rem!important}.mt-lg-5{margin-top:3rem!important}.mt-lg-auto{margin-top:auto!important}.me-lg-0{margin-right:0!important}.me-lg-1{margin-right:.25rem!important}.me-lg-2{margin-right:.5rem!important}.me-lg-3{margin-right:1rem!important}.me-lg-4{margin-right:1.5rem!important}.me-lg-5{margin-right:3rem!important}.me-lg-auto{margin-right:auto!important}.mb-lg-0{margin-bottom:0!important}.mb-lg-1{margin-bottom:.25rem!important}.mb-lg-2{margin-bottom:.5rem!important}.mb-lg-3{margin-bottom:1rem!important}.mb-lg-4{margin-bottom:1.5rem!important}.mb-lg-5{margin-bottom:3rem!important}.mb-lg-auto{margin-bottom:auto!important}.ms-lg-0{margin-left:0!important}.ms-lg-1{margin-left:.25rem!important}.ms-lg-2{margin-left:.5rem!important}.ms-lg-3{margin-left:1rem!important}.ms-lg-4{margin-left:1.5rem!important}.ms-lg-5{margin-left:3rem!important}.ms-lg-auto{margin-left:auto!important}.p-lg-0{padding:0!important}.p-lg-1{padding:.25rem!important}.p-lg-2{padding:.5rem!important}.p-lg-3{padding:1rem!important}.p-lg-4{padding:1.5rem!important}.p-lg-5{padding:3rem!important}.px-lg-0{padding-right:0!important;padding-left:0!important}.px-lg-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-lg-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-lg-3{padding-right:1rem!important;padding-left:1rem!important}.px-lg-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-lg-5{padding-right:3rem!important;padding-left:3rem!important}.py-lg-0{padding-top:0!important;padding-bottom:0!important}.py-lg-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-lg-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-lg-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-lg-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-lg-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-lg-0{padding-top:0!important}.pt-lg-1{padding-top:.25rem!important}.pt-lg-2{padding-top:.5rem!important}.pt-lg-3{padding-top:1rem!important}.pt-lg-4{padding-top:1.5rem!important}.pt-lg-5{padding-top:3rem!important}.pe-lg-0{padding-right:0!important}.pe-lg-1{padding-right:.25rem!important}.pe-lg-2{padding-right:.5rem!important}.pe-lg-3{padding-right:1rem!important}.pe-lg-4{padding-right:1.5rem!important}.pe-lg-5{padding-right:3rem!important}.pb-lg-0{padding-bottom:0!important}.pb-lg-1{padding-bottom:.25rem!important}.pb-lg-2{padding-bottom:.5rem!important}.pb-lg-3{padding-bottom:1rem!important}.pb-lg-4{padding-bottom:1.5rem!important}.pb-lg-5{padding-bottom:3rem!important}.ps-lg-0{padding-left:0!important}.ps-lg-1{padding-left:.25rem!important}.ps-lg-2{padding-left:.5rem!important}.ps-lg-3{padding-left:1rem!important}.ps-lg-4{padding-left:1.5rem!important}.ps-lg-5{padding-left:3rem!important}.text-lg-start{text-align:left!important}.text-lg-end{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.float-xl-start{float:left!important}.float-xl-end{float:right!important}.float-xl-none{float:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-grid{display:grid!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}.d-xl-none{display:none!important}.flex-xl-fill{flex:1 1 auto!important}.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-xl-0{gap:0!important}.gap-xl-1{gap:.25rem!important}.gap-xl-2{gap:.5rem!important}.gap-xl-3{gap:1rem!important}.gap-xl-4{gap:1.5rem!important}.gap-xl-5{gap:3rem!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.justify-content-xl-evenly{justify-content:space-evenly!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}.order-xl-first{order:-1!important}.order-xl-0{order:0!important}.order-xl-1{order:1!important}.order-xl-2{order:2!important}.order-xl-3{order:3!important}.order-xl-4{order:4!important}.order-xl-5{order:5!important}.order-xl-last{order:6!important}.m-xl-0{margin:0!important}.m-xl-1{margin:.25rem!important}.m-xl-2{margin:.5rem!important}.m-xl-3{margin:1rem!important}.m-xl-4{margin:1.5rem!important}.m-xl-5{margin:3rem!important}.m-xl-auto{margin:auto!important}.mx-xl-0{margin-right:0!important;margin-left:0!important}.mx-xl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xl-auto{margin-right:auto!important;margin-left:auto!important}.my-xl-0{margin-top:0!important;margin-bottom:0!important}.my-xl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xl-0{margin-top:0!important}.mt-xl-1{margin-top:.25rem!important}.mt-xl-2{margin-top:.5rem!important}.mt-xl-3{margin-top:1rem!important}.mt-xl-4{margin-top:1.5rem!important}.mt-xl-5{margin-top:3rem!important}.mt-xl-auto{margin-top:auto!important}.me-xl-0{margin-right:0!important}.me-xl-1{margin-right:.25rem!important}.me-xl-2{margin-right:.5rem!important}.me-xl-3{margin-right:1rem!important}.me-xl-4{margin-right:1.5rem!important}.me-xl-5{margin-right:3rem!important}.me-xl-auto{margin-right:auto!important}.mb-xl-0{margin-bottom:0!important}.mb-xl-1{margin-bottom:.25rem!important}.mb-xl-2{margin-bottom:.5rem!important}.mb-xl-3{margin-bottom:1rem!important}.mb-xl-4{margin-bottom:1.5rem!important}.mb-xl-5{margin-bottom:3rem!important}.mb-xl-auto{margin-bottom:auto!important}.ms-xl-0{margin-left:0!important}.ms-xl-1{margin-left:.25rem!important}.ms-xl-2{margin-left:.5rem!important}.ms-xl-3{margin-left:1rem!important}.ms-xl-4{margin-left:1.5rem!important}.ms-xl-5{margin-left:3rem!important}.ms-xl-auto{margin-left:auto!important}.p-xl-0{padding:0!important}.p-xl-1{padding:.25rem!important}.p-xl-2{padding:.5rem!important}.p-xl-3{padding:1rem!important}.p-xl-4{padding:1.5rem!important}.p-xl-5{padding:3rem!important}.px-xl-0{padding-right:0!important;padding-left:0!important}.px-xl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xl-0{padding-top:0!important;padding-bottom:0!important}.py-xl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xl-0{padding-top:0!important}.pt-xl-1{padding-top:.25rem!important}.pt-xl-2{padding-top:.5rem!important}.pt-xl-3{padding-top:1rem!important}.pt-xl-4{padding-top:1.5rem!important}.pt-xl-5{padding-top:3rem!important}.pe-xl-0{padding-right:0!important}.pe-xl-1{padding-right:.25rem!important}.pe-xl-2{padding-right:.5rem!important}.pe-xl-3{padding-right:1rem!important}.pe-xl-4{padding-right:1.5rem!important}.pe-xl-5{padding-right:3rem!important}.pb-xl-0{padding-bottom:0!important}.pb-xl-1{padding-bottom:.25rem!important}.pb-xl-2{padding-bottom:.5rem!important}.pb-xl-3{padding-bottom:1rem!important}.pb-xl-4{padding-bottom:1.5rem!important}.pb-xl-5{padding-bottom:3rem!important}.ps-xl-0{padding-left:0!important}.ps-xl-1{padding-left:.25rem!important}.ps-xl-2{padding-left:.5rem!important}.ps-xl-3{padding-left:1rem!important}.ps-xl-4{padding-left:1.5rem!important}.ps-xl-5{padding-left:3rem!important}.text-xl-start{text-align:left!important}.text-xl-end{text-align:right!important}.text-xl-center{text-align:center!important}}@media (min-width:1400px){.float-xxl-start{float:left!important}.float-xxl-end{float:right!important}.float-xxl-none{float:none!important}.d-xxl-inline{display:inline!important}.d-xxl-inline-block{display:inline-block!important}.d-xxl-block{display:block!important}.d-xxl-grid{display:grid!important}.d-xxl-table{display:table!important}.d-xxl-table-row{display:table-row!important}.d-xxl-table-cell{display:table-cell!important}.d-xxl-flex{display:flex!important}.d-xxl-inline-flex{display:inline-flex!important}.d-xxl-none{display:none!important}.flex-xxl-fill{flex:1 1 auto!important}.flex-xxl-row{flex-direction:row!important}.flex-xxl-column{flex-direction:column!important}.flex-xxl-row-reverse{flex-direction:row-reverse!important}.flex-xxl-column-reverse{flex-direction:column-reverse!important}.flex-xxl-grow-0{flex-grow:0!important}.flex-xxl-grow-1{flex-grow:1!important}.flex-xxl-shrink-0{flex-shrink:0!important}.flex-xxl-shrink-1{flex-shrink:1!important}.flex-xxl-wrap{flex-wrap:wrap!important}.flex-xxl-nowrap{flex-wrap:nowrap!important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-xxl-0{gap:0!important}.gap-xxl-1{gap:.25rem!important}.gap-xxl-2{gap:.5rem!important}.gap-xxl-3{gap:1rem!important}.gap-xxl-4{gap:1.5rem!important}.gap-xxl-5{gap:3rem!important}.justify-content-xxl-start{justify-content:flex-start!important}.justify-content-xxl-end{justify-content:flex-end!important}.justify-content-xxl-center{justify-content:center!important}.justify-content-xxl-between{justify-content:space-between!important}.justify-content-xxl-around{justify-content:space-around!important}.justify-content-xxl-evenly{justify-content:space-evenly!important}.align-items-xxl-start{align-items:flex-start!important}.align-items-xxl-end{align-items:flex-end!important}.align-items-xxl-center{align-items:center!important}.align-items-xxl-baseline{align-items:baseline!important}.align-items-xxl-stretch{align-items:stretch!important}.align-content-xxl-start{align-content:flex-start!important}.align-content-xxl-end{align-content:flex-end!important}.align-content-xxl-center{align-content:center!important}.align-content-xxl-between{align-content:space-between!important}.align-content-xxl-around{align-content:space-around!important}.align-content-xxl-stretch{align-content:stretch!important}.align-self-xxl-auto{align-self:auto!important}.align-self-xxl-start{align-self:flex-start!important}.align-self-xxl-end{align-self:flex-end!important}.align-self-xxl-center{align-self:center!important}.align-self-xxl-baseline{align-self:baseline!important}.align-self-xxl-stretch{align-self:stretch!important}.order-xxl-first{order:-1!important}.order-xxl-0{order:0!important}.order-xxl-1{order:1!important}.order-xxl-2{order:2!important}.order-xxl-3{order:3!important}.order-xxl-4{order:4!important}.order-xxl-5{order:5!important}.order-xxl-last{order:6!important}.m-xxl-0{margin:0!important}.m-xxl-1{margin:.25rem!important}.m-xxl-2{margin:.5rem!important}.m-xxl-3{margin:1rem!important}.m-xxl-4{margin:1.5rem!important}.m-xxl-5{margin:3rem!important}.m-xxl-auto{margin:auto!important}.mx-xxl-0{margin-right:0!important;margin-left:0!important}.mx-xxl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xxl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xxl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xxl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xxl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xxl-auto{margin-right:auto!important;margin-left:auto!important}.my-xxl-0{margin-top:0!important;margin-bottom:0!important}.my-xxl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xxl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xxl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xxl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xxl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xxl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xxl-0{margin-top:0!important}.mt-xxl-1{margin-top:.25rem!important}.mt-xxl-2{margin-top:.5rem!important}.mt-xxl-3{margin-top:1rem!important}.mt-xxl-4{margin-top:1.5rem!important}.mt-xxl-5{margin-top:3rem!important}.mt-xxl-auto{margin-top:auto!important}.me-xxl-0{margin-right:0!important}.me-xxl-1{margin-right:.25rem!important}.me-xxl-2{margin-right:.5rem!important}.me-xxl-3{margin-right:1rem!important}.me-xxl-4{margin-right:1.5rem!important}.me-xxl-5{margin-right:3rem!important}.me-xxl-auto{margin-right:auto!important}.mb-xxl-0{margin-bottom:0!important}.mb-xxl-1{margin-bottom:.25rem!important}.mb-xxl-2{margin-bottom:.5rem!important}.mb-xxl-3{margin-bottom:1rem!important}.mb-xxl-4{margin-bottom:1.5rem!important}.mb-xxl-5{margin-bottom:3rem!important}.mb-xxl-auto{margin-bottom:auto!important}.ms-xxl-0{margin-left:0!important}.ms-xxl-1{margin-left:.25rem!important}.ms-xxl-2{margin-left:.5rem!important}.ms-xxl-3{margin-left:1rem!important}.ms-xxl-4{margin-left:1.5rem!important}.ms-xxl-5{margin-left:3rem!important}.ms-xxl-auto{margin-left:auto!important}.p-xxl-0{padding:0!important}.p-xxl-1{padding:.25rem!important}.p-xxl-2{padding:.5rem!important}.p-xxl-3{padding:1rem!important}.p-xxl-4{padding:1.5rem!important}.p-xxl-5{padding:3rem!important}.px-xxl-0{padding-right:0!important;padding-left:0!important}.px-xxl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xxl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xxl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xxl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xxl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xxl-0{padding-top:0!important;padding-bottom:0!important}.py-xxl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xxl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xxl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xxl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xxl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xxl-0{padding-top:0!important}.pt-xxl-1{padding-top:.25rem!important}.pt-xxl-2{padding-top:.5rem!important}.pt-xxl-3{padding-top:1rem!important}.pt-xxl-4{padding-top:1.5rem!important}.pt-xxl-5{padding-top:3rem!important}.pe-xxl-0{padding-right:0!important}.pe-xxl-1{padding-right:.25rem!important}.pe-xxl-2{padding-right:.5rem!important}.pe-xxl-3{padding-right:1rem!important}.pe-xxl-4{padding-right:1.5rem!important}.pe-xxl-5{padding-right:3rem!important}.pb-xxl-0{padding-bottom:0!important}.pb-xxl-1{padding-bottom:.25rem!important}.pb-xxl-2{padding-bottom:.5rem!important}.pb-xxl-3{padding-bottom:1rem!important}.pb-xxl-4{padding-bottom:1.5rem!important}.pb-xxl-5{padding-bottom:3rem!important}.ps-xxl-0{padding-left:0!important}.ps-xxl-1{padding-left:.25rem!important}.ps-xxl-2{padding-left:.5rem!important}.ps-xxl-3{padding-left:1rem!important}.ps-xxl-4{padding-left:1.5rem!important}.ps-xxl-5{padding-left:3rem!important}.text-xxl-start{text-align:left!important}.text-xxl-end{text-align:right!important}.text-xxl-center{text-align:center!important}}@media (min-width:1200px){.fs-1{font-size:2.5rem!important}.fs-2{font-size:2rem!important}.fs-3{font-size:1.75rem!important}.fs-4{font-size:1.5rem!important}}@media print{.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-grid{display:grid!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}.d-print-none{display:none!important}}
diff --git a/themes/30coffe/assets/css/boxicons.min.css b/themes/30coffe/assets/css/boxicons.min.css
new file mode 100644
index 00000000..d0428ef9
--- /dev/null
+++ b/themes/30coffe/assets/css/boxicons.min.css
@@ -0,0 +1 @@
+@font-face{font-family:boxicons;font-weight:400;font-style:normal;src:url(../fonts/boxicons.eot);src:url(../fonts/boxicons.eot) format('embedded-opentype'),url(../fonts/boxicons.woff2) format('woff2'),url(../fonts/boxicons.woff) format('woff'),url(../fonts/boxicons.ttf) format('truetype'),url(../fonts/boxiconsd41d.svg?#boxicons) format('svg')}.bx{font-family:boxicons!important;font-weight:400;font-style:normal;font-variant:normal;line-height:1;text-rendering:auto;display:inline-block;text-transform:none;speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.bx-ul{margin-left:2em;padding-left:0;list-style:none}.bx-ul>li{position:relative}.bx-ul .bx{font-size:inherit;line-height:inherit;position:absolute;left:-2em;width:2em;text-align:center}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes spin{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@-webkit-keyframes burst{0%{-webkit-transform:scale(1);transform:scale(1);opacity:1}90%{-webkit-transform:scale(1.5);transform:scale(1.5);opacity:0}}@keyframes burst{0%{-webkit-transform:scale(1);transform:scale(1);opacity:1}90%{-webkit-transform:scale(1.5);transform:scale(1.5);opacity:0}}@-webkit-keyframes flashing{0%{opacity:1}45%{opacity:0}90%{opacity:1}}@keyframes flashing{0%{opacity:1}45%{opacity:0}90%{opacity:1}}@-webkit-keyframes fade-left{0%{-webkit-transform:translateX(0);transform:translateX(0);opacity:1}75%{-webkit-transform:translateX(-20px);transform:translateX(-20px);opacity:0}}@keyframes fade-left{0%{-webkit-transform:translateX(0);transform:translateX(0);opacity:1}75%{-webkit-transform:translateX(-20px);transform:translateX(-20px);opacity:0}}@-webkit-keyframes fade-right{0%{-webkit-transform:translateX(0);transform:translateX(0);opacity:1}75%{-webkit-transform:translateX(20px);transform:translateX(20px);opacity:0}}@keyframes fade-right{0%{-webkit-transform:translateX(0);transform:translateX(0);opacity:1}75%{-webkit-transform:translateX(20px);transform:translateX(20px);opacity:0}}@-webkit-keyframes fade-up{0%{-webkit-transform:translateY(0);transform:translateY(0);opacity:1}75%{-webkit-transform:translateY(-20px);transform:translateY(-20px);opacity:0}}@keyframes fade-up{0%{-webkit-transform:translateY(0);transform:translateY(0);opacity:1}75%{-webkit-transform:translateY(-20px);transform:translateY(-20px);opacity:0}}@-webkit-keyframes fade-down{0%{-webkit-transform:translateY(0);transform:translateY(0);opacity:1}75%{-webkit-transform:translateY(20px);transform:translateY(20px);opacity:0}}@keyframes fade-down{0%{-webkit-transform:translateY(0);transform:translateY(0);opacity:1}75%{-webkit-transform:translateY(20px);transform:translateY(20px);opacity:0}}@-webkit-keyframes tada{from{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}10%,20%{-webkit-transform:scale3d(.95,.95,.95) rotate3d(0,0,1,-10deg);transform:scale3d(.95,.95,.95) rotate3d(0,0,1,-10deg)}30%,50%,70%,90%{-webkit-transform:scale3d(1,1,1) rotate3d(0,0,1,10deg);transform:scale3d(1,1,1) rotate3d(0,0,1,10deg)}40%,60%,80%{-webkit-transform:scale3d(1,1,1) rotate3d(0,0,1,-10deg);transform:scale3d(1,1,1) rotate3d(0,0,1,-10deg)}to{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}}@keyframes tada{from{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}10%,20%{-webkit-transform:scale3d(.95,.95,.95) rotate3d(0,0,1,-10deg);transform:scale3d(.95,.95,.95) rotate3d(0,0,1,-10deg)}30%,50%,70%,90%{-webkit-transform:scale3d(1,1,1) rotate3d(0,0,1,10deg);transform:scale3d(1,1,1) rotate3d(0,0,1,10deg)}40%,60%,80%{-webkit-transform:rotate3d(0,0,1,-10deg);transform:rotate3d(0,0,1,-10deg)}to{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}}.bx-spin{-webkit-animation:spin 2s linear infinite;animation:spin 2s linear infinite}.bx-spin-hover:hover{-webkit-animation:spin 2s linear infinite;animation:spin 2s linear infinite}.bx-tada{-webkit-animation:tada 1.5s ease infinite;animation:tada 1.5s ease infinite}.bx-tada-hover:hover{-webkit-animation:tada 1.5s ease infinite;animation:tada 1.5s ease infinite}.bx-flashing{-webkit-animation:flashing 1.5s infinite linear;animation:flashing 1.5s infinite linear}.bx-flashing-hover:hover{-webkit-animation:flashing 1.5s infinite linear;animation:flashing 1.5s infinite linear}.bx-burst{-webkit-animation:burst 1.5s infinite linear;animation:burst 1.5s infinite linear}.bx-burst-hover:hover{-webkit-animation:burst 1.5s infinite linear;animation:burst 1.5s infinite linear}.bx-fade-up{-webkit-animation:fade-up 1.5s infinite linear;animation:fade-up 1.5s infinite linear}.bx-fade-up-hover:hover{-webkit-animation:fade-up 1.5s infinite linear;animation:fade-up 1.5s infinite linear}.bx-fade-down{-webkit-animation:fade-down 1.5s infinite linear;animation:fade-down 1.5s infinite linear}.bx-fade-down-hover:hover{-webkit-animation:fade-down 1.5s infinite linear;animation:fade-down 1.5s infinite linear}.bx-fade-left{-webkit-animation:fade-left 1.5s infinite linear;animation:fade-left 1.5s infinite linear}.bx-fade-left-hover:hover{-webkit-animation:fade-left 1.5s infinite linear;animation:fade-left 1.5s infinite linear}.bx-fade-right{-webkit-animation:fade-right 1.5s infinite linear;animation:fade-right 1.5s infinite linear}.bx-fade-right-hover:hover{-webkit-animation:fade-right 1.5s infinite linear;animation:fade-right 1.5s infinite linear}.bx-xs{font-size:1rem!important}.bx-sm{font-size:1.55rem!important}.bx-md{font-size:2.25rem!important}.bx-lg{font-size:3rem!important}.bx-fw{font-size:1.2857142857em;line-height:.8em;width:1.2857142857em;height:.8em;margin-top:-.2em!important;vertical-align:middle}.bx-pull-left{float:left;margin-right:.3em!important}.bx-pull-right{float:right;margin-left:.3em!important}.bx-rotate-90{transform:rotate(90deg)}.bx-rotate-180{transform:rotate(180deg)}.bx-rotate-270{transform:rotate(270deg)}.bx-flip-horizontal{transform:scaleX(-1)}.bx-flip-vertical{transform:scaleY(-1)}.bx-border{padding:.25em;border:.07em solid rgba(0,0,0,.1);border-radius:.25em}.bx-border-circle{padding:.25em;border:.07em solid rgba(0,0,0,.1);border-radius:50%}.bxs-color:before{content:"\ef39"}.bx-reflect-horizontal:before{content:"\ef3a"}.bx-reflect-vertical:before{content:"\ef3b"}.bx-color:before{content:"\ef3c"}.bxl-mongodb:before{content:"\ef3d"}.bxl-postgresql:before{content:"\ef3e"}.bxl-deezer:before{content:"\ef3f"}.bxs-hard-hat:before{content:"\ef2a"}.bxs-home-alt-2:before{content:"\ef2b"}.bxs-cheese:before{content:"\ef2c"}.bx-home-alt-2:before{content:"\ef2d"}.bx-hard-hat:before{content:"\ef2e"}.bx-cheese:before{content:"\ef2f"}.bx-cart-add:before{content:"\ef30"}.bx-cart-download:before{content:"\ef31"}.bx-no-signal:before{content:"\ef32"}.bx-signal-1:before{content:"\ef33"}.bx-signal-2:before{content:"\ef34"}.bx-signal-3:before{content:"\ef35"}.bx-signal-4:before{content:"\ef36"}.bx-signal-5:before{content:"\ef37"}.bxl-xing:before{content:"\ef38"}.bxl-meta:before{content:"\ef27"}.bx-lemon:before{content:"\ef28"}.bxs-lemon:before{content:"\ef29"}.bx-cricket-ball:before{content:"\ef0c"}.bx-baguette:before{content:"\ef0d"}.bx-bowl-hot:before{content:"\ef0e"}.bx-bowl-rice:before{content:"\ef0f"}.bx-cable-car:before{content:"\ef10"}.bx-candles:before{content:"\ef11"}.bx-circle-half:before{content:"\ef12"}.bx-circle-quarter:before{content:"\ef13"}.bx-circle-three-quarter:before{content:"\ef14"}.bx-cross:before{content:"\ef15"}.bx-fork:before{content:"\ef16"}.bx-knife:before{content:"\ef17"}.bx-money-withdraw:before{content:"\ef18"}.bx-popsicle:before{content:"\ef19"}.bx-scatter-chart:before{content:"\ef1a"}.bxs-baguette:before{content:"\ef1b"}.bxs-bowl-hot:before{content:"\ef1c"}.bxs-bowl-rice:before{content:"\ef1d"}.bxs-cable-car:before{content:"\ef1e"}.bxs-circle-half:before{content:"\ef1f"}.bxs-circle-quarter:before{content:"\ef20"}.bxs-circle-three-quarter:before{content:"\ef21"}.bxs-cricket-ball:before{content:"\ef22"}.bxs-invader:before{content:"\ef23"}.bx-male-female:before{content:"\ef24"}.bxs-popsicle:before{content:"\ef25"}.bxs-tree-alt:before{content:"\ef26"}.bxl-venmo:before{content:"\e900"}.bxl-upwork:before{content:"\e901"}.bxl-netlify:before{content:"\e902"}.bxl-java:before{content:"\e903"}.bxl-heroku:before{content:"\e904"}.bxl-go-lang:before{content:"\e905"}.bxl-gmail:before{content:"\e906"}.bxl-flask:before{content:"\e907"}.bxl-99designs:before{content:"\e908"}.bxl-500px:before{content:"\e909"}.bxl-adobe:before{content:"\e90a"}.bxl-airbnb:before{content:"\e90b"}.bxl-algolia:before{content:"\e90c"}.bxl-amazon:before{content:"\e90d"}.bxl-android:before{content:"\e90e"}.bxl-angular:before{content:"\e90f"}.bxl-apple:before{content:"\e910"}.bxl-audible:before{content:"\e911"}.bxl-aws:before{content:"\e912"}.bxl-baidu:before{content:"\e913"}.bxl-behance:before{content:"\e914"}.bxl-bing:before{content:"\e915"}.bxl-bitcoin:before{content:"\e916"}.bxl-blender:before{content:"\e917"}.bxl-blogger:before{content:"\e918"}.bxl-bootstrap:before{content:"\e919"}.bxl-chrome:before{content:"\e91a"}.bxl-codepen:before{content:"\e91b"}.bxl-c-plus-plus:before{content:"\e91c"}.bxl-creative-commons:before{content:"\e91d"}.bxl-css3:before{content:"\e91e"}.bxl-dailymotion:before{content:"\e91f"}.bxl-deviantart:before{content:"\e920"}.bxl-dev-to:before{content:"\e921"}.bxl-digg:before{content:"\e922"}.bxl-digitalocean:before{content:"\e923"}.bxl-discord:before{content:"\e924"}.bxl-discord-alt:before{content:"\e925"}.bxl-discourse:before{content:"\e926"}.bxl-django:before{content:"\e927"}.bxl-docker:before{content:"\e928"}.bxl-dribbble:before{content:"\e929"}.bxl-dropbox:before{content:"\e92a"}.bxl-drupal:before{content:"\e92b"}.bxl-ebay:before{content:"\e92c"}.bxl-edge:before{content:"\e92d"}.bxl-etsy:before{content:"\e92e"}.bxl-facebook:before{content:"\e92f"}.bxl-facebook-circle:before{content:"\e930"}.bxl-facebook-square:before{content:"\e931"}.bxl-figma:before{content:"\e932"}.bxl-firebase:before{content:"\e933"}.bxl-firefox:before{content:"\e934"}.bxl-flickr:before{content:"\e935"}.bxl-flickr-square:before{content:"\e936"}.bxl-flutter:before{content:"\e937"}.bxl-foursquare:before{content:"\e938"}.bxl-git:before{content:"\e939"}.bxl-github:before{content:"\e93a"}.bxl-gitlab:before{content:"\e93b"}.bxl-google:before{content:"\e93c"}.bxl-google-cloud:before{content:"\e93d"}.bxl-google-plus:before{content:"\e93e"}.bxl-google-plus-circle:before{content:"\e93f"}.bxl-html5:before{content:"\e940"}.bxl-imdb:before{content:"\e941"}.bxl-instagram:before{content:"\e942"}.bxl-instagram-alt:before{content:"\e943"}.bxl-internet-explorer:before{content:"\e944"}.bxl-invision:before{content:"\e945"}.bxl-javascript:before{content:"\e946"}.bxl-joomla:before{content:"\e947"}.bxl-jquery:before{content:"\e948"}.bxl-jsfiddle:before{content:"\e949"}.bxl-kickstarter:before{content:"\e94a"}.bxl-kubernetes:before{content:"\e94b"}.bxl-less:before{content:"\e94c"}.bxl-linkedin:before{content:"\e94d"}.bxl-linkedin-square:before{content:"\e94e"}.bxl-magento:before{content:"\e94f"}.bxl-mailchimp:before{content:"\e950"}.bxl-markdown:before{content:"\e951"}.bxl-mastercard:before{content:"\e952"}.bxl-mastodon:before{content:"\e953"}.bxl-medium:before{content:"\e954"}.bxl-medium-old:before{content:"\e955"}.bxl-medium-square:before{content:"\e956"}.bxl-messenger:before{content:"\e957"}.bxl-microsoft:before{content:"\e958"}.bxl-microsoft-teams:before{content:"\e959"}.bxl-nodejs:before{content:"\e95a"}.bxl-ok-ru:before{content:"\e95b"}.bxl-opera:before{content:"\e95c"}.bxl-patreon:before{content:"\e95d"}.bxl-paypal:before{content:"\e95e"}.bxl-periscope:before{content:"\e95f"}.bxl-php:before{content:"\e960"}.bxl-pinterest:before{content:"\e961"}.bxl-pinterest-alt:before{content:"\e962"}.bxl-play-store:before{content:"\e963"}.bxl-pocket:before{content:"\e964"}.bxl-product-hunt:before{content:"\e965"}.bxl-python:before{content:"\e966"}.bxl-quora:before{content:"\e967"}.bxl-react:before{content:"\e968"}.bxl-redbubble:before{content:"\e969"}.bxl-reddit:before{content:"\e96a"}.bxl-redux:before{content:"\e96b"}.bxl-sass:before{content:"\e96c"}.bxl-shopify:before{content:"\e96d"}.bxl-sketch:before{content:"\e96e"}.bxl-skype:before{content:"\e96f"}.bxl-slack:before{content:"\e970"}.bxl-slack-old:before{content:"\e971"}.bxl-snapchat:before{content:"\e972"}.bxl-soundcloud:before{content:"\e973"}.bxl-spotify:before{content:"\e974"}.bxl-spring-boot:before{content:"\e975"}.bxl-squarespace:before{content:"\e976"}.bxl-stack-overflow:before{content:"\e977"}.bxl-steam:before{content:"\e978"}.bxl-stripe:before{content:"\e979"}.bxl-tailwind-css:before{content:"\e97a"}.bxl-telegram:before{content:"\e97b"}.bxl-tiktok:before{content:"\e97c"}.bxl-trello:before{content:"\e97d"}.bxl-trip-advisor:before{content:"\e97e"}.bxl-tumblr:before{content:"\e97f"}.bxl-tux:before{content:"\e980"}.bxl-twitch:before{content:"\e981"}.bxl-twitter:before{content:"\e982"}.bxl-unity:before{content:"\e983"}.bxl-unsplash:before{content:"\e984"}.bxl-vimeo:before{content:"\e985"}.bxl-visa:before{content:"\e986"}.bxl-visual-studio:before{content:"\e987"}.bxl-vk:before{content:"\e988"}.bxl-vuejs:before{content:"\e989"}.bxl-whatsapp:before{content:"\e98a"}.bxl-whatsapp-square:before{content:"\e98b"}.bxl-wikipedia:before{content:"\e98c"}.bxl-windows:before{content:"\e98d"}.bxl-wix:before{content:"\e98e"}.bxl-wordpress:before{content:"\e98f"}.bxl-yahoo:before{content:"\e990"}.bxl-yelp:before{content:"\e991"}.bxl-youtube:before{content:"\e992"}.bxl-zoom:before{content:"\e993"}.bx-collapse-alt:before{content:"\e994"}.bx-collapse-horizontal:before{content:"\e995"}.bx-collapse-vertical:before{content:"\e996"}.bx-expand-horizontal:before{content:"\e997"}.bx-expand-vertical:before{content:"\e998"}.bx-injection:before{content:"\e999"}.bx-leaf:before{content:"\e99a"}.bx-math:before{content:"\e99b"}.bx-party:before{content:"\e99c"}.bx-abacus:before{content:"\e99d"}.bx-accessibility:before{content:"\e99e"}.bx-add-to-queue:before{content:"\e99f"}.bx-adjust:before{content:"\e9a0"}.bx-alarm:before{content:"\e9a1"}.bx-alarm-add:before{content:"\e9a2"}.bx-alarm-exclamation:before{content:"\e9a3"}.bx-alarm-off:before{content:"\e9a4"}.bx-alarm-snooze:before{content:"\e9a5"}.bx-album:before{content:"\e9a6"}.bx-align-justify:before{content:"\e9a7"}.bx-align-left:before{content:"\e9a8"}.bx-align-middle:before{content:"\e9a9"}.bx-align-right:before{content:"\e9aa"}.bx-analyse:before{content:"\e9ab"}.bx-anchor:before{content:"\e9ac"}.bx-angry:before{content:"\e9ad"}.bx-aperture:before{content:"\e9ae"}.bx-arch:before{content:"\e9af"}.bx-archive:before{content:"\e9b0"}.bx-archive-in:before{content:"\e9b1"}.bx-archive-out:before{content:"\e9b2"}.bx-area:before{content:"\e9b3"}.bx-arrow-back:before{content:"\e9b4"}.bx-arrow-from-bottom:before{content:"\e9b5"}.bx-arrow-from-left:before{content:"\e9b6"}.bx-arrow-from-right:before{content:"\e9b7"}.bx-arrow-from-top:before{content:"\e9b8"}.bx-arrow-to-bottom:before{content:"\e9b9"}.bx-arrow-to-left:before{content:"\e9ba"}.bx-arrow-to-right:before{content:"\e9bb"}.bx-arrow-to-top:before{content:"\e9bc"}.bx-at:before{content:"\e9bd"}.bx-atom:before{content:"\e9be"}.bx-award:before{content:"\e9bf"}.bx-badge:before{content:"\e9c0"}.bx-badge-check:before{content:"\e9c1"}.bx-ball:before{content:"\e9c2"}.bx-band-aid:before{content:"\e9c3"}.bx-bar-chart:before{content:"\e9c4"}.bx-bar-chart-alt:before{content:"\e9c5"}.bx-bar-chart-alt-2:before{content:"\e9c6"}.bx-bar-chart-square:before{content:"\e9c7"}.bx-barcode:before{content:"\e9c8"}.bx-barcode-reader:before{content:"\e9c9"}.bx-baseball:before{content:"\e9ca"}.bx-basket:before{content:"\e9cb"}.bx-basketball:before{content:"\e9cc"}.bx-bath:before{content:"\e9cd"}.bx-battery:before{content:"\e9ce"}.bx-bed:before{content:"\e9cf"}.bx-been-here:before{content:"\e9d0"}.bx-beer:before{content:"\e9d1"}.bx-bell:before{content:"\e9d2"}.bx-bell-minus:before{content:"\e9d3"}.bx-bell-off:before{content:"\e9d4"}.bx-bell-plus:before{content:"\e9d5"}.bx-bible:before{content:"\e9d6"}.bx-bitcoin:before{content:"\e9d7"}.bx-blanket:before{content:"\e9d8"}.bx-block:before{content:"\e9d9"}.bx-bluetooth:before{content:"\e9da"}.bx-body:before{content:"\e9db"}.bx-bold:before{content:"\e9dc"}.bx-bolt-circle:before{content:"\e9dd"}.bx-bomb:before{content:"\e9de"}.bx-bone:before{content:"\e9df"}.bx-bong:before{content:"\e9e0"}.bx-book:before{content:"\e9e1"}.bx-book-add:before{content:"\e9e2"}.bx-book-alt:before{content:"\e9e3"}.bx-book-bookmark:before{content:"\e9e4"}.bx-book-content:before{content:"\e9e5"}.bx-book-heart:before{content:"\e9e6"}.bx-bookmark:before{content:"\e9e7"}.bx-bookmark-alt:before{content:"\e9e8"}.bx-bookmark-alt-minus:before{content:"\e9e9"}.bx-bookmark-alt-plus:before{content:"\e9ea"}.bx-bookmark-heart:before{content:"\e9eb"}.bx-bookmark-minus:before{content:"\e9ec"}.bx-bookmark-plus:before{content:"\e9ed"}.bx-bookmarks:before{content:"\e9ee"}.bx-book-open:before{content:"\e9ef"}.bx-book-reader:before{content:"\e9f0"}.bx-border-all:before{content:"\e9f1"}.bx-border-bottom:before{content:"\e9f2"}.bx-border-inner:before{content:"\e9f3"}.bx-border-left:before{content:"\e9f4"}.bx-border-none:before{content:"\e9f5"}.bx-border-outer:before{content:"\e9f6"}.bx-border-radius:before{content:"\e9f7"}.bx-border-right:before{content:"\e9f8"}.bx-border-top:before{content:"\e9f9"}.bx-bot:before{content:"\e9fa"}.bx-bowling-ball:before{content:"\e9fb"}.bx-box:before{content:"\e9fc"}.bx-bracket:before{content:"\e9fd"}.bx-braille:before{content:"\e9fe"}.bx-brain:before{content:"\e9ff"}.bx-briefcase:before{content:"\ea00"}.bx-briefcase-alt:before{content:"\ea01"}.bx-briefcase-alt-2:before{content:"\ea02"}.bx-brightness:before{content:"\ea03"}.bx-brightness-half:before{content:"\ea04"}.bx-broadcast:before{content:"\ea05"}.bx-brush:before{content:"\ea06"}.bx-brush-alt:before{content:"\ea07"}.bx-bug:before{content:"\ea08"}.bx-bug-alt:before{content:"\ea09"}.bx-building:before{content:"\ea0a"}.bx-building-house:before{content:"\ea0b"}.bx-buildings:before{content:"\ea0c"}.bx-bulb:before{content:"\ea0d"}.bx-bullseye:before{content:"\ea0e"}.bx-buoy:before{content:"\ea0f"}.bx-bus:before{content:"\ea10"}.bx-bus-school:before{content:"\ea11"}.bx-cabinet:before{content:"\ea12"}.bx-cake:before{content:"\ea13"}.bx-calculator:before{content:"\ea14"}.bx-calendar:before{content:"\ea15"}.bx-calendar-alt:before{content:"\ea16"}.bx-calendar-check:before{content:"\ea17"}.bx-calendar-edit:before{content:"\ea18"}.bx-calendar-event:before{content:"\ea19"}.bx-calendar-exclamation:before{content:"\ea1a"}.bx-calendar-heart:before{content:"\ea1b"}.bx-calendar-minus:before{content:"\ea1c"}.bx-calendar-plus:before{content:"\ea1d"}.bx-calendar-star:before{content:"\ea1e"}.bx-calendar-week:before{content:"\ea1f"}.bx-calendar-x:before{content:"\ea20"}.bx-camera:before{content:"\ea21"}.bx-camera-home:before{content:"\ea22"}.bx-camera-movie:before{content:"\ea23"}.bx-camera-off:before{content:"\ea24"}.bx-capsule:before{content:"\ea25"}.bx-captions:before{content:"\ea26"}.bx-car:before{content:"\ea27"}.bx-card:before{content:"\ea28"}.bx-caret-down:before{content:"\ea29"}.bx-caret-down-circle:before{content:"\ea2a"}.bx-caret-down-square:before{content:"\ea2b"}.bx-caret-left:before{content:"\ea2c"}.bx-caret-left-circle:before{content:"\ea2d"}.bx-caret-left-square:before{content:"\ea2e"}.bx-caret-right:before{content:"\ea2f"}.bx-caret-right-circle:before{content:"\ea30"}.bx-caret-right-square:before{content:"\ea31"}.bx-caret-up:before{content:"\ea32"}.bx-caret-up-circle:before{content:"\ea33"}.bx-caret-up-square:before{content:"\ea34"}.bx-carousel:before{content:"\ea35"}.bx-cart:before{content:"\ea36"}.bx-cart-alt:before{content:"\ea37"}.bx-cast:before{content:"\ea38"}.bx-category:before{content:"\ea39"}.bx-category-alt:before{content:"\ea3a"}.bx-cctv:before{content:"\ea3b"}.bx-certification:before{content:"\ea3c"}.bx-chair:before{content:"\ea3d"}.bx-chalkboard:before{content:"\ea3e"}.bx-chart:before{content:"\ea3f"}.bx-chat:before{content:"\ea40"}.bx-check:before{content:"\ea41"}.bx-checkbox:before{content:"\ea42"}.bx-checkbox-checked:before{content:"\ea43"}.bx-checkbox-minus:before{content:"\ea44"}.bx-checkbox-square:before{content:"\ea45"}.bx-check-circle:before{content:"\ea46"}.bx-check-double:before{content:"\ea47"}.bx-check-shield:before{content:"\ea48"}.bx-check-square:before{content:"\ea49"}.bx-chevron-down:before{content:"\ea4a"}.bx-chevron-down-circle:before{content:"\ea4b"}.bx-chevron-down-square:before{content:"\ea4c"}.bx-chevron-left:before{content:"\ea4d"}.bx-chevron-left-circle:before{content:"\ea4e"}.bx-chevron-left-square:before{content:"\ea4f"}.bx-chevron-right:before{content:"\ea50"}.bx-chevron-right-circle:before{content:"\ea51"}.bx-chevron-right-square:before{content:"\ea52"}.bx-chevrons-down:before{content:"\ea53"}.bx-chevrons-left:before{content:"\ea54"}.bx-chevrons-right:before{content:"\ea55"}.bx-chevrons-up:before{content:"\ea56"}.bx-chevron-up:before{content:"\ea57"}.bx-chevron-up-circle:before{content:"\ea58"}.bx-chevron-up-square:before{content:"\ea59"}.bx-chip:before{content:"\ea5a"}.bx-church:before{content:"\ea5b"}.bx-circle:before{content:"\ea5c"}.bx-clinic:before{content:"\ea5d"}.bx-clipboard:before{content:"\ea5e"}.bx-closet:before{content:"\ea5f"}.bx-cloud:before{content:"\ea60"}.bx-cloud-download:before{content:"\ea61"}.bx-cloud-drizzle:before{content:"\ea62"}.bx-cloud-lightning:before{content:"\ea63"}.bx-cloud-light-rain:before{content:"\ea64"}.bx-cloud-rain:before{content:"\ea65"}.bx-cloud-snow:before{content:"\ea66"}.bx-cloud-upload:before{content:"\ea67"}.bx-code:before{content:"\ea68"}.bx-code-alt:before{content:"\ea69"}.bx-code-block:before{content:"\ea6a"}.bx-code-curly:before{content:"\ea6b"}.bx-coffee:before{content:"\ea6c"}.bx-coffee-togo:before{content:"\ea6d"}.bx-cog:before{content:"\ea6e"}.bx-coin:before{content:"\ea6f"}.bx-coin-stack:before{content:"\ea70"}.bx-collapse:before{content:"\ea71"}.bx-collection:before{content:"\ea72"}.bx-color-fill:before{content:"\ea73"}.bx-columns:before{content:"\ea74"}.bx-command:before{content:"\ea75"}.bx-comment:before{content:"\ea76"}.bx-comment-add:before{content:"\ea77"}.bx-comment-check:before{content:"\ea78"}.bx-comment-detail:before{content:"\ea79"}.bx-comment-dots:before{content:"\ea7a"}.bx-comment-edit:before{content:"\ea7b"}.bx-comment-error:before{content:"\ea7c"}.bx-comment-minus:before{content:"\ea7d"}.bx-comment-x:before{content:"\ea7e"}.bx-compass:before{content:"\ea7f"}.bx-confused:before{content:"\ea80"}.bx-conversation:before{content:"\ea81"}.bx-cookie:before{content:"\ea82"}.bx-cool:before{content:"\ea83"}.bx-copy:before{content:"\ea84"}.bx-copy-alt:before{content:"\ea85"}.bx-copyright:before{content:"\ea86"}.bx-credit-card:before{content:"\ea87"}.bx-credit-card-alt:before{content:"\ea88"}.bx-credit-card-front:before{content:"\ea89"}.bx-crop:before{content:"\ea8a"}.bx-crosshair:before{content:"\ea8b"}.bx-crown:before{content:"\ea8c"}.bx-cube:before{content:"\ea8d"}.bx-cube-alt:before{content:"\ea8e"}.bx-cuboid:before{content:"\ea8f"}.bx-current-location:before{content:"\ea90"}.bx-customize:before{content:"\ea91"}.bx-cut:before{content:"\ea92"}.bx-cycling:before{content:"\ea93"}.bx-cylinder:before{content:"\ea94"}.bx-data:before{content:"\ea95"}.bx-desktop:before{content:"\ea96"}.bx-detail:before{content:"\ea97"}.bx-devices:before{content:"\ea98"}.bx-dialpad:before{content:"\ea99"}.bx-dialpad-alt:before{content:"\ea9a"}.bx-diamond:before{content:"\ea9b"}.bx-dice-1:before{content:"\ea9c"}.bx-dice-2:before{content:"\ea9d"}.bx-dice-3:before{content:"\ea9e"}.bx-dice-4:before{content:"\ea9f"}.bx-dice-5:before{content:"\eaa0"}.bx-dice-6:before{content:"\eaa1"}.bx-directions:before{content:"\eaa2"}.bx-disc:before{content:"\eaa3"}.bx-dish:before{content:"\eaa4"}.bx-dislike:before{content:"\eaa5"}.bx-dizzy:before{content:"\eaa6"}.bx-dna:before{content:"\eaa7"}.bx-dock-bottom:before{content:"\eaa8"}.bx-dock-left:before{content:"\eaa9"}.bx-dock-right:before{content:"\eaaa"}.bx-dock-top:before{content:"\eaab"}.bx-dollar:before{content:"\eaac"}.bx-dollar-circle:before{content:"\eaad"}.bx-donate-blood:before{content:"\eaae"}.bx-donate-heart:before{content:"\eaaf"}.bx-door-open:before{content:"\eab0"}.bx-dots-horizontal:before{content:"\eab1"}.bx-dots-horizontal-rounded:before{content:"\eab2"}.bx-dots-vertical:before{content:"\eab3"}.bx-dots-vertical-rounded:before{content:"\eab4"}.bx-doughnut-chart:before{content:"\eab5"}.bx-down-arrow:before{content:"\eab6"}.bx-down-arrow-alt:before{content:"\eab7"}.bx-down-arrow-circle:before{content:"\eab8"}.bx-download:before{content:"\eab9"}.bx-downvote:before{content:"\eaba"}.bx-drink:before{content:"\eabb"}.bx-droplet:before{content:"\eabc"}.bx-dumbbell:before{content:"\eabd"}.bx-duplicate:before{content:"\eabe"}.bx-edit:before{content:"\eabf"}.bx-edit-alt:before{content:"\eac0"}.bx-envelope:before{content:"\eac1"}.bx-envelope-open:before{content:"\eac2"}.bx-equalizer:before{content:"\eac3"}.bx-eraser:before{content:"\eac4"}.bx-error:before{content:"\eac5"}.bx-error-alt:before{content:"\eac6"}.bx-error-circle:before{content:"\eac7"}.bx-euro:before{content:"\eac8"}.bx-exclude:before{content:"\eac9"}.bx-exit:before{content:"\eaca"}.bx-exit-fullscreen:before{content:"\eacb"}.bx-expand:before{content:"\eacc"}.bx-expand-alt:before{content:"\eacd"}.bx-export:before{content:"\eace"}.bx-extension:before{content:"\eacf"}.bx-face:before{content:"\ead0"}.bx-fast-forward:before{content:"\ead1"}.bx-fast-forward-circle:before{content:"\ead2"}.bx-female:before{content:"\ead3"}.bx-female-sign:before{content:"\ead4"}.bx-file:before{content:"\ead5"}.bx-file-blank:before{content:"\ead6"}.bx-file-find:before{content:"\ead7"}.bx-film:before{content:"\ead8"}.bx-filter:before{content:"\ead9"}.bx-filter-alt:before{content:"\eada"}.bx-fingerprint:before{content:"\eadb"}.bx-first-aid:before{content:"\eadc"}.bx-first-page:before{content:"\eadd"}.bx-flag:before{content:"\eade"}.bx-folder:before{content:"\eadf"}.bx-folder-minus:before{content:"\eae0"}.bx-folder-open:before{content:"\eae1"}.bx-folder-plus:before{content:"\eae2"}.bx-font:before{content:"\eae3"}.bx-font-color:before{content:"\eae4"}.bx-font-family:before{content:"\eae5"}.bx-font-size:before{content:"\eae6"}.bx-food-menu:before{content:"\eae7"}.bx-food-tag:before{content:"\eae8"}.bx-football:before{content:"\eae9"}.bx-fridge:before{content:"\eaea"}.bx-fullscreen:before{content:"\eaeb"}.bx-game:before{content:"\eaec"}.bx-gas-pump:before{content:"\eaed"}.bx-ghost:before{content:"\eaee"}.bx-gift:before{content:"\eaef"}.bx-git-branch:before{content:"\eaf0"}.bx-git-commit:before{content:"\eaf1"}.bx-git-compare:before{content:"\eaf2"}.bx-git-merge:before{content:"\eaf3"}.bx-git-pull-request:before{content:"\eaf4"}.bx-git-repo-forked:before{content:"\eaf5"}.bx-glasses:before{content:"\eaf6"}.bx-glasses-alt:before{content:"\eaf7"}.bx-globe:before{content:"\eaf8"}.bx-globe-alt:before{content:"\eaf9"}.bx-grid:before{content:"\eafa"}.bx-grid-alt:before{content:"\eafb"}.bx-grid-horizontal:before{content:"\eafc"}.bx-grid-small:before{content:"\eafd"}.bx-grid-vertical:before{content:"\eafe"}.bx-group:before{content:"\eaff"}.bx-handicap:before{content:"\eb00"}.bx-happy:before{content:"\eb01"}.bx-happy-alt:before{content:"\eb02"}.bx-happy-beaming:before{content:"\eb03"}.bx-happy-heart-eyes:before{content:"\eb04"}.bx-hash:before{content:"\eb05"}.bx-hdd:before{content:"\eb06"}.bx-heading:before{content:"\eb07"}.bx-headphone:before{content:"\eb08"}.bx-health:before{content:"\eb09"}.bx-heart:before{content:"\eb0a"}.bx-heart-circle:before{content:"\eb0b"}.bx-heart-square:before{content:"\eb0c"}.bx-help-circle:before{content:"\eb0d"}.bx-hide:before{content:"\eb0e"}.bx-highlight:before{content:"\eb0f"}.bx-history:before{content:"\eb10"}.bx-hive:before{content:"\eb11"}.bx-home:before{content:"\eb12"}.bx-home-alt:before{content:"\eb13"}.bx-home-circle:before{content:"\eb14"}.bx-home-heart:before{content:"\eb15"}.bx-home-smile:before{content:"\eb16"}.bx-horizontal-center:before{content:"\eb17"}.bx-hotel:before{content:"\eb18"}.bx-hourglass:before{content:"\eb19"}.bx-id-card:before{content:"\eb1a"}.bx-image:before{content:"\eb1b"}.bx-image-add:before{content:"\eb1c"}.bx-image-alt:before{content:"\eb1d"}.bx-images:before{content:"\eb1e"}.bx-import:before{content:"\eb1f"}.bx-infinite:before{content:"\eb20"}.bx-info-circle:before{content:"\eb21"}.bx-info-square:before{content:"\eb22"}.bx-intersect:before{content:"\eb23"}.bx-italic:before{content:"\eb24"}.bx-joystick:before{content:"\eb25"}.bx-joystick-alt:before{content:"\eb26"}.bx-joystick-button:before{content:"\eb27"}.bx-key:before{content:"\eb28"}.bx-label:before{content:"\eb29"}.bx-landscape:before{content:"\eb2a"}.bx-laptop:before{content:"\eb2b"}.bx-last-page:before{content:"\eb2c"}.bx-laugh:before{content:"\eb2d"}.bx-layer:before{content:"\eb2e"}.bx-layer-minus:before{content:"\eb2f"}.bx-layer-plus:before{content:"\eb30"}.bx-layout:before{content:"\eb31"}.bx-left-arrow:before{content:"\eb32"}.bx-left-arrow-alt:before{content:"\eb33"}.bx-left-arrow-circle:before{content:"\eb34"}.bx-left-down-arrow-circle:before{content:"\eb35"}.bx-left-indent:before{content:"\eb36"}.bx-left-top-arrow-circle:before{content:"\eb37"}.bx-library:before{content:"\eb38"}.bx-like:before{content:"\eb39"}.bx-line-chart:before{content:"\eb3a"}.bx-line-chart-down:before{content:"\eb3b"}.bx-link:before{content:"\eb3c"}.bx-link-alt:before{content:"\eb3d"}.bx-link-external:before{content:"\eb3e"}.bx-lira:before{content:"\eb3f"}.bx-list-check:before{content:"\eb40"}.bx-list-minus:before{content:"\eb41"}.bx-list-ol:before{content:"\eb42"}.bx-list-plus:before{content:"\eb43"}.bx-list-ul:before{content:"\eb44"}.bx-loader:before{content:"\eb45"}.bx-loader-alt:before{content:"\eb46"}.bx-loader-circle:before{content:"\eb47"}.bx-location-plus:before{content:"\eb48"}.bx-lock:before{content:"\eb49"}.bx-lock-alt:before{content:"\eb4a"}.bx-lock-open:before{content:"\eb4b"}.bx-lock-open-alt:before{content:"\eb4c"}.bx-log-in:before{content:"\eb4d"}.bx-log-in-circle:before{content:"\eb4e"}.bx-log-out:before{content:"\eb4f"}.bx-log-out-circle:before{content:"\eb50"}.bx-low-vision:before{content:"\eb51"}.bx-magnet:before{content:"\eb52"}.bx-mail-send:before{content:"\eb53"}.bx-male:before{content:"\eb54"}.bx-male-sign:before{content:"\eb55"}.bx-map:before{content:"\eb56"}.bx-map-alt:before{content:"\eb57"}.bx-map-pin:before{content:"\eb58"}.bx-mask:before{content:"\eb59"}.bx-medal:before{content:"\eb5a"}.bx-meh:before{content:"\eb5b"}.bx-meh-alt:before{content:"\eb5c"}.bx-meh-blank:before{content:"\eb5d"}.bx-memory-card:before{content:"\eb5e"}.bx-menu:before{content:"\eb5f"}.bx-menu-alt-left:before{content:"\eb60"}.bx-menu-alt-right:before{content:"\eb61"}.bx-merge:before{content:"\eb62"}.bx-message:before{content:"\eb63"}.bx-message-add:before{content:"\eb64"}.bx-message-alt:before{content:"\eb65"}.bx-message-alt-add:before{content:"\eb66"}.bx-message-alt-check:before{content:"\eb67"}.bx-message-alt-detail:before{content:"\eb68"}.bx-message-alt-dots:before{content:"\eb69"}.bx-message-alt-edit:before{content:"\eb6a"}.bx-message-alt-error:before{content:"\eb6b"}.bx-message-alt-minus:before{content:"\eb6c"}.bx-message-alt-x:before{content:"\eb6d"}.bx-message-check:before{content:"\eb6e"}.bx-message-detail:before{content:"\eb6f"}.bx-message-dots:before{content:"\eb70"}.bx-message-edit:before{content:"\eb71"}.bx-message-error:before{content:"\eb72"}.bx-message-minus:before{content:"\eb73"}.bx-message-rounded:before{content:"\eb74"}.bx-message-rounded-add:before{content:"\eb75"}.bx-message-rounded-check:before{content:"\eb76"}.bx-message-rounded-detail:before{content:"\eb77"}.bx-message-rounded-dots:before{content:"\eb78"}.bx-message-rounded-edit:before{content:"\eb79"}.bx-message-rounded-error:before{content:"\eb7a"}.bx-message-rounded-minus:before{content:"\eb7b"}.bx-message-rounded-x:before{content:"\eb7c"}.bx-message-square:before{content:"\eb7d"}.bx-message-square-add:before{content:"\eb7e"}.bx-message-square-check:before{content:"\eb7f"}.bx-message-square-detail:before{content:"\eb80"}.bx-message-square-dots:before{content:"\eb81"}.bx-message-square-edit:before{content:"\eb82"}.bx-message-square-error:before{content:"\eb83"}.bx-message-square-minus:before{content:"\eb84"}.bx-message-square-x:before{content:"\eb85"}.bx-message-x:before{content:"\eb86"}.bx-meteor:before{content:"\eb87"}.bx-microchip:before{content:"\eb88"}.bx-microphone:before{content:"\eb89"}.bx-microphone-off:before{content:"\eb8a"}.bx-minus:before{content:"\eb8b"}.bx-minus-back:before{content:"\eb8c"}.bx-minus-circle:before{content:"\eb8d"}.bx-minus-front:before{content:"\eb8e"}.bx-mobile:before{content:"\eb8f"}.bx-mobile-alt:before{content:"\eb90"}.bx-mobile-landscape:before{content:"\eb91"}.bx-mobile-vibration:before{content:"\eb92"}.bx-money:before{content:"\eb93"}.bx-moon:before{content:"\eb94"}.bx-mouse:before{content:"\eb95"}.bx-mouse-alt:before{content:"\eb96"}.bx-move:before{content:"\eb97"}.bx-move-horizontal:before{content:"\eb98"}.bx-move-vertical:before{content:"\eb99"}.bx-movie:before{content:"\eb9a"}.bx-movie-play:before{content:"\eb9b"}.bx-music:before{content:"\eb9c"}.bx-navigation:before{content:"\eb9d"}.bx-network-chart:before{content:"\eb9e"}.bx-news:before{content:"\eb9f"}.bx-no-entry:before{content:"\eba0"}.bx-note:before{content:"\eba1"}.bx-notepad:before{content:"\eba2"}.bx-notification:before{content:"\eba3"}.bx-notification-off:before{content:"\eba4"}.bx-outline:before{content:"\eba5"}.bx-package:before{content:"\eba6"}.bx-paint:before{content:"\eba7"}.bx-paint-roll:before{content:"\eba8"}.bx-palette:before{content:"\eba9"}.bx-paperclip:before{content:"\ebaa"}.bx-paper-plane:before{content:"\ebab"}.bx-paragraph:before{content:"\ebac"}.bx-paste:before{content:"\ebad"}.bx-pause:before{content:"\ebae"}.bx-pause-circle:before{content:"\ebaf"}.bx-pen:before{content:"\ebb0"}.bx-pencil:before{content:"\ebb1"}.bx-phone:before{content:"\ebb2"}.bx-phone-call:before{content:"\ebb3"}.bx-phone-incoming:before{content:"\ebb4"}.bx-phone-off:before{content:"\ebb5"}.bx-phone-outgoing:before{content:"\ebb6"}.bx-photo-album:before{content:"\ebb7"}.bx-pie-chart:before{content:"\ebb8"}.bx-pie-chart-alt:before{content:"\ebb9"}.bx-pie-chart-alt-2:before{content:"\ebba"}.bx-pin:before{content:"\ebbb"}.bx-planet:before{content:"\ebbc"}.bx-play:before{content:"\ebbd"}.bx-play-circle:before{content:"\ebbe"}.bx-plug:before{content:"\ebbf"}.bx-plus:before{content:"\ebc0"}.bx-plus-circle:before{content:"\ebc1"}.bx-plus-medical:before{content:"\ebc2"}.bx-podcast:before{content:"\ebc3"}.bx-pointer:before{content:"\ebc4"}.bx-poll:before{content:"\ebc5"}.bx-polygon:before{content:"\ebc6"}.bx-pound:before{content:"\ebc7"}.bx-power-off:before{content:"\ebc8"}.bx-printer:before{content:"\ebc9"}.bx-pulse:before{content:"\ebca"}.bx-purchase-tag:before{content:"\ebcb"}.bx-purchase-tag-alt:before{content:"\ebcc"}.bx-pyramid:before{content:"\ebcd"}.bx-qr:before{content:"\ebce"}.bx-qr-scan:before{content:"\ebcf"}.bx-question-mark:before{content:"\ebd0"}.bx-radar:before{content:"\ebd1"}.bx-radio:before{content:"\ebd2"}.bx-radio-circle:before{content:"\ebd3"}.bx-radio-circle-marked:before{content:"\ebd4"}.bx-receipt:before{content:"\ebd5"}.bx-rectangle:before{content:"\ebd6"}.bx-recycle:before{content:"\ebd7"}.bx-redo:before{content:"\ebd8"}.bx-refresh:before{content:"\ebd9"}.bx-registered:before{content:"\ebda"}.bx-rename:before{content:"\ebdb"}.bx-repeat:before{content:"\ebdc"}.bx-reply:before{content:"\ebdd"}.bx-reply-all:before{content:"\ebde"}.bx-repost:before{content:"\ebdf"}.bx-reset:before{content:"\ebe0"}.bx-restaurant:before{content:"\ebe1"}.bx-revision:before{content:"\ebe2"}.bx-rewind:before{content:"\ebe3"}.bx-rewind-circle:before{content:"\ebe4"}.bx-right-arrow:before{content:"\ebe5"}.bx-right-arrow-alt:before{content:"\ebe6"}.bx-right-arrow-circle:before{content:"\ebe7"}.bx-right-down-arrow-circle:before{content:"\ebe8"}.bx-right-indent:before{content:"\ebe9"}.bx-right-top-arrow-circle:before{content:"\ebea"}.bx-rocket:before{content:"\ebeb"}.bx-rotate-left:before{content:"\ebec"}.bx-rotate-right:before{content:"\ebed"}.bx-rss:before{content:"\ebee"}.bx-ruble:before{content:"\ebef"}.bx-ruler:before{content:"\ebf0"}.bx-run:before{content:"\ebf1"}.bx-rupee:before{content:"\ebf2"}.bx-sad:before{content:"\ebf3"}.bx-save:before{content:"\ebf4"}.bx-scan:before{content:"\ebf5"}.bx-screenshot:before{content:"\ebf6"}.bx-search:before{content:"\ebf7"}.bx-search-alt:before{content:"\ebf8"}.bx-search-alt-2:before{content:"\ebf9"}.bx-selection:before{content:"\ebfa"}.bx-select-multiple:before{content:"\ebfb"}.bx-send:before{content:"\ebfc"}.bx-server:before{content:"\ebfd"}.bx-shape-circle:before{content:"\ebfe"}.bx-shape-polygon:before{content:"\ebff"}.bx-shape-square:before{content:"\ec00"}.bx-shape-triangle:before{content:"\ec01"}.bx-share:before{content:"\ec02"}.bx-share-alt:before{content:"\ec03"}.bx-shekel:before{content:"\ec04"}.bx-shield:before{content:"\ec05"}.bx-shield-alt:before{content:"\ec06"}.bx-shield-alt-2:before{content:"\ec07"}.bx-shield-quarter:before{content:"\ec08"}.bx-shield-x:before{content:"\ec09"}.bx-shocked:before{content:"\ec0a"}.bx-shopping-bag:before{content:"\ec0b"}.bx-show:before{content:"\ec0c"}.bx-show-alt:before{content:"\ec0d"}.bx-shuffle:before{content:"\ec0e"}.bx-sidebar:before{content:"\ec0f"}.bx-sitemap:before{content:"\ec10"}.bx-skip-next:before{content:"\ec11"}.bx-skip-next-circle:before{content:"\ec12"}.bx-skip-previous:before{content:"\ec13"}.bx-skip-previous-circle:before{content:"\ec14"}.bx-sleepy:before{content:"\ec15"}.bx-slider:before{content:"\ec16"}.bx-slider-alt:before{content:"\ec17"}.bx-slideshow:before{content:"\ec18"}.bx-smile:before{content:"\ec19"}.bx-sort:before{content:"\ec1a"}.bx-sort-alt-2:before{content:"\ec1b"}.bx-sort-a-z:before{content:"\ec1c"}.bx-sort-down:before{content:"\ec1d"}.bx-sort-up:before{content:"\ec1e"}.bx-sort-z-a:before{content:"\ec1f"}.bx-spa:before{content:"\ec20"}.bx-space-bar:before{content:"\ec21"}.bx-speaker:before{content:"\ec22"}.bx-spray-can:before{content:"\ec23"}.bx-spreadsheet:before{content:"\ec24"}.bx-square:before{content:"\ec25"}.bx-square-rounded:before{content:"\ec26"}.bx-star:before{content:"\ec27"}.bx-station:before{content:"\ec28"}.bx-stats:before{content:"\ec29"}.bx-sticker:before{content:"\ec2a"}.bx-stop:before{content:"\ec2b"}.bx-stop-circle:before{content:"\ec2c"}.bx-stopwatch:before{content:"\ec2d"}.bx-store:before{content:"\ec2e"}.bx-store-alt:before{content:"\ec2f"}.bx-street-view:before{content:"\ec30"}.bx-strikethrough:before{content:"\ec31"}.bx-subdirectory-left:before{content:"\ec32"}.bx-subdirectory-right:before{content:"\ec33"}.bx-sun:before{content:"\ec34"}.bx-support:before{content:"\ec35"}.bx-swim:before{content:"\ec36"}.bx-sync:before{content:"\ec37"}.bx-tab:before{content:"\ec38"}.bx-table:before{content:"\ec39"}.bx-tachometer:before{content:"\ec3a"}.bx-tag:before{content:"\ec3b"}.bx-tag-alt:before{content:"\ec3c"}.bx-target-lock:before{content:"\ec3d"}.bx-task:before{content:"\ec3e"}.bx-task-x:before{content:"\ec3f"}.bx-taxi:before{content:"\ec40"}.bx-tennis-ball:before{content:"\ec41"}.bx-terminal:before{content:"\ec42"}.bx-test-tube:before{content:"\ec43"}.bx-text:before{content:"\ec44"}.bx-time:before{content:"\ec45"}.bx-time-five:before{content:"\ec46"}.bx-timer:before{content:"\ec47"}.bx-tired:before{content:"\ec48"}.bx-toggle-left:before{content:"\ec49"}.bx-toggle-right:before{content:"\ec4a"}.bx-tone:before{content:"\ec4b"}.bx-traffic-cone:before{content:"\ec4c"}.bx-train:before{content:"\ec4d"}.bx-transfer:before{content:"\ec4e"}.bx-transfer-alt:before{content:"\ec4f"}.bx-trash:before{content:"\ec50"}.bx-trash-alt:before{content:"\ec51"}.bx-trending-down:before{content:"\ec52"}.bx-trending-up:before{content:"\ec53"}.bx-trim:before{content:"\ec54"}.bx-trip:before{content:"\ec55"}.bx-trophy:before{content:"\ec56"}.bx-tv:before{content:"\ec57"}.bx-underline:before{content:"\ec58"}.bx-undo:before{content:"\ec59"}.bx-unite:before{content:"\ec5a"}.bx-unlink:before{content:"\ec5b"}.bx-up-arrow:before{content:"\ec5c"}.bx-up-arrow-alt:before{content:"\ec5d"}.bx-up-arrow-circle:before{content:"\ec5e"}.bx-upload:before{content:"\ec5f"}.bx-upside-down:before{content:"\ec60"}.bx-upvote:before{content:"\ec61"}.bx-usb:before{content:"\ec62"}.bx-user:before{content:"\ec63"}.bx-user-check:before{content:"\ec64"}.bx-user-circle:before{content:"\ec65"}.bx-user-minus:before{content:"\ec66"}.bx-user-pin:before{content:"\ec67"}.bx-user-plus:before{content:"\ec68"}.bx-user-voice:before{content:"\ec69"}.bx-user-x:before{content:"\ec6a"}.bx-vector:before{content:"\ec6b"}.bx-vertical-center:before{content:"\ec6c"}.bx-vial:before{content:"\ec6d"}.bx-video:before{content:"\ec6e"}.bx-video-off:before{content:"\ec6f"}.bx-video-plus:before{content:"\ec70"}.bx-video-recording:before{content:"\ec71"}.bx-voicemail:before{content:"\ec72"}.bx-volume:before{content:"\ec73"}.bx-volume-full:before{content:"\ec74"}.bx-volume-low:before{content:"\ec75"}.bx-volume-mute:before{content:"\ec76"}.bx-walk:before{content:"\ec77"}.bx-wallet:before{content:"\ec78"}.bx-wallet-alt:before{content:"\ec79"}.bx-water:before{content:"\ec7a"}.bx-webcam:before{content:"\ec7b"}.bx-wifi:before{content:"\ec7c"}.bx-wifi-0:before{content:"\ec7d"}.bx-wifi-1:before{content:"\ec7e"}.bx-wifi-2:before{content:"\ec7f"}.bx-wifi-off:before{content:"\ec80"}.bx-wind:before{content:"\ec81"}.bx-window:before{content:"\ec82"}.bx-window-alt:before{content:"\ec83"}.bx-window-close:before{content:"\ec84"}.bx-window-open:before{content:"\ec85"}.bx-windows:before{content:"\ec86"}.bx-wine:before{content:"\ec87"}.bx-wink-smile:before{content:"\ec88"}.bx-wink-tongue:before{content:"\ec89"}.bx-won:before{content:"\ec8a"}.bx-world:before{content:"\ec8b"}.bx-wrench:before{content:"\ec8c"}.bx-x:before{content:"\ec8d"}.bx-x-circle:before{content:"\ec8e"}.bx-yen:before{content:"\ec8f"}.bx-zoom-in:before{content:"\ec90"}.bx-zoom-out:before{content:"\ec91"}.bxs-party:before{content:"\ec92"}.bxs-hot:before{content:"\ec93"}.bxs-droplet:before{content:"\ec94"}.bxs-cat:before{content:"\ec95"}.bxs-dog:before{content:"\ec96"}.bxs-injection:before{content:"\ec97"}.bxs-leaf:before{content:"\ec98"}.bxs-add-to-queue:before{content:"\ec99"}.bxs-adjust:before{content:"\ec9a"}.bxs-adjust-alt:before{content:"\ec9b"}.bxs-alarm:before{content:"\ec9c"}.bxs-alarm-add:before{content:"\ec9d"}.bxs-alarm-exclamation:before{content:"\ec9e"}.bxs-alarm-off:before{content:"\ec9f"}.bxs-alarm-snooze:before{content:"\eca0"}.bxs-album:before{content:"\eca1"}.bxs-ambulance:before{content:"\eca2"}.bxs-analyse:before{content:"\eca3"}.bxs-angry:before{content:"\eca4"}.bxs-arch:before{content:"\eca5"}.bxs-archive:before{content:"\eca6"}.bxs-archive-in:before{content:"\eca7"}.bxs-archive-out:before{content:"\eca8"}.bxs-area:before{content:"\eca9"}.bxs-arrow-from-bottom:before{content:"\ecaa"}.bxs-arrow-from-left:before{content:"\ecab"}.bxs-arrow-from-right:before{content:"\ecac"}.bxs-arrow-from-top:before{content:"\ecad"}.bxs-arrow-to-bottom:before{content:"\ecae"}.bxs-arrow-to-left:before{content:"\ecaf"}.bxs-arrow-to-right:before{content:"\ecb0"}.bxs-arrow-to-top:before{content:"\ecb1"}.bxs-award:before{content:"\ecb2"}.bxs-baby-carriage:before{content:"\ecb3"}.bxs-backpack:before{content:"\ecb4"}.bxs-badge:before{content:"\ecb5"}.bxs-badge-check:before{content:"\ecb6"}.bxs-badge-dollar:before{content:"\ecb7"}.bxs-ball:before{content:"\ecb8"}.bxs-band-aid:before{content:"\ecb9"}.bxs-bank:before{content:"\ecba"}.bxs-bar-chart-alt-2:before{content:"\ecbb"}.bxs-bar-chart-square:before{content:"\ecbc"}.bxs-barcode:before{content:"\ecbd"}.bxs-baseball:before{content:"\ecbe"}.bxs-basket:before{content:"\ecbf"}.bxs-basketball:before{content:"\ecc0"}.bxs-bath:before{content:"\ecc1"}.bxs-battery:before{content:"\ecc2"}.bxs-battery-charging:before{content:"\ecc3"}.bxs-battery-full:before{content:"\ecc4"}.bxs-battery-low:before{content:"\ecc5"}.bxs-bed:before{content:"\ecc6"}.bxs-been-here:before{content:"\ecc7"}.bxs-beer:before{content:"\ecc8"}.bxs-bell:before{content:"\ecc9"}.bxs-bell-minus:before{content:"\ecca"}.bxs-bell-off:before{content:"\eccb"}.bxs-bell-plus:before{content:"\eccc"}.bxs-bell-ring:before{content:"\eccd"}.bxs-bible:before{content:"\ecce"}.bxs-binoculars:before{content:"\eccf"}.bxs-blanket:before{content:"\ecd0"}.bxs-bolt:before{content:"\ecd1"}.bxs-bolt-circle:before{content:"\ecd2"}.bxs-bomb:before{content:"\ecd3"}.bxs-bone:before{content:"\ecd4"}.bxs-bong:before{content:"\ecd5"}.bxs-book:before{content:"\ecd6"}.bxs-book-add:before{content:"\ecd7"}.bxs-book-alt:before{content:"\ecd8"}.bxs-book-bookmark:before{content:"\ecd9"}.bxs-book-content:before{content:"\ecda"}.bxs-book-heart:before{content:"\ecdb"}.bxs-bookmark:before{content:"\ecdc"}.bxs-bookmark-alt:before{content:"\ecdd"}.bxs-bookmark-alt-minus:before{content:"\ecde"}.bxs-bookmark-alt-plus:before{content:"\ecdf"}.bxs-bookmark-heart:before{content:"\ece0"}.bxs-bookmark-minus:before{content:"\ece1"}.bxs-bookmark-plus:before{content:"\ece2"}.bxs-bookmarks:before{content:"\ece3"}.bxs-bookmark-star:before{content:"\ece4"}.bxs-book-open:before{content:"\ece5"}.bxs-book-reader:before{content:"\ece6"}.bxs-bot:before{content:"\ece7"}.bxs-bowling-ball:before{content:"\ece8"}.bxs-box:before{content:"\ece9"}.bxs-brain:before{content:"\ecea"}.bxs-briefcase:before{content:"\eceb"}.bxs-briefcase-alt:before{content:"\ecec"}.bxs-briefcase-alt-2:before{content:"\eced"}.bxs-brightness:before{content:"\ecee"}.bxs-brightness-half:before{content:"\ecef"}.bxs-brush:before{content:"\ecf0"}.bxs-brush-alt:before{content:"\ecf1"}.bxs-bug:before{content:"\ecf2"}.bxs-bug-alt:before{content:"\ecf3"}.bxs-building:before{content:"\ecf4"}.bxs-building-house:before{content:"\ecf5"}.bxs-buildings:before{content:"\ecf6"}.bxs-bulb:before{content:"\ecf7"}.bxs-bullseye:before{content:"\ecf8"}.bxs-buoy:before{content:"\ecf9"}.bxs-bus:before{content:"\ecfa"}.bxs-business:before{content:"\ecfb"}.bxs-bus-school:before{content:"\ecfc"}.bxs-cabinet:before{content:"\ecfd"}.bxs-cake:before{content:"\ecfe"}.bxs-calculator:before{content:"\ecff"}.bxs-calendar:before{content:"\ed00"}.bxs-calendar-alt:before{content:"\ed01"}.bxs-calendar-check:before{content:"\ed02"}.bxs-calendar-edit:before{content:"\ed03"}.bxs-calendar-event:before{content:"\ed04"}.bxs-calendar-exclamation:before{content:"\ed05"}.bxs-calendar-heart:before{content:"\ed06"}.bxs-calendar-minus:before{content:"\ed07"}.bxs-calendar-plus:before{content:"\ed08"}.bxs-calendar-star:before{content:"\ed09"}.bxs-calendar-week:before{content:"\ed0a"}.bxs-calendar-x:before{content:"\ed0b"}.bxs-camera:before{content:"\ed0c"}.bxs-camera-home:before{content:"\ed0d"}.bxs-camera-movie:before{content:"\ed0e"}.bxs-camera-off:before{content:"\ed0f"}.bxs-camera-plus:before{content:"\ed10"}.bxs-capsule:before{content:"\ed11"}.bxs-captions:before{content:"\ed12"}.bxs-car:before{content:"\ed13"}.bxs-car-battery:before{content:"\ed14"}.bxs-car-crash:before{content:"\ed15"}.bxs-card:before{content:"\ed16"}.bxs-caret-down-circle:before{content:"\ed17"}.bxs-caret-down-square:before{content:"\ed18"}.bxs-caret-left-circle:before{content:"\ed19"}.bxs-caret-left-square:before{content:"\ed1a"}.bxs-caret-right-circle:before{content:"\ed1b"}.bxs-caret-right-square:before{content:"\ed1c"}.bxs-caret-up-circle:before{content:"\ed1d"}.bxs-caret-up-square:before{content:"\ed1e"}.bxs-car-garage:before{content:"\ed1f"}.bxs-car-mechanic:before{content:"\ed20"}.bxs-carousel:before{content:"\ed21"}.bxs-cart:before{content:"\ed22"}.bxs-cart-add:before{content:"\ed23"}.bxs-cart-alt:before{content:"\ed24"}.bxs-cart-download:before{content:"\ed25"}.bxs-car-wash:before{content:"\ed26"}.bxs-category:before{content:"\ed27"}.bxs-category-alt:before{content:"\ed28"}.bxs-cctv:before{content:"\ed29"}.bxs-certification:before{content:"\ed2a"}.bxs-chalkboard:before{content:"\ed2b"}.bxs-chart:before{content:"\ed2c"}.bxs-chat:before{content:"\ed2d"}.bxs-checkbox:before{content:"\ed2e"}.bxs-checkbox-checked:before{content:"\ed2f"}.bxs-checkbox-minus:before{content:"\ed30"}.bxs-check-circle:before{content:"\ed31"}.bxs-check-shield:before{content:"\ed32"}.bxs-check-square:before{content:"\ed33"}.bxs-chess:before{content:"\ed34"}.bxs-chevron-down:before{content:"\ed35"}.bxs-chevron-down-circle:before{content:"\ed36"}.bxs-chevron-down-square:before{content:"\ed37"}.bxs-chevron-left:before{content:"\ed38"}.bxs-chevron-left-circle:before{content:"\ed39"}.bxs-chevron-left-square:before{content:"\ed3a"}.bxs-chevron-right:before{content:"\ed3b"}.bxs-chevron-right-circle:before{content:"\ed3c"}.bxs-chevron-right-square:before{content:"\ed3d"}.bxs-chevrons-down:before{content:"\ed3e"}.bxs-chevrons-left:before{content:"\ed3f"}.bxs-chevrons-right:before{content:"\ed40"}.bxs-chevrons-up:before{content:"\ed41"}.bxs-chevron-up:before{content:"\ed42"}.bxs-chevron-up-circle:before{content:"\ed43"}.bxs-chevron-up-square:before{content:"\ed44"}.bxs-chip:before{content:"\ed45"}.bxs-church:before{content:"\ed46"}.bxs-circle:before{content:"\ed47"}.bxs-city:before{content:"\ed48"}.bxs-clinic:before{content:"\ed49"}.bxs-cloud:before{content:"\ed4a"}.bxs-cloud-download:before{content:"\ed4b"}.bxs-cloud-lightning:before{content:"\ed4c"}.bxs-cloud-rain:before{content:"\ed4d"}.bxs-cloud-upload:before{content:"\ed4e"}.bxs-coffee:before{content:"\ed4f"}.bxs-coffee-alt:before{content:"\ed50"}.bxs-coffee-togo:before{content:"\ed51"}.bxs-cog:before{content:"\ed52"}.bxs-coin:before{content:"\ed53"}.bxs-coin-stack:before{content:"\ed54"}.bxs-collection:before{content:"\ed55"}.bxs-color-fill:before{content:"\ed56"}.bxs-comment:before{content:"\ed57"}.bxs-comment-add:before{content:"\ed58"}.bxs-comment-check:before{content:"\ed59"}.bxs-comment-detail:before{content:"\ed5a"}.bxs-comment-dots:before{content:"\ed5b"}.bxs-comment-edit:before{content:"\ed5c"}.bxs-comment-error:before{content:"\ed5d"}.bxs-comment-minus:before{content:"\ed5e"}.bxs-comment-x:before{content:"\ed5f"}.bxs-compass:before{content:"\ed60"}.bxs-component:before{content:"\ed61"}.bxs-confused:before{content:"\ed62"}.bxs-contact:before{content:"\ed63"}.bxs-conversation:before{content:"\ed64"}.bxs-cookie:before{content:"\ed65"}.bxs-cool:before{content:"\ed66"}.bxs-copy:before{content:"\ed67"}.bxs-copy-alt:before{content:"\ed68"}.bxs-copyright:before{content:"\ed69"}.bxs-coupon:before{content:"\ed6a"}.bxs-credit-card:before{content:"\ed6b"}.bxs-credit-card-alt:before{content:"\ed6c"}.bxs-credit-card-front:before{content:"\ed6d"}.bxs-crop:before{content:"\ed6e"}.bxs-crown:before{content:"\ed6f"}.bxs-cube:before{content:"\ed70"}.bxs-cube-alt:before{content:"\ed71"}.bxs-cuboid:before{content:"\ed72"}.bxs-customize:before{content:"\ed73"}.bxs-cylinder:before{content:"\ed74"}.bxs-dashboard:before{content:"\ed75"}.bxs-data:before{content:"\ed76"}.bxs-detail:before{content:"\ed77"}.bxs-devices:before{content:"\ed78"}.bxs-diamond:before{content:"\ed79"}.bxs-dice-1:before{content:"\ed7a"}.bxs-dice-2:before{content:"\ed7b"}.bxs-dice-3:before{content:"\ed7c"}.bxs-dice-4:before{content:"\ed7d"}.bxs-dice-5:before{content:"\ed7e"}.bxs-dice-6:before{content:"\ed7f"}.bxs-direction-left:before{content:"\ed80"}.bxs-direction-right:before{content:"\ed81"}.bxs-directions:before{content:"\ed82"}.bxs-disc:before{content:"\ed83"}.bxs-discount:before{content:"\ed84"}.bxs-dish:before{content:"\ed85"}.bxs-dislike:before{content:"\ed86"}.bxs-dizzy:before{content:"\ed87"}.bxs-dock-bottom:before{content:"\ed88"}.bxs-dock-left:before{content:"\ed89"}.bxs-dock-right:before{content:"\ed8a"}.bxs-dock-top:before{content:"\ed8b"}.bxs-dollar-circle:before{content:"\ed8c"}.bxs-donate-blood:before{content:"\ed8d"}.bxs-donate-heart:before{content:"\ed8e"}.bxs-door-open:before{content:"\ed8f"}.bxs-doughnut-chart:before{content:"\ed90"}.bxs-down-arrow:before{content:"\ed91"}.bxs-down-arrow-alt:before{content:"\ed92"}.bxs-down-arrow-circle:before{content:"\ed93"}.bxs-down-arrow-square:before{content:"\ed94"}.bxs-download:before{content:"\ed95"}.bxs-downvote:before{content:"\ed96"}.bxs-drink:before{content:"\ed97"}.bxs-droplet-half:before{content:"\ed98"}.bxs-dryer:before{content:"\ed99"}.bxs-duplicate:before{content:"\ed9a"}.bxs-edit:before{content:"\ed9b"}.bxs-edit-alt:before{content:"\ed9c"}.bxs-edit-location:before{content:"\ed9d"}.bxs-eject:before{content:"\ed9e"}.bxs-envelope:before{content:"\ed9f"}.bxs-envelope-open:before{content:"\eda0"}.bxs-eraser:before{content:"\eda1"}.bxs-error:before{content:"\eda2"}.bxs-error-alt:before{content:"\eda3"}.bxs-error-circle:before{content:"\eda4"}.bxs-ev-station:before{content:"\eda5"}.bxs-exit:before{content:"\eda6"}.bxs-extension:before{content:"\eda7"}.bxs-eyedropper:before{content:"\eda8"}.bxs-face:before{content:"\eda9"}.bxs-face-mask:before{content:"\edaa"}.bxs-factory:before{content:"\edab"}.bxs-fast-forward-circle:before{content:"\edac"}.bxs-file:before{content:"\edad"}.bxs-file-archive:before{content:"\edae"}.bxs-file-blank:before{content:"\edaf"}.bxs-file-css:before{content:"\edb0"}.bxs-file-doc:before{content:"\edb1"}.bxs-file-export:before{content:"\edb2"}.bxs-file-find:before{content:"\edb3"}.bxs-file-gif:before{content:"\edb4"}.bxs-file-html:before{content:"\edb5"}.bxs-file-image:before{content:"\edb6"}.bxs-file-import:before{content:"\edb7"}.bxs-file-jpg:before{content:"\edb8"}.bxs-file-js:before{content:"\edb9"}.bxs-file-json:before{content:"\edba"}.bxs-file-md:before{content:"\edbb"}.bxs-file-pdf:before{content:"\edbc"}.bxs-file-plus:before{content:"\edbd"}.bxs-file-png:before{content:"\edbe"}.bxs-file-txt:before{content:"\edbf"}.bxs-film:before{content:"\edc0"}.bxs-filter-alt:before{content:"\edc1"}.bxs-first-aid:before{content:"\edc2"}.bxs-flag:before{content:"\edc3"}.bxs-flag-alt:before{content:"\edc4"}.bxs-flag-checkered:before{content:"\edc5"}.bxs-flame:before{content:"\edc6"}.bxs-flask:before{content:"\edc7"}.bxs-florist:before{content:"\edc8"}.bxs-folder:before{content:"\edc9"}.bxs-folder-minus:before{content:"\edca"}.bxs-folder-open:before{content:"\edcb"}.bxs-folder-plus:before{content:"\edcc"}.bxs-food-menu:before{content:"\edcd"}.bxs-fridge:before{content:"\edce"}.bxs-game:before{content:"\edcf"}.bxs-gas-pump:before{content:"\edd0"}.bxs-ghost:before{content:"\edd1"}.bxs-gift:before{content:"\edd2"}.bxs-graduation:before{content:"\edd3"}.bxs-grid:before{content:"\edd4"}.bxs-grid-alt:before{content:"\edd5"}.bxs-group:before{content:"\edd6"}.bxs-guitar-amp:before{content:"\edd7"}.bxs-hand:before{content:"\edd8"}.bxs-hand-down:before{content:"\edd9"}.bxs-hand-left:before{content:"\edda"}.bxs-hand-right:before{content:"\eddb"}.bxs-hand-up:before{content:"\eddc"}.bxs-happy:before{content:"\eddd"}.bxs-happy-alt:before{content:"\edde"}.bxs-happy-beaming:before{content:"\eddf"}.bxs-happy-heart-eyes:before{content:"\ede0"}.bxs-hdd:before{content:"\ede1"}.bxs-heart:before{content:"\ede2"}.bxs-heart-circle:before{content:"\ede3"}.bxs-heart-square:before{content:"\ede4"}.bxs-help-circle:before{content:"\ede5"}.bxs-hide:before{content:"\ede6"}.bxs-home:before{content:"\ede7"}.bxs-home-circle:before{content:"\ede8"}.bxs-home-heart:before{content:"\ede9"}.bxs-home-smile:before{content:"\edea"}.bxs-hotel:before{content:"\edeb"}.bxs-hourglass:before{content:"\edec"}.bxs-hourglass-bottom:before{content:"\eded"}.bxs-hourglass-top:before{content:"\edee"}.bxs-id-card:before{content:"\edef"}.bxs-image:before{content:"\edf0"}.bxs-image-add:before{content:"\edf1"}.bxs-image-alt:before{content:"\edf2"}.bxs-inbox:before{content:"\edf3"}.bxs-info-circle:before{content:"\edf4"}.bxs-info-square:before{content:"\edf5"}.bxs-institution:before{content:"\edf6"}.bxs-joystick:before{content:"\edf7"}.bxs-joystick-alt:before{content:"\edf8"}.bxs-joystick-button:before{content:"\edf9"}.bxs-key:before{content:"\edfa"}.bxs-keyboard:before{content:"\edfb"}.bxs-label:before{content:"\edfc"}.bxs-landmark:before{content:"\edfd"}.bxs-landscape:before{content:"\edfe"}.bxs-laugh:before{content:"\edff"}.bxs-layer:before{content:"\ee00"}.bxs-layer-minus:before{content:"\ee01"}.bxs-layer-plus:before{content:"\ee02"}.bxs-layout:before{content:"\ee03"}.bxs-left-arrow:before{content:"\ee04"}.bxs-left-arrow-alt:before{content:"\ee05"}.bxs-left-arrow-circle:before{content:"\ee06"}.bxs-left-arrow-square:before{content:"\ee07"}.bxs-left-down-arrow-circle:before{content:"\ee08"}.bxs-left-top-arrow-circle:before{content:"\ee09"}.bxs-like:before{content:"\ee0a"}.bxs-location-plus:before{content:"\ee0b"}.bxs-lock:before{content:"\ee0c"}.bxs-lock-alt:before{content:"\ee0d"}.bxs-lock-open:before{content:"\ee0e"}.bxs-lock-open-alt:before{content:"\ee0f"}.bxs-log-in:before{content:"\ee10"}.bxs-log-in-circle:before{content:"\ee11"}.bxs-log-out:before{content:"\ee12"}.bxs-log-out-circle:before{content:"\ee13"}.bxs-low-vision:before{content:"\ee14"}.bxs-magic-wand:before{content:"\ee15"}.bxs-magnet:before{content:"\ee16"}.bxs-map:before{content:"\ee17"}.bxs-map-alt:before{content:"\ee18"}.bxs-map-pin:before{content:"\ee19"}.bxs-mask:before{content:"\ee1a"}.bxs-medal:before{content:"\ee1b"}.bxs-megaphone:before{content:"\ee1c"}.bxs-meh:before{content:"\ee1d"}.bxs-meh-alt:before{content:"\ee1e"}.bxs-meh-blank:before{content:"\ee1f"}.bxs-memory-card:before{content:"\ee20"}.bxs-message:before{content:"\ee21"}.bxs-message-add:before{content:"\ee22"}.bxs-message-alt:before{content:"\ee23"}.bxs-message-alt-add:before{content:"\ee24"}.bxs-message-alt-check:before{content:"\ee25"}.bxs-message-alt-detail:before{content:"\ee26"}.bxs-message-alt-dots:before{content:"\ee27"}.bxs-message-alt-edit:before{content:"\ee28"}.bxs-message-alt-error:before{content:"\ee29"}.bxs-message-alt-minus:before{content:"\ee2a"}.bxs-message-alt-x:before{content:"\ee2b"}.bxs-message-check:before{content:"\ee2c"}.bxs-message-detail:before{content:"\ee2d"}.bxs-message-dots:before{content:"\ee2e"}.bxs-message-edit:before{content:"\ee2f"}.bxs-message-error:before{content:"\ee30"}.bxs-message-minus:before{content:"\ee31"}.bxs-message-rounded:before{content:"\ee32"}.bxs-message-rounded-add:before{content:"\ee33"}.bxs-message-rounded-check:before{content:"\ee34"}.bxs-message-rounded-detail:before{content:"\ee35"}.bxs-message-rounded-dots:before{content:"\ee36"}.bxs-message-rounded-edit:before{content:"\ee37"}.bxs-message-rounded-error:before{content:"\ee38"}.bxs-message-rounded-minus:before{content:"\ee39"}.bxs-message-rounded-x:before{content:"\ee3a"}.bxs-message-square:before{content:"\ee3b"}.bxs-message-square-add:before{content:"\ee3c"}.bxs-message-square-check:before{content:"\ee3d"}.bxs-message-square-detail:before{content:"\ee3e"}.bxs-message-square-dots:before{content:"\ee3f"}.bxs-message-square-edit:before{content:"\ee40"}.bxs-message-square-error:before{content:"\ee41"}.bxs-message-square-minus:before{content:"\ee42"}.bxs-message-square-x:before{content:"\ee43"}.bxs-message-x:before{content:"\ee44"}.bxs-meteor:before{content:"\ee45"}.bxs-microchip:before{content:"\ee46"}.bxs-microphone:before{content:"\ee47"}.bxs-microphone-alt:before{content:"\ee48"}.bxs-microphone-off:before{content:"\ee49"}.bxs-minus-circle:before{content:"\ee4a"}.bxs-minus-square:before{content:"\ee4b"}.bxs-mobile:before{content:"\ee4c"}.bxs-mobile-vibration:before{content:"\ee4d"}.bxs-moon:before{content:"\ee4e"}.bxs-mouse:before{content:"\ee4f"}.bxs-mouse-alt:before{content:"\ee50"}.bxs-movie:before{content:"\ee51"}.bxs-movie-play:before{content:"\ee52"}.bxs-music:before{content:"\ee53"}.bxs-navigation:before{content:"\ee54"}.bxs-network-chart:before{content:"\ee55"}.bxs-news:before{content:"\ee56"}.bxs-no-entry:before{content:"\ee57"}.bxs-note:before{content:"\ee58"}.bxs-notepad:before{content:"\ee59"}.bxs-notification:before{content:"\ee5a"}.bxs-notification-off:before{content:"\ee5b"}.bxs-offer:before{content:"\ee5c"}.bxs-package:before{content:"\ee5d"}.bxs-paint:before{content:"\ee5e"}.bxs-paint-roll:before{content:"\ee5f"}.bxs-palette:before{content:"\ee60"}.bxs-paper-plane:before{content:"\ee61"}.bxs-parking:before{content:"\ee62"}.bxs-paste:before{content:"\ee63"}.bxs-pen:before{content:"\ee64"}.bxs-pencil:before{content:"\ee65"}.bxs-phone:before{content:"\ee66"}.bxs-phone-call:before{content:"\ee67"}.bxs-phone-incoming:before{content:"\ee68"}.bxs-phone-off:before{content:"\ee69"}.bxs-phone-outgoing:before{content:"\ee6a"}.bxs-photo-album:before{content:"\ee6b"}.bxs-piano:before{content:"\ee6c"}.bxs-pie-chart:before{content:"\ee6d"}.bxs-pie-chart-alt:before{content:"\ee6e"}.bxs-pie-chart-alt-2:before{content:"\ee6f"}.bxs-pin:before{content:"\ee70"}.bxs-pizza:before{content:"\ee71"}.bxs-plane:before{content:"\ee72"}.bxs-plane-alt:before{content:"\ee73"}.bxs-plane-land:before{content:"\ee74"}.bxs-planet:before{content:"\ee75"}.bxs-plane-take-off:before{content:"\ee76"}.bxs-playlist:before{content:"\ee77"}.bxs-plug:before{content:"\ee78"}.bxs-plus-circle:before{content:"\ee79"}.bxs-plus-square:before{content:"\ee7a"}.bxs-pointer:before{content:"\ee7b"}.bxs-polygon:before{content:"\ee7c"}.bxs-printer:before{content:"\ee7d"}.bxs-purchase-tag:before{content:"\ee7e"}.bxs-purchase-tag-alt:before{content:"\ee7f"}.bxs-pyramid:before{content:"\ee80"}.bxs-quote-alt-left:before{content:"\ee81"}.bxs-quote-alt-right:before{content:"\ee82"}.bxs-quote-left:before{content:"\ee83"}.bxs-quote-right:before{content:"\ee84"}.bxs-quote-single-left:before{content:"\ee85"}.bxs-quote-single-right:before{content:"\ee86"}.bxs-radiation:before{content:"\ee87"}.bxs-radio:before{content:"\ee88"}.bxs-receipt:before{content:"\ee89"}.bxs-rectangle:before{content:"\ee8a"}.bxs-registered:before{content:"\ee8b"}.bxs-rename:before{content:"\ee8c"}.bxs-report:before{content:"\ee8d"}.bxs-rewind-circle:before{content:"\ee8e"}.bxs-right-arrow:before{content:"\ee8f"}.bxs-right-arrow-alt:before{content:"\ee90"}.bxs-right-arrow-circle:before{content:"\ee91"}.bxs-right-arrow-square:before{content:"\ee92"}.bxs-right-down-arrow-circle:before{content:"\ee93"}.bxs-right-top-arrow-circle:before{content:"\ee94"}.bxs-rocket:before{content:"\ee95"}.bxs-ruler:before{content:"\ee96"}.bxs-sad:before{content:"\ee97"}.bxs-save:before{content:"\ee98"}.bxs-school:before{content:"\ee99"}.bxs-search:before{content:"\ee9a"}.bxs-search-alt-2:before{content:"\ee9b"}.bxs-select-multiple:before{content:"\ee9c"}.bxs-send:before{content:"\ee9d"}.bxs-server:before{content:"\ee9e"}.bxs-shapes:before{content:"\ee9f"}.bxs-share:before{content:"\eea0"}.bxs-share-alt:before{content:"\eea1"}.bxs-shield:before{content:"\eea2"}.bxs-shield-alt-2:before{content:"\eea3"}.bxs-shield-x:before{content:"\eea4"}.bxs-ship:before{content:"\eea5"}.bxs-shocked:before{content:"\eea6"}.bxs-shopping-bag:before{content:"\eea7"}.bxs-shopping-bag-alt:before{content:"\eea8"}.bxs-shopping-bags:before{content:"\eea9"}.bxs-show:before{content:"\eeaa"}.bxs-skip-next-circle:before{content:"\eeab"}.bxs-skip-previous-circle:before{content:"\eeac"}.bxs-skull:before{content:"\eead"}.bxs-sleepy:before{content:"\eeae"}.bxs-slideshow:before{content:"\eeaf"}.bxs-smile:before{content:"\eeb0"}.bxs-sort-alt:before{content:"\eeb1"}.bxs-spa:before{content:"\eeb2"}.bxs-speaker:before{content:"\eeb3"}.bxs-spray-can:before{content:"\eeb4"}.bxs-spreadsheet:before{content:"\eeb5"}.bxs-square:before{content:"\eeb6"}.bxs-square-rounded:before{content:"\eeb7"}.bxs-star:before{content:"\eeb8"}.bxs-star-half:before{content:"\eeb9"}.bxs-sticker:before{content:"\eeba"}.bxs-stopwatch:before{content:"\eebb"}.bxs-store:before{content:"\eebc"}.bxs-store-alt:before{content:"\eebd"}.bxs-sun:before{content:"\eebe"}.bxs-tachometer:before{content:"\eebf"}.bxs-tag:before{content:"\eec0"}.bxs-tag-alt:before{content:"\eec1"}.bxs-tag-x:before{content:"\eec2"}.bxs-taxi:before{content:"\eec3"}.bxs-tennis-ball:before{content:"\eec4"}.bxs-terminal:before{content:"\eec5"}.bxs-thermometer:before{content:"\eec6"}.bxs-time:before{content:"\eec7"}.bxs-time-five:before{content:"\eec8"}.bxs-timer:before{content:"\eec9"}.bxs-tired:before{content:"\eeca"}.bxs-toggle-left:before{content:"\eecb"}.bxs-toggle-right:before{content:"\eecc"}.bxs-tone:before{content:"\eecd"}.bxs-torch:before{content:"\eece"}.bxs-to-top:before{content:"\eecf"}.bxs-traffic:before{content:"\eed0"}.bxs-traffic-barrier:before{content:"\eed1"}.bxs-traffic-cone:before{content:"\eed2"}.bxs-train:before{content:"\eed3"}.bxs-trash:before{content:"\eed4"}.bxs-trash-alt:before{content:"\eed5"}.bxs-tree:before{content:"\eed6"}.bxs-trophy:before{content:"\eed7"}.bxs-truck:before{content:"\eed8"}.bxs-t-shirt:before{content:"\eed9"}.bxs-tv:before{content:"\eeda"}.bxs-up-arrow:before{content:"\eedb"}.bxs-up-arrow-alt:before{content:"\eedc"}.bxs-up-arrow-circle:before{content:"\eedd"}.bxs-up-arrow-square:before{content:"\eede"}.bxs-upside-down:before{content:"\eedf"}.bxs-upvote:before{content:"\eee0"}.bxs-user:before{content:"\eee1"}.bxs-user-account:before{content:"\eee2"}.bxs-user-badge:before{content:"\eee3"}.bxs-user-check:before{content:"\eee4"}.bxs-user-circle:before{content:"\eee5"}.bxs-user-detail:before{content:"\eee6"}.bxs-user-minus:before{content:"\eee7"}.bxs-user-pin:before{content:"\eee8"}.bxs-user-plus:before{content:"\eee9"}.bxs-user-rectangle:before{content:"\eeea"}.bxs-user-voice:before{content:"\eeeb"}.bxs-user-x:before{content:"\eeec"}.bxs-vector:before{content:"\eeed"}.bxs-vial:before{content:"\eeee"}.bxs-video:before{content:"\eeef"}.bxs-video-off:before{content:"\eef0"}.bxs-video-plus:before{content:"\eef1"}.bxs-video-recording:before{content:"\eef2"}.bxs-videos:before{content:"\eef3"}.bxs-virus:before{content:"\eef4"}.bxs-virus-block:before{content:"\eef5"}.bxs-volume:before{content:"\eef6"}.bxs-volume-full:before{content:"\eef7"}.bxs-volume-low:before{content:"\eef8"}.bxs-volume-mute:before{content:"\eef9"}.bxs-wallet:before{content:"\eefa"}.bxs-wallet-alt:before{content:"\eefb"}.bxs-washer:before{content:"\eefc"}.bxs-watch:before{content:"\eefd"}.bxs-watch-alt:before{content:"\eefe"}.bxs-webcam:before{content:"\eeff"}.bxs-widget:before{content:"\ef00"}.bxs-window-alt:before{content:"\ef01"}.bxs-wine:before{content:"\ef02"}.bxs-wink-smile:before{content:"\ef03"}.bxs-wink-tongue:before{content:"\ef04"}.bxs-wrench:before{content:"\ef05"}.bxs-x-circle:before{content:"\ef06"}.bxs-x-square:before{content:"\ef07"}.bxs-yin-yang:before{content:"\ef08"}.bxs-zap:before{content:"\ef09"}.bxs-zoom-in:before{content:"\ef0a"}.bxs-zoom-out:before{content:"\ef0b"}
\ No newline at end of file
diff --git a/themes/30coffe/assets/css/magnific-popup.min.css b/themes/30coffe/assets/css/magnific-popup.min.css
new file mode 100644
index 00000000..c00c6ff9
--- /dev/null
+++ b/themes/30coffe/assets/css/magnific-popup.min.css
@@ -0,0 +1 @@
+.mfp-bg{top:0;left:0;width:100%;height:100%;z-index:1042;overflow:hidden;position:fixed;background:#0b0b0b;opacity:.8}.mfp-wrap{top:0;left:0;width:100%;height:100%;z-index:1043;position:fixed;outline:none !important;-webkit-backface-visibility:hidden}.mfp-container{text-align:center;position:absolute;width:100%;height:100%;left:0;top:0;padding:0 8px;box-sizing:border-box}.mfp-container:before{content:'';display:inline-block;height:100%;vertical-align:middle}.mfp-align-top .mfp-container:before{display:none}.mfp-content{position:relative;display:inline-block;vertical-align:middle;margin:0 auto;text-align:left;z-index:1045}.mfp-inline-holder .mfp-content,.mfp-ajax-holder .mfp-content{width:100%;cursor:auto}.mfp-ajax-cur{cursor:progress}.mfp-zoom-out-cur,.mfp-zoom-out-cur .mfp-image-holder .mfp-close{cursor:-moz-zoom-out;cursor:-webkit-zoom-out;cursor:zoom-out}.mfp-zoom{cursor:pointer;cursor:-webkit-zoom-in;cursor:-moz-zoom-in;cursor:zoom-in}.mfp-auto-cursor .mfp-content{cursor:auto}.mfp-close,.mfp-arrow,.mfp-preloader,.mfp-counter{-webkit-user-select:none;-moz-user-select:none;user-select:none}.mfp-loading.mfp-figure{display:none}.mfp-hide{display:none !important}.mfp-preloader{color:#CCC;position:absolute;top:50%;width:auto;text-align:center;margin-top:-0.8em;left:8px;right:8px;z-index:1044}.mfp-preloader a{color:#CCC}.mfp-preloader a:hover{color:#FFF}.mfp-s-ready .mfp-preloader{display:none}.mfp-s-error .mfp-content{display:none}button.mfp-close,button.mfp-arrow{overflow:visible;cursor:pointer;background:transparent;border:0;-webkit-appearance:none;display:block;outline:0;padding:0;z-index:1046;box-shadow:none;touch-action:manipulation}button::-moz-focus-inner{padding:0;border:0}.mfp-close{width:44px;height:44px;line-height:44px;position:absolute;right:0;top:0;text-decoration:none;text-align:center;opacity:.65;padding:0 0 18px 10px;color:#FFF;font-style:normal;font-size:28px;font-family:Arial,Baskerville,monospace}.mfp-close:hover,.mfp-close:focus{opacity:1}.mfp-close:active{top:1px}.mfp-close-btn-in .mfp-close{color:#333}.mfp-image-holder .mfp-close,.mfp-iframe-holder .mfp-close{color:#FFF;right:-6px;text-align:right;padding-right:6px;width:100%}.mfp-counter{position:absolute;top:0;right:0;color:#CCC;font-size:12px;line-height:18px;white-space:nowrap}.mfp-arrow{position:absolute;opacity:.65;margin:0;top:50%;margin-top:-55px;padding:0;width:90px;height:110px;-webkit-tap-highlight-color:transparent}.mfp-arrow:active{margin-top:-54px}.mfp-arrow:hover,.mfp-arrow:focus{opacity:1}.mfp-arrow:before,.mfp-arrow:after{content:'';display:block;width:0;height:0;position:absolute;left:0;top:0;margin-top:35px;margin-left:35px;border:medium inset transparent}.mfp-arrow:after{border-top-width:13px;border-bottom-width:13px;top:8px}.mfp-arrow:before{border-top-width:21px;border-bottom-width:21px;opacity:.7}.mfp-arrow-left{left:0}.mfp-arrow-left:after{border-right:17px solid #FFF;margin-left:31px}.mfp-arrow-left:before{margin-left:25px;border-right:27px solid #3f3f3f}.mfp-arrow-right{right:0}.mfp-arrow-right:after{border-left:17px solid #FFF;margin-left:39px}.mfp-arrow-right:before{border-left:27px solid #3f3f3f}.mfp-iframe-holder{padding-top:40px;padding-bottom:40px}.mfp-iframe-holder .mfp-content{line-height:0;width:100%;max-width:900px}.mfp-iframe-holder .mfp-close{top:-40px}.mfp-iframe-scaler{width:100%;height:0;overflow:hidden;padding-top:56.25%}.mfp-iframe-scaler iframe{position:absolute;display:block;top:0;left:0;width:100%;height:100%;box-shadow:0 0 8px rgba(0,0,0,0.6);background:#000}img.mfp-img{width:auto;max-width:100%;height:auto;display:block;line-height:0;box-sizing:border-box;padding:40px 0 40px;margin:0 auto}.mfp-figure{line-height:0}.mfp-figure:after{content:'';position:absolute;left:0;top:40px;bottom:40px;display:block;right:0;width:auto;height:auto;z-index:-1;box-shadow:0 0 8px rgba(0,0,0,0.6);background:#444}.mfp-figure small{color:#bdbdbd;display:block;font-size:12px;line-height:14px}.mfp-figure figure{margin:0}.mfp-bottom-bar{margin-top:-36px;position:absolute;top:100%;left:0;width:100%;cursor:auto}.mfp-title{text-align:left;line-height:18px;color:#f3f3f3;word-wrap:break-word;padding-right:36px}.mfp-image-holder .mfp-content{max-width:100%}.mfp-gallery .mfp-image-holder .mfp-figure{cursor:pointer}@media screen and (max-width:800px) and (orientation:landscape),screen and (max-height:300px){.mfp-img-mobile .mfp-image-holder{padding-left:0;padding-right:0}.mfp-img-mobile img.mfp-img{padding:0}.mfp-img-mobile .mfp-figure:after{top:0;bottom:0}.mfp-img-mobile .mfp-figure small{display:inline;margin-left:5px}.mfp-img-mobile .mfp-bottom-bar{background:rgba(0,0,0,0.6);bottom:0;margin:0;top:auto;padding:3px 5px;position:fixed;box-sizing:border-box}.mfp-img-mobile .mfp-bottom-bar:empty{padding:0}.mfp-img-mobile .mfp-counter{right:5px;top:3px}.mfp-img-mobile .mfp-close{top:0;right:0;width:35px;height:35px;line-height:35px;background:rgba(0,0,0,0.6);position:fixed;text-align:center;padding:0}}@media all and (max-width:900px){.mfp-arrow{-webkit-transform:scale(0.75);transform:scale(0.75)}.mfp-arrow-left{-webkit-transform-origin:0 0;transform-origin:0 0}.mfp-arrow-right{-webkit-transform-origin:100%;transform-origin:100%}.mfp-container{padding-left:6px;padding-right:6px}}
\ No newline at end of file
diff --git a/themes/30coffe/assets/css/meanmenu.css b/themes/30coffe/assets/css/meanmenu.css
new file mode 100644
index 00000000..02b77f43
--- /dev/null
+++ b/themes/30coffe/assets/css/meanmenu.css
@@ -0,0 +1 @@
+@import "https://fonts.googleapis.com/css?family=Oswald:200,300,400,500,600,700";body{font-family:oswald,sans-serif;padding:0;margin:0;font-size:15px;background-color:#f1f1f1}a.meanmenu-reveal{display:none}.mean-container .mean-bar{float:left;width:100%;position:absolute;background:0 0;padding:20px 0 0;z-index:999;border-bottom:1px solid rgba(0,0,0,.03);height:55px}.mean-container a.meanmenu-reveal{width:35px;height:30px;padding:12px 15px 0 0;position:absolute;right:0;cursor:pointer;color:#fff;text-decoration:none;font-size:16px;text-indent:-9999em;line-height:22px;font-size:1px;display:block;font-weight:700}.mean-container a.meanmenu-reveal span{display:block;background:#fff;height:4px;margin-top:3px;border-radius:3px}.mean-container .mean-nav{float:left;width:100%;background:#fff;margin-top:55px}.mean-container .mean-nav ul{padding:0;margin:0;width:100%;border:none;list-style-type:none}.mean-container .mean-nav ul li{position:relative;float:left;width:100%}.mean-container .mean-nav ul li a{display:block;float:left;width:90%;padding:1em 5%;margin:0;text-align:left;color:#677294;border-top:1px solid #dbeefd;text-decoration:none}.mean-container .mean-nav ul li a.active{color:#000}.mean-container .mean-nav ul li li a{width:80%;padding:1em 10%;color:#677294;border-top:1px solid #dbeefd;opacity:1;filter:alpha(opacity=75);text-shadow:none!important;visibility:visible;text-transform:none;font-size:14px}.mean-container .mean-nav ul li.mean-last a{border-bottom:none;margin-bottom:0}.mean-container .mean-nav ul li li li a{width:70%;padding:1em 15%}.mean-container .mean-nav ul li li li li a{width:60%;padding:1em 20%}.mean-container .mean-nav ul li li li li li a{width:50%;padding:1em 25%}.mean-container .mean-nav ul li a:hover{background:#252525;background:rgba(255,255,255,.1)}.mean-container .mean-nav ul li a.mean-expand{margin-top:3px;width:100%;height:24px;padding:12px!important;text-align:right;position:absolute;right:0;top:0;z-index:2;font-weight:700;background:0 0;border:none!important}.mean-container .mean-push{float:left;width:100%;padding:0;margin:0;clear:both}.mean-nav .wrapper{width:100%;padding:0;margin:0}.mean-container .mean-bar,.mean-container .mean-bar *{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.mean-remove{display:none!important}.mobile-nav{display:none}.mobile-nav.mean-container .mean-nav ul li a.active{color:#ff2d55}.main-nav{background:#000;position:absolute;top:0;left:0;padding-top:15px;padding-bottom:15px;width:100%;z-index:999;height:auto}.mean-nav .dropdown-toggle::after{display:none}.navbar-light .navbar-brand,.navbar-light .navbar-brand:hover{color:#fff;font-weight:700;text-transform:uppercase}.main-nav nav ul{padding:0;margin:0;list-style-type:none}.main-nav nav .navbar-nav .nav-item{position:relative;padding:15px 0}.main-nav nav .navbar-nav .nav-item a{font-weight:500;font-size:16px;text-transform:uppercase;color:#fff;padding-left:0;padding-right:0;padding-top:0;padding-bottom:0;margin-left:15px;margin-right:15px}.main-nav nav .navbar-nav .nav-item a:hover,.main-nav nav .navbar-nav .nav-item a:focus,.main-nav nav .navbar-nav .nav-item a.active{color:#ff2d55}.main-nav nav .navbar-nav .nav-item:hover a{color:#ff2d55}.main-nav nav .navbar-nav .nav-item .dropdown-menu{-webkit-box-shadow:0 0 30px 0 rgba(0,0,0,.05);box-shadow:0 0 30px 0 rgba(0,0,0,.05);background:#0d1028;position:absolute;top:80px;left:0;width:250px;z-index:99;display:block;padding-top:20px;padding-left:5px;padding-right:5px;padding-bottom:20px;opacity:0;visibility:hidden;-webkit-transition:all .3s ease-in-out;transition:all .3s ease-in-out}.main-nav nav .navbar-nav .nav-item .dropdown-menu li{position:relative;padding:0}.main-nav nav .navbar-nav .nav-item .dropdown-menu li a{font-size:15px;font-weight:500;text-transform:capitalize;padding:9px 15px;margin:0;display:block;color:#fff}.main-nav nav .navbar-nav .nav-item .dropdown-menu li a:hover,.main-nav nav .navbar-nav .nav-item .dropdown-menu li a:focus,.main-nav nav .navbar-nav .nav-item .dropdown-menu li a.active{color:#ff2d55}.main-nav nav .navbar-nav .nav-item .dropdown-menu li .dropdown-menu{position:absolute;left:-100%;top:0;opacity:0!important;visibility:hidden!important}.main-nav nav .navbar-nav .nav-item .dropdown-menu li:hover .dropdown-menu{opacity:1!important;visibility:visible!important;top:-20px!important}.main-nav nav .navbar-nav .nav-item .dropdown-menu li .dropdown-menu li .dropdown-menu{position:absolute;left:-100%;top:0;opacity:0!important;visibility:hidden!important}.main-nav nav .navbar-nav .nav-item .dropdown-menu li:hover .dropdown-menu li:hover .dropdown-menu{opacity:1!important;visibility:visible!important;top:-20px!important}.main-nav nav .navbar-nav .nav-item .dropdown-menu li .dropdown-menu li a{color:#fff;text-transform:capitalize}.main-nav nav .navbar-nav .nav-item .dropdown-menu li .dropdown-menu li a:hover,.main-nav nav .navbar-nav .nav-item .dropdown-menu li .dropdown-menu li a:focus,.main-nav nav .navbar-nav .nav-item .dropdown-menu li .dropdown-menu li a.active{color:#ff2d55}.main-nav nav .navbar-nav .nav-item .dropdown-menu li:hover a{color:#ff2d55}.main-nav nav .navbar-nav .nav-item:hover ul{opacity:1;visibility:visible;top:100%}.main-nav nav .navbar-nav .nav-item:last-child .dropdown-menu{left:auto;right:0}@media only screen and (max-width:991px){.mobile-nav{display:block;position:relative}.mobile-nav .logo{text-decoration:none;position:absolute;top:11px;z-index:999;left:15px;color:#fff;font-weight:700;text-transform:uppercase;font-size:20px}.mean-container .mean-bar{background-color:#000;padding:0}.mean-container a.meanmenu-reveal{padding:15px 15px 0 0}.mobile-nav nav .navbar-nav .nav-item a i{display:none}.main-nav{display:none!important}}
\ No newline at end of file
diff --git a/themes/30coffe/assets/css/owl.carousel.min.css b/themes/30coffe/assets/css/owl.carousel.min.css
new file mode 100644
index 00000000..107223dd
--- /dev/null
+++ b/themes/30coffe/assets/css/owl.carousel.min.css
@@ -0,0 +1,6 @@
+/**
+ * Owl Carousel v2.3.4
+ * Copyright 2013-2018 David Deutsch
+ * Licensed under: SEE LICENSE IN https://github.com/OwlCarousel2/OwlCarousel2/blob/master/LICENSE
+ */
+.owl-carousel,.owl-carousel .owl-item{-webkit-tap-highlight-color:transparent;position:relative}.owl-carousel{display:none;width:100%;z-index:1}.owl-carousel .owl-stage{position:relative;-ms-touch-action:pan-Y;touch-action:manipulation;-moz-backface-visibility:hidden}.owl-carousel .owl-stage:after{content:".";display:block;clear:both;visibility:hidden;line-height:0;height:0}.owl-carousel .owl-stage-outer{position:relative;overflow:hidden;-webkit-transform:translate3d(0,0,0)}.owl-carousel .owl-item,.owl-carousel .owl-wrapper{-webkit-backface-visibility:hidden;-moz-backface-visibility:hidden;-ms-backface-visibility:hidden;-webkit-transform:translate3d(0,0,0);-moz-transform:translate3d(0,0,0);-ms-transform:translate3d(0,0,0)}.owl-carousel .owl-item{min-height:1px;float:left;-webkit-backface-visibility:hidden;-webkit-touch-callout:none}.owl-carousel .owl-item img{display:block;width:100%}.owl-carousel .owl-dots.disabled,.owl-carousel .owl-nav.disabled{display:none}.no-js .owl-carousel,.owl-carousel.owl-loaded{display:block}.owl-carousel .owl-dot,.owl-carousel .owl-nav .owl-next,.owl-carousel .owl-nav .owl-prev{cursor:pointer;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.owl-carousel .owl-nav button.owl-next,.owl-carousel .owl-nav button.owl-prev,.owl-carousel button.owl-dot{background:0 0;color:inherit;border:none;padding:0!important;font:inherit}.owl-carousel.owl-loading{opacity:0;display:block}.owl-carousel.owl-hidden{opacity:0}.owl-carousel.owl-refresh .owl-item{visibility:hidden}.owl-carousel.owl-drag .owl-item{-ms-touch-action:pan-y;touch-action:pan-y;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.owl-carousel.owl-grab{cursor:move;cursor:grab}.owl-carousel.owl-rtl{direction:rtl}.owl-carousel.owl-rtl .owl-item{float:right}.owl-carousel .animated{animation-duration:1s;animation-fill-mode:both}.owl-carousel .owl-animated-in{z-index:0}.owl-carousel .owl-animated-out{z-index:1}.owl-carousel .fadeOut{animation-name:fadeOut}@keyframes fadeOut{0%{opacity:1}100%{opacity:0}}.owl-height{transition:height .5s ease-in-out}.owl-carousel .owl-item .owl-lazy{opacity:0;transition:opacity .4s ease}.owl-carousel .owl-item .owl-lazy:not([src]),.owl-carousel .owl-item .owl-lazy[src^=""]{max-height:0}.owl-carousel .owl-item img.owl-lazy{transform-style:preserve-3d}.owl-carousel .owl-video-wrapper{position:relative;height:100%;background:#000}.owl-carousel .owl-video-play-icon{position:absolute;height:80px;width:80px;left:50%;top:50%;margin-left:-40px;margin-top:-40px;background:url(owl.video.play.html) no-repeat;cursor:pointer;z-index:1;-webkit-backface-visibility:hidden;transition:transform .1s ease}.owl-carousel .owl-video-play-icon:hover{-ms-transform:scale(1.3,1.3);transform:scale(1.3,1.3)}.owl-carousel .owl-video-playing .owl-video-play-icon,.owl-carousel .owl-video-playing .owl-video-tn{display:none}.owl-carousel .owl-video-tn{opacity:0;height:100%;background-position:center center;background-repeat:no-repeat;background-size:contain;transition:opacity .4s ease}.owl-carousel .owl-video-frame{position:relative;z-index:1;height:100%;width:100%}
\ No newline at end of file
diff --git a/themes/30coffe/assets/css/owl.theme.default.min.css b/themes/30coffe/assets/css/owl.theme.default.min.css
new file mode 100644
index 00000000..487088d2
--- /dev/null
+++ b/themes/30coffe/assets/css/owl.theme.default.min.css
@@ -0,0 +1,6 @@
+/**
+ * Owl Carousel v2.3.4
+ * Copyright 2013-2018 David Deutsch
+ * Licensed under: SEE LICENSE IN https://github.com/OwlCarousel2/OwlCarousel2/blob/master/LICENSE
+ */
+.owl-theme .owl-dots,.owl-theme .owl-nav{text-align:center;-webkit-tap-highlight-color:transparent}.owl-theme .owl-nav{margin-top:10px}.owl-theme .owl-nav [class*=owl-]{color:#FFF;font-size:14px;margin:5px;padding:4px 7px;background:#D6D6D6;display:inline-block;cursor:pointer;border-radius:3px}.owl-theme .owl-nav [class*=owl-]:hover{background:#869791;color:#FFF;text-decoration:none}.owl-theme .owl-nav .disabled{opacity:.5;cursor:default}.owl-theme .owl-nav.disabled+.owl-dots{margin-top:10px}.owl-theme .owl-dots .owl-dot{display:inline-block;zoom:1}.owl-theme .owl-dots .owl-dot span{width:10px;height:10px;margin:5px 7px;background:#D6D6D6;display:block;-webkit-backface-visibility:visible;transition:opacity .2s ease;border-radius:30px}.owl-theme .owl-dots .owl-dot.active span,.owl-theme .owl-dots .owl-dot:hover span{background:#869791}
\ No newline at end of file
diff --git a/themes/30coffe/assets/css/owl.video.play.html b/themes/30coffe/assets/css/owl.video.play.html
new file mode 100644
index 00000000..94da9ab8
--- /dev/null
+++ b/themes/30coffe/assets/css/owl.video.play.html
@@ -0,0 +1,7 @@
+
+404 Not Found
+
+404 Not Found
+nginx/1.18.0
+
+
diff --git a/themes/30coffe/assets/css/responsive.css b/themes/30coffe/assets/css/responsive.css
new file mode 100644
index 00000000..af49d4f8
--- /dev/null
+++ b/themes/30coffe/assets/css/responsive.css
@@ -0,0 +1 @@
+@media only screen and (max-width:767px){body{font-size:14px}.ptb-100{padding-top:50px;padding-bottom:50px}.pt-100{padding-top:50px}.pb-70{padding-bottom:20px}.pb-100{padding-bottom:50px}.navbar-area .side-nav{position:absolute;top:0;right:75px}.navbar-area .side-nav .nav-cart{border:1px solid #fff;color:#fff;width:35px;height:35px;line-height:42px;font-size:20px;top:10px}.navbar-area .side-nav .nav-cart span{color:#0b0320;background-color:#fff}.navbar-area .side-nav .nav-tel{display:none}.navbar-area .side-nav .modal-btn{display:none}.main-nav nav .navbar-nav .nav-item a{font-size:14px}.mean-container .mean-bar{background-color:#0b0320}.mobile-nav.mean-container .mean-nav ul li a.active{color:#784400}.mean-container .mean-nav .navbar-nav{height:350px;overflow-y:scroll}.mean-container a.meanmenu-reveal span{position:relative;top:8px;margin-top:-6px}.banner-area{height:100%;padding-top:125px;padding-bottom:100px}.banner-area .banner-shape img:nth-child(1){display:none}.banner-area .banner-shape img:nth-child(2){display:none}.banner-area .banner-shape img:nth-child(4){display:none}.banner-area .banner-content{text-align:center}.banner-area .banner-content h1{font-size:35px}.banner-area .banner-content form .form-control{font-size:14px;padding-left:20px;height:60px}.banner-area .banner-content form .banner-form-btn{padding:12px 14px;position:absolute;top:7px;right:7px;font-size:14px}.banner-area .banner-slider{display:none}.section-title{margin-bottom:35px;margin-top:-4px}.section-title .sub-title{font-size:14px;margin-bottom:4px}.section-title h2{font-size:25px;margin-bottom:12px}.feature-area .section-title{text-align:center}.feature-area .section-title p{margin-left:auto}.feature-item .feature-inner{bottom:15px;max-width:265px}.feature-item .feature-inner ul li img{top:-1px}.feature-item .feature-inner ul li span{font-size:16px;margin-left:8px}.feature-item .feature-inner ul li a{width:35px;height:35px;line-height:40px;font-size:21px}.service-area .service-item{padding:32px 15px 30px}.service-area .service-slider .owl-prev{display:none!important}.service-area .service-slider .owl-next{display:none!important}.restant-area{padding-top:95px;padding-bottom:50px}.restant-area .restant-shape img{max-width:75px}.restant-area .restant-content{padding-left:0;text-align:center;max-width:100%}.restant-area .restant-content .section-title{margin-bottom:25px;text-align:center}.restant-area .restant-content .section-title p{margin-left:auto}.restant-area .restant-img{margin-bottom:75px}.restant-area .restant-img img:nth-child(1){max-width:285px}.restant-area .restant-img img:nth-child(2){top:-45px;max-width:185px}.restant-area .restant-img img:nth-child(3){display:none}.restant-area .restant-img img:nth-child(4){max-width:190px;bottom:-85px}.restant-area .restant-img img:nth-child(5){display:none}.cmn-btn{font-size:14px}.collection-area .more-collection a{font-size:15px}.collection-item .collection-bottom{padding:15px 15px 17px}.collection-item .collection-bottom h3{font-size:18px;margin-bottom:12px}.collection-item .collection-bottom ul{vertical-align:middle}.collection-item .collection-bottom ul li span{font-size:22px}.collection-item .collection-bottom ul li .minus,.collection-item .collection-bottom ul li .plus{width:30px;height:30px;line-height:31px;font-size:20px}.collection-item .collection-bottom ul li .form-control{width:40px}.sorting-menu ul{margin-bottom:30px}.sorting-menu ul li{font-size:14px;padding:7px 20px;margin-left:2px;margin-right:2px}.menu-item{padding:35px 25px 32px;border:1px solid #784400}.menu-item img{margin-bottom:22px}.reservation-area{border-radius:0 0 20px 0}.reservation-area .reservation-item{padding-top:50px;padding-bottom:30px}.reservation-area .reservation-item .section-title{text-align:center;margin-bottom:35px}.reservation-area .reservation-item .section-title p{margin-left:auto}.reservation-area .reservation-item ul{padding:20px 10px 10px;border-radius:5px}.reservation-area .reservation-item ul li{margin-bottom:10px;display:block}.reservation-area .reservation-item ul li .form-control{width:100%}.reservation-area .reservation-item ul li:first-child{padding-right:0;margin-right:0}.reservation-area .reservation-item ul li:first-child:before{display:none}.reservation-area .reservation-img{position:relative;top:0;padding-bottom:40px}.chef-area{padding-top:50px}.chef-item .chef-bottom ul li:nth-child(1){opacity:1;z-index:1;bottom:0}.chef-item .chef-bottom ul li:nth-child(2){opacity:1;z-index:1;bottom:0}.chef-item .chef-bottom ul li:nth-child(3){bottom:-10px;opacity:1;z-index:1;bottom:0}.review-area .slick-prev i{left:110px}.review-area .slick-next i{right:110px}.review-area .review-img img:nth-child(2){top:12px}.review-area .review-item{padding-top:30px;padding-bottom:33px}.review-area .review-item .slider-nav{margin-bottom:25px}.review-area .review-item .slider-for p{padding-left:5px;padding-right:5px}.blog-item .blog-bottom{padding:28px 8px 45px 15px}.blog-item .blog-bottom h3{font-size:20px}.blog-area .read-blog-btn{font-size:15px}.subscribe-area{border-radius:0 0 20px 0}.subscribe-item{padding-top:50px;padding-bottom:30px}.subscribe-item .section-title{text-align:center}.subscribe-item .section-title p{margin-left:auto}.subscribe-item .newsletter-form .form-control{height:55px;padding:5px 15px;font-size:15px}.subscribe-item .newsletter-form .cmn-btn{right:8px;top:6px;padding:10px 25px}.subscribe-item .social-link ul{text-align:center}.subscribe-img{padding-bottom:40px}.footer-item .footer-service h3{margin-bottom:20px}.copyright-area{padding-top:20px;padding-bottom:20px;border-radius:20px 20px 0 0}#myModalRight{display:none}.banner-area-two .banner-shape img:nth-child(3){left:0}.banner-area-two .banner-content{padding-top:115px;padding-bottom:70px;text-align:center}.banner-area-two .banner-content h1{font-size:30px}.banner-area-two .banner-content p{margin-bottom:30px;margin-left:auto;margin-right:auto}.banner-area-two .banner-content .banner-btn-wrap .cmn-btn{margin-right:10px}.banner-area-two .banner-content .banner-btn-wrap .banner-btn-two{padding:11px 20px}.banner-area-two .banner-img{position:relative;bottom:0;right:0;left:0;text-align:center}.banner-area-two .banner-img img{max-width:100%}.food-img-area{margin-top:50px}.about-area .about-shape{display:none}.about-area .about-img{position:relative;text-align:center}.about-area .about-img img:nth-child(1){display:none}.about-area .about-img img:nth-child(2){max-width:100%;right:0}.about-area .about-content{margin-top:31px;padding-left:0;text-align:center;margin-left:auto;margin-right:auto}.about-area .about-content .section-title{text-align:center;margin-bottom:25px}.about-area .about-content .section-title h2{margin-bottom:20px}.about-area .about-content .section-title p{margin-left:auto}.download-area .download-content .section-title{text-align:center}.download-area .download-content ul li{padding:10px 15px;font-size:17px}.download-area .download-content ul li:hover{margin-left:0}.download-area .download-content ul li span{width:45px;height:45px;line-height:47px;font-size:20px;margin-right:10px}.download-area .download-content .app-wrap{margin-top:30px}.download-area .download-content .app-wrap a{margin-right:10px;max-width:130px}.join-area{margin-top:50px}.join-area .join-img img{position:relative;top:-60px;max-width:100%}.join-area .join-content{padding-top:0;padding-bottom:50px;margin-top:-30px;text-align:center}.join-area .join-content .section-title{text-align:center}.join-area .join-content .section-title p{margin-left:auto;margin-right:auto}.chef-area-two{padding-top:50px}.review-area-two{padding-top:50px}.review-area-two .review-shape img{max-width:100px}.review-area-two .review-img{text-align:center}.review-area-two .review-img img{border-radius:42px}.review-area-two .review-item{padding-top:30px;padding-bottom:94px}.review-area-two .slick-prev i{left:90px;bottom:-120px}.review-area-two .slick-next i{right:90px;bottom:-120px}.footer-area-two{border-radius:20px 20px 0 0}.copyright-area-two{border-radius:0}.banner-area-three .banner-shape img:nth-child(1){display:none}.banner-area-three .banner-shape img:nth-child(2){top:74px;max-width:62px}.banner-area-three .banner-shape img:nth-child(3){bottom:0;right:0;max-width:90px}.banner-area-three .banner-content{padding-top:115px;padding-bottom:40px;text-align:center}.banner-area-three .banner-content h1{font-size:28px}.banner-area-three .banner-content p{margin-bottom:30px;margin-left:auto;margin-right:auto}.banner-area-three .banner-content .banner-btn-wrap .cmn-btn{margin-right:10px}.banner-area-three .banner-content .banner-btn-wrap .banner-btn-two{padding:11px 20px}.banner-area-three .banner-img{position:relative;bottom:0;right:0;text-align:center;padding-bottom:80px}.banner-area-three .banner-img img{max-width:100%}.about-area-two .about-shape img{bottom:0;right:0;max-width:130px}.about-area-two .about-img{margin-bottom:75px;text-align:center}.about-area-two .about-img img:nth-child(1){max-width:100%;top:0}.about-area-two .about-img img:nth-child(2){display:none}.about-area-two .about-img .video-wrap{bottom:-45px;right:0;left:0}.about-area-two .about-img .video-wrap a{margin-left:auto;margin-right:auto}.about-area-two .about-content{padding-left:0;text-align:center}.about-area-two .about-content .section-title{text-align:center;margin-bottom:30px}.service-area-three .service-item .accordion a{font-size:14px}.service-area-three .service-item .accordion a span{font-size:14px}.service-area-three .service-img img{max-width:100%}.join-area-two{margin-top:60px}.page-title-area{padding-top:150px;padding-bottom:110px}.page-title-item{padding-left:30px}.page-title-item:before{width:5px;height:77px}.page-title-item h2{font-size:30px;margin-bottom:6px}.story-area .story-shape img{max-width:75px}.story-area .story-head{margin-bottom:35px}.story-area .story-head h2{font-size:25px;margin-bottom:15px;margin-top:-6px}.story-area .story-item h3{font-size:16px;max-width:245px;padding:12px;bottom:20px}.download-area-two{border-radius:0 0 20px 0}.download-area-two .download-item{text-align:center}.download-area-two .download-item h2{font-size:25px;margin-bottom:35px}.download-area-two .download-item ul li{margin-right:15px}.download-area-two .download-item ul li a{max-width:130px}.download-area-two .download-img{margin-bottom:30px;text-align:center}.download-area-two .download-img img{position:relative;top:0;right:0;left:0;max-width:100%}.page-title-area-two:before{display:none}.page-title-area-two .page-title-plate{display:none}.service-details-area .service-details-item .service-details-more h3{margin-bottom:25px}.service-details-area .service-details-item .service-details-more ul li a{font-size:14px}.service-details-area .service-details-item .service-details-fresh{text-align:center}.service-details-area .service-details-item .service-details-fresh h2{font-size:25px}.page-title-img-two:before{-webkit-clip-path:polygon(0 0,79% 0%,55% 100%,0% 100%);clip-path:polygon(0 0,79% 0%,55% 100%,0% 100%)}.blog-details-more{padding-bottom:20px}.blog-details-nav ul li a{font-size:14px}.cart-wrap{text-align:center}.cart-wrap .table .thead tr .table-head{font-size:13px;padding-right:4px;padding-left:4px}.cart-wrap .table tr td{font-size:14px}.cart-wrap .shop-back{margin-bottom:15px}.cart-wrap .shop-back a{margin-top:15px}.cart-wrap .total-shopping h2{font-size:25px;margin-bottom:15px}.cart-wrap .total-shopping h3{font-size:18px}.cart-wrap .total-shopping a{font-size:14px;padding:14px 25px}.checkout-item h2{font-size:25px;padding-top:10px;padding-bottom:14px;margin-bottom:30px}.checkout-item .checkout-one{max-width:100%;padding:0 10px}.checkout-item .checkout-one label{font-size:14px;margin-right:7px}.checkout-item .checkout-one .form-group .form-control{width:64%}.checkout-item .checkout-two{max-width:100%;padding:0 10px 30px}.checkout-item .checkout-two h3{margin-bottom:12px;font-size:20px}.checkout-item .checkout-two p{font-size:14px}.checkout-item .checkout-two .form-check span{font-size:14px}.checkout-item .checkout-two .form-check .form-check-input{margin-top:3px}.coming-item{height:100vh}.coming-item h1{font-size:28px;margin-bottom:12px}.coming-item .coming-wrap{margin-bottom:20px}.coming-item .coming-wrap .coming-inner h3{font-size:35px}.coming-item .coming-wrap .coming-inner p{font-size:14px}.faq-area .faq-head h2{margin-bottom:30px;font-size:25px}.faq-area .accordion p{font-size:14px;padding:12px 12px 12px 18px}.faq-area .accordion a{font-size:15px}.privacy-area{padding-bottom:0}.privacy-item{margin-bottom:50px}.privacy-item h2{font-size:22px}.error-item{height:480px}.error-item h1{font-size:80px;margin-bottom:0}.error-item p{font-size:20px;padding-left:5px;padding-right:5px}.error-item a{padding:13px 25px;margin-top:30px;font-size:14px}.contact-location-area .location-item{padding:40px 15px;border:1px solid #784400}.contact-form-area .contact-item{padding:30px 15px}.contact-form-area .contact-item #contactForm{margin-bottom:20px}.contact-form-area .contact-item #contactForm .form-group .form-control{height:50px;padding-left:25px;font-size:14px}.contact-form-area .contact-item .text-danger{font-size:16px}.contact-form-area .contact-item .text-success{font-size:16px}.contact-form-area .contact-img img{position:relative;left:0;right:0;top:50px}.book-table-area .book-table-wrap{padding:30px 10px 35px}}@media only screen and (min-width:576px) and (max-width:767px){.service-area .service-item h3{font-size:18px}}@media only screen and (min-width:768px) and (max-width:991px){body{font-size:14px}.ptb-100{padding-top:70px;padding-bottom:70px}.pt-100{padding-top:70px}.pb-70{padding-bottom:40px}.pb-100{padding-bottom:70px}.navbar-area .side-nav{position:absolute;top:0;right:75px}.navbar-area .side-nav .nav-cart{border:1px solid #fff;color:#fff;width:35px;height:35px;line-height:42px;font-size:20px;top:10px}.navbar-area .side-nav .nav-cart span{color:#0b0320;background-color:#fff}.navbar-area .side-nav .nav-tel{display:none}.navbar-area .side-nav .modal-btn{display:none}.main-nav nav .navbar-nav .nav-item a{font-size:14px}.mean-container .mean-bar{background-color:#0b0320}.mobile-nav.mean-container .mean-nav ul li a.active{color:#784400}.mean-container .mean-nav .navbar-nav{height:350px;overflow-y:scroll}.mean-container a.meanmenu-reveal span{position:relative;top:8px;margin-top:-6px}.banner-area{height:100%;padding-top:110px;padding-bottom:70px}.banner-area .banner-shape img:nth-child(1){display:none}.banner-area .banner-shape img:nth-child(2){display:none}.banner-area .banner-shape img:nth-child(4){display:none}.banner-area .banner-content{text-align:center}.banner-area .banner-content h1{font-size:35px}.banner-area .banner-content form .form-control{font-size:14px;padding-left:20px;height:60px}.banner-area .banner-content form .banner-form-btn{padding:12px 14px;position:absolute;top:7px;right:7px;font-size:14px}.banner-area .banner-slider{display:none}.section-title{margin-bottom:40px;margin-top:-2px}.section-title .sub-title{font-size:14px;margin-bottom:4px}.section-title h2{font-size:30px;margin-bottom:12px}.feature-area .section-title{text-align:center}.feature-area .section-title p{margin-left:auto}.feature-item .feature-inner{bottom:15px;max-width:265px}.feature-item .feature-inner ul li img{top:-1px}.feature-item .feature-inner ul li span{font-size:16px;margin-left:8px}.feature-item .feature-inner ul li a{width:35px;height:35px;line-height:40px;font-size:21px}.service-area .service-slider .owl-prev{display:none!important}.service-area .service-slider .owl-next{display:none!important}.restant-area{padding-top:115px;padding-bottom:70px}.restant-area .restant-shape img{max-width:100px}.restant-area .restant-content{padding-left:0;text-align:center;margin-left:auto;margin-right:auto}.restant-area .restant-content .section-title{margin-bottom:25px;text-align:center}.restant-area .restant-content .section-title p{margin-left:auto}.restant-area .restant-img{margin-bottom:75px}.restant-area .restant-img img:nth-child(1){max-width:100%}.restant-area .restant-img img:nth-child(2){top:-45px;max-width:210px}.restant-area .restant-img img:nth-child(3){top:260px;right:-36px;max-width:195px}.restant-area .restant-img img:nth-child(4){max-width:220px;bottom:-85px}.restant-area .restant-img img:nth-child(5){top:245px;max-width:230px}.cmn-btn{font-size:14px}.collection-area .more-collection a{font-size:15px}.collection-item .collection-bottom{padding:15px 15px 17px}.collection-item .collection-bottom h3{font-size:18px;margin-bottom:12px}.collection-item .collection-bottom ul{vertical-align:middle}.collection-item .collection-bottom ul li span{font-size:22px}.collection-item .collection-bottom ul li .minus,.collection-item .collection-bottom ul li .plus{width:30px;height:30px;line-height:31px;font-size:20px}.collection-item .collection-bottom ul li .form-control{width:40px}.sorting-menu ul{margin-bottom:30px}.sorting-menu ul li{font-size:14px;padding:7px 20px;margin-left:2px;margin-right:2px}.reservation-area{border-radius:0 0 20px 0}.reservation-area .reservation-item{padding-top:70px;padding-bottom:30px}.reservation-area .reservation-item .section-title{text-align:center;margin-bottom:35px}.reservation-area .reservation-item .section-title p{margin-left:auto}.reservation-area .reservation-img{position:relative;top:0;padding-bottom:60px}.chef-area{padding-top:70px}.chef-item .chef-bottom ul li:nth-child(1){opacity:1;z-index:1;bottom:0}.chef-item .chef-bottom ul li:nth-child(2){opacity:1;z-index:1;bottom:0}.chef-item .chef-bottom ul li:nth-child(3){bottom:-10px;opacity:1;z-index:1;bottom:0}.review-area .slick-prev i{left:230px}.review-area .slick-next i{right:230px}.review-area .review-img img:nth-child(2){top:12px;right:25px}.review-area .review-item{padding-top:30px;padding-bottom:45px;margin-left:auto;margin-right:auto}.review-area .review-item .slider-nav{margin-bottom:25px}.review-area .review-item .slider-for p{padding-left:5px;padding-right:5px}.subscribe-area{border-radius:0 0 20px 0}.subscribe-item{padding-top:70px;padding-bottom:30px}.subscribe-item .section-title{text-align:center}.subscribe-item .section-title p{margin-left:auto}.subscribe-item .newsletter-form .form-control{height:55px;padding:5px 15px 5px 30px;font-size:15px}.subscribe-item .newsletter-form .cmn-btn{right:8px;top:6px;padding:10px 25px}.subscribe-item .social-link ul{text-align:center}.subscribe-img{padding-bottom:60px}.footer-item .footer-service h3{margin-bottom:20px}.copyright-area{padding-top:20px;padding-bottom:20px;border-radius:20px 20px 0 0}#myModalRight{display:none}.banner-area-two .banner-shape img:nth-child(3){left:0}.banner-area-two .banner-content{padding-top:115px;padding-bottom:70px;text-align:center}.banner-area-two .banner-content h1{font-size:42px}.banner-area-two .banner-content p{margin-bottom:30px;font-size:16px;margin-left:auto;margin-right:auto}.banner-area-two .banner-img{position:relative;bottom:0;right:0;left:0;text-align:center}.banner-area-two .banner-img img{max-width:100%}.food-img-area{margin-top:70px}.about-area .about-shape{display:none}.about-area .about-img img:nth-child(2){max-width:100%}.about-area .about-content{margin-top:31px;padding-left:0;text-align:center;margin-left:auto;margin-right:auto}.about-area .about-content .section-title{text-align:center;margin-bottom:25px}.about-area .about-content .section-title h2{margin-bottom:20px}.about-area .about-content .section-title p{margin-left:auto}.download-area .download-content .section-title{text-align:center}.download-area .download-content ul li{padding:10px 15px;font-size:17px;margin-left:auto;margin-right:auto}.download-area .download-content ul li:hover{margin-left:0}.download-area .download-content ul li span{width:45px;height:45px;line-height:47px;font-size:20px;margin-right:10px}.download-area .download-content .app-wrap{margin-top:30px;text-align:center}.download-area .download-content .app-wrap a{margin-right:10px;max-width:130px}.download-area .download-img{text-align:center}.join-area{margin-top:60px}.join-area .join-img img{position:relative;top:-60px;max-width:100%}.join-area .join-content{padding-top:0;padding-bottom:50px;margin-top:-30px;text-align:center}.join-area .join-content .section-title{text-align:center}.join-area .join-content .section-title p{margin-left:auto;margin-right:auto}.chef-area-two{padding-top:70px}.review-area-two{padding-top:70px}.review-area-two .review-shape img{max-width:100px}.review-area-two .review-img{text-align:center}.review-area-two .review-img img{border-radius:42px}.review-area-two .review-item{padding-top:30px;padding-bottom:135px}.review-area-two .slick-prev i{left:215px;bottom:-120px}.review-area-two .slick-next i{right:215px;bottom:-120px}.footer-area-two{border-radius:20px 20px 0 0}.copyright-area-two{border-radius:0}.banner-area-three .banner-shape img:nth-child(1){display:none}.banner-area-three .banner-shape img:nth-child(2){top:74px;max-width:62px}.banner-area-three .banner-shape img:nth-child(3){bottom:0;right:0;max-width:90px}.banner-area-three .banner-content{padding-top:115px;padding-bottom:40px;text-align:center}.banner-area-three .banner-content h1{font-size:38px}.banner-area-three .banner-content p{margin-bottom:30px;margin-left:auto;margin-right:auto}.banner-area-three .banner-content .banner-btn-wrap .cmn-btn{margin-right:10px}.banner-area-three .banner-content .banner-btn-wrap .banner-btn-two{padding:11px 20px}.banner-area-three .banner-img{position:relative;bottom:0;right:0;text-align:center;padding-bottom:80px}.banner-area-three .banner-img img{max-width:100%}.about-area-two .about-shape img{bottom:0;right:0;max-width:130px}.about-area-two .about-img{margin-bottom:75px;text-align:center}.about-area-two .about-img img:nth-child(1){max-width:100%;top:0}.about-area-two .about-img img:nth-child(2){display:none}.about-area-two .about-img .video-wrap{bottom:-45px;right:0;left:0}.about-area-two .about-img .video-wrap a{margin-left:auto;margin-right:auto}.about-area-two .about-content{padding-left:0;text-align:center}.about-area-two .about-content .section-title{text-align:center;margin-bottom:30px}.service-area-three .service-item .accordion a{font-size:14px}.service-area-three .service-item .accordion a span{font-size:14px}.service-area-three .service-img img{max-width:100%}.join-area-two{margin-top:60px}.page-title-area{padding-top:150px;padding-bottom:110px}.page-title-item{padding-left:30px}.page-title-item:before{width:5px;height:77px}.page-title-item h2{font-size:30px;margin-bottom:6px}.story-area .story-shape img{max-width:75px}.story-area .story-head{margin-bottom:35px}.story-area .story-head h2{font-size:30px;margin-bottom:15px;margin-top:-8px}.story-area .story-item h3{font-size:16px;max-width:245px;padding:12px;bottom:20px}.download-area-two{border-radius:0 0 20px 0}.download-area-two .download-item{text-align:center}.download-area-two .download-item h2{font-size:25px;margin-bottom:35px}.download-area-two .download-item ul li{margin-right:15px}.download-area-two .download-item ul li a{max-width:130px}.download-area-two .download-img{margin-bottom:30px;text-align:center}.download-area-two .download-img img{position:relative;top:0;right:0;left:0;max-width:100%}.page-title-area-two:before{display:none}.page-title-area-two .page-title-plate{display:none}.service-details-area .service-details-item .service-details-more h3{margin-bottom:25px}.service-details-area .service-details-item .service-details-more ul li a{font-size:14px}.service-details-area .service-details-item .service-details-fresh{text-align:center}.service-details-area .service-details-item .service-details-fresh h2{font-size:25px}.blog-details-more{padding-bottom:20px}.blog-details-nav ul li a{font-size:14px}.checkout-item h2{font-size:30px}.checkout-item .checkout-one{max-width:100%;padding-left:15px;padding-right:15px}.checkout-item .checkout-two{max-width:100%;padding-left:15px;padding-right:15px}.coming-item{height:100vh}.coming-item h1{font-size:35px;margin-bottom:12px}.coming-item .coming-wrap{margin-bottom:20px}.coming-item .coming-wrap .coming-inner h3{font-size:35px}.coming-item .coming-wrap .coming-inner p{font-size:14px}.faq-area .faq-head h2{margin-bottom:30px;font-size:30px}.faq-area .accordion p{font-size:14px;padding:12px 12px 12px 18px}.faq-area .accordion a{font-size:15px}.privacy-area{padding-bottom:20px}.privacy-item{margin-bottom:50px}.privacy-item h2{font-size:22px}.error-item{height:480px}.error-item h1{font-size:80px;margin-bottom:0}.error-item p{font-size:20px;padding-left:5px;padding-right:5px}.error-item a{padding:13px 25px;margin-top:30px;font-size:14px}.contact-location-area .location-item{padding:40px 15px;border:1px solid #784400}.contact-form-area .contact-item{padding:30px 15px}.contact-form-area .contact-item #contactForm{margin-bottom:20px}.contact-form-area .contact-item #contactForm .form-group .form-control{height:50px;padding-left:25px;font-size:14px}.contact-form-area .contact-item .text-danger{font-size:18px}.contact-form-area .contact-item .text-success{font-size:18px}.contact-form-area .contact-img img{position:relative;left:0;right:0;top:70px}.book-table-area .book-table-wrap{padding:40px 30px 45px}.reservation-area .reservation-item ul{max-width:540px;margin-left:auto;margin-right:auto}}@media only screen and (min-width:992px) and (max-width:1199px){.main-nav nav .navbar-nav .nav-item a{font-size:14px;margin-left:5px;margin-right:5px}.main-nav nav .side-nav .nav-tel{margin-left:10px}.feature-item .feature-inner{max-width:265px}.service-area .service-slider .owl-prev{display:none}.service-area .service-slider .owl-next{display:none}.collection-item .collection-bottom{padding:15px 10px 17px}.collection-item .collection-bottom h3{font-size:16px}.reservation-area .reservation-item ul li:last-child .cmn-btn{margin-top:10px;margin-left:0}.review-area .slick-prev i{left:185px}.review-area .slick-next i{right:185px}.banner-area-two .banner-img img{max-width:350px}.join-area .join-img img{top:-95px}.banner-area-three .banner-img img{max-width:340px}.service-area-three .service-item .accordion a{font-size:14px}.checkout-item .checkout-one{padding-left:10px;padding-right:10px}.checkout-item .checkout-two{padding-left:10px;padding-right:10px}.checkout-item .checkout-one .form-group .form-control{width:75%}.banner-area .banner-content h1{font-size:50px}.banner-area .banner-shape img:nth-child(4){display:none}.reservation-area .reservation-item ul{padding:20px 11px 20px 20px}.restant-area .restant-img img:nth-child(1){max-width:495px}.about-area-two .about-img img:nth-child(1){max-width:385px}}@media only screen and (min-width:1400px){.about-area-two .about-img img:nth-child(1){max-width:555px}.about-area-two .about-img .video-wrap{bottom:15px}}@media only screen and (min-width:1800px){.banner-area-three .banner-img img{max-width:100%}.about-area-two .about-img .video-wrap{bottom:12px}.about-area-two .about-img img:nth-child(1){max-width:560px}}@media only screen and (min-width:1800px){.restant-area .restant-img{margin-right:0}}@media only screen and (max-width:991px){.mean-container .mean-nav ul li a.mean-expand{margin-top:0}}
\ No newline at end of file
diff --git a/themes/30coffe/assets/css/slick-theme.min.css b/themes/30coffe/assets/css/slick-theme.min.css
new file mode 100644
index 00000000..51ff5ed6
--- /dev/null
+++ b/themes/30coffe/assets/css/slick-theme.min.css
@@ -0,0 +1,2 @@
+@charset 'UTF-8';.slick-loading .slick-list{background:#fff url(../fonts/ajax-loader.gif) center center no-repeat}@font-face{font-family:slick;font-weight:400;font-style:normal;src:url(../fonts/slick.eot);src:url(../fonts/slickd41d.eot?#iefix) format('embedded-opentype'),url(../fonts/slick.woff) format('woff'),url(../fonts/slick.ttf) format('truetype'),url(../fonts/slick.svg#slick) format('svg')}.slick-next,.slick-prev{font-size:0;line-height:0;position:absolute;top:50%;display:block;width:20px;height:20px;padding:0;-webkit-transform:translate(0,-50%);-ms-transform:translate(0,-50%);transform:translate(0,-50%);cursor:pointer;color:transparent;border:none;outline:0;background:0 0}.slick-next:focus,.slick-next:hover,.slick-prev:focus,.slick-prev:hover{color:transparent;outline:0;background:0 0}.slick-next:focus:before,.slick-next:hover:before,.slick-prev:focus:before,.slick-prev:hover:before{opacity:1}.slick-next.slick-disabled:before,.slick-prev.slick-disabled:before{opacity:.25}.slick-next:before,.slick-prev:before{font-family:slick;font-size:20px;line-height:1;opacity:.75;color:#fff;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.slick-prev{left:-25px}[dir=rtl] .slick-prev{right:-25px;left:auto}.slick-prev:before{content:'←'}[dir=rtl] .slick-prev:before{content:'→'}.slick-next{right:-25px}[dir=rtl] .slick-next{right:auto;left:-25px}.slick-next:before{content:'→'}[dir=rtl] .slick-next:before{content:'←'}.slick-dotted.slick-slider{margin-bottom:30px}.slick-dots{position:absolute;bottom:-25px;display:block;width:100%;padding:0;margin:0;list-style:none;text-align:center}.slick-dots li{position:relative;display:inline-block;width:20px;height:20px;margin:0 5px;padding:0;cursor:pointer}.slick-dots li button{font-size:0;line-height:0;display:block;width:20px;height:20px;padding:5px;cursor:pointer;color:transparent;border:0;outline:0;background:0 0}.slick-dots li button:focus,.slick-dots li button:hover{outline:0}.slick-dots li button:focus:before,.slick-dots li button:hover:before{opacity:1}.slick-dots li button:before{font-family:slick;font-size:6px;line-height:20px;position:absolute;top:0;left:0;width:20px;height:20px;content:'•';text-align:center;opacity:.25;color:#000;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.slick-dots li.slick-active button:before{opacity:.75;color:#000}
+/*# sourceMappingURL=slick-theme.min.css.map */
\ No newline at end of file
diff --git a/themes/30coffe/assets/css/slick.min.css b/themes/30coffe/assets/css/slick.min.css
new file mode 100644
index 00000000..41691514
--- /dev/null
+++ b/themes/30coffe/assets/css/slick.min.css
@@ -0,0 +1,2 @@
+.slick-slider{position:relative;display:block;box-sizing:border-box;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-touch-callout:none;-khtml-user-select:none;-ms-touch-action:pan-y;touch-action:pan-y;-webkit-tap-highlight-color:transparent}.slick-list{position:relative;display:block;overflow:hidden;margin:0;padding:0}.slick-list:focus{outline:0}.slick-list.dragging{cursor:pointer;cursor:hand}.slick-slider .slick-list,.slick-slider .slick-track{-webkit-transform:translate3d(0,0,0);-moz-transform:translate3d(0,0,0);-ms-transform:translate3d(0,0,0);-o-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.slick-track{position:relative;top:0;left:0;display:block;margin-left:auto;margin-right:auto}.slick-track:after,.slick-track:before{display:table;content:''}.slick-track:after{clear:both}.slick-loading .slick-track{visibility:hidden}.slick-slide{display:none;float:left;height:100%;min-height:1px}[dir=rtl] .slick-slide{float:right}.slick-slide img{display:block}.slick-slide.slick-loading img{display:none}.slick-slide.dragging img{pointer-events:none}.slick-initialized .slick-slide{display:block}.slick-loading .slick-slide{visibility:hidden}.slick-vertical .slick-slide{display:block;height:auto;border:1px solid transparent}.slick-arrow.slick-hidden{display:none}
+/*# sourceMappingURL=slick.min.css.map */
\ No newline at end of file
diff --git a/themes/30coffe/assets/css/style.css b/themes/30coffe/assets/css/style.css
new file mode 100644
index 00000000..8a850335
--- /dev/null
+++ b/themes/30coffe/assets/css/style.css
@@ -0,0 +1 @@
+@import "https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap";@import "https://fonts.googleapis.com/css2?family=Playfair+Display:wght@500;600;700&display=swap";body{font-family:poppins,sans-serif;color:#2e2e30;background-color:#fff;font-size:15px}h1,h2,h3,h4,h5,h6{line-height:1.3;font-family:playfair display,serif;color:#0b0320}p{line-height:1.7;color:#2e2e30}a{-webkit-transition:.5s all ease;transition:.5s all ease;text-decoration:none;color:#784400}a:hover{color:#0b0320}img{max-width:100%}.d-table{width:100%;height:100%}.d-table-cell{padding-top: 120px;padding-bottom: 0px;}.ptb-100{padding-top:100px;padding-bottom:100px}.pt-100{padding-top:100px}.pb-70{padding-bottom:70px}.pb-100{padding-bottom:100px}button:focus{outline:0}.btn.focus,.btn:focus{-webkit-box-shadow:none;box-shadow:none}.navbar-light .navbar-brand .logo-two{display:none}.navbar-area .side-nav{position:relative;top:4px}.navbar-area .side-nav .nav-cart{width:42px;height:42px;line-height:48px;border-radius:50%;border:1px solid #0b0320;color:#0b0320;display:inline-block;text-align:center;font-size:22px;position:relative}.navbar-area .side-nav .nav-cart:hover{color:#fff;background-color:#0b0320}.navbar-area .side-nav .nav-cart span{display:inline-block;color:#fff;background-color:#814100;border-radius:50%;width:18px;height:18px;line-height:18px;font-size:12px;position:absolute;top:-3px;right:-5px;font-weight:500}.navbar-area .side-nav .nav-tel{color:#f5e9b3;border-radius:50px;padding:12px 15px 11px;background-color:#784400;font-size:14px;position:relative;top:-6px;margin-left:15px;display:inline-block}.navbar-area .side-nav .nav-tel i{display:inline-block;margin-right:5px;font-size:18px;position:relative;top:3px}.navbar-area .side-nav .nav-tel:hover{color:#fff;background-color:#0b0320}.main-nav{background:0 0;-webkit-transition:.5s all ease;transition:.5s all ease}.main-nav nav .navbar-nav{margin-left:auto;margin-right:auto}.main-nav nav .navbar-nav .nav-link:focus,.main-nav nav .navbar-nav .nav-link:hover{color:#784400}.main-nav nav .navbar-nav .nav-item:hover a{color:#784400}.main-nav nav .navbar-nav .nav-item a{font-weight:400;font-size:15px;color:#0b0320;font-family:poppins,sans-serif;text-transform:unset}.main-nav nav .navbar-nav .nav-item a:hover,.main-nav nav .navbar-nav .nav-item a:focus,.main-nav nav .navbar-nav .nav-item a.active{color:#784400}.main-nav nav .navbar-nav .nav-item a:hover i{-webkit-transform:rotate(0deg);transform:rotate(0deg);color:#784400}.main-nav nav .navbar-nav .nav-item a i{display:inline-block;font-size:18px;position:relative;top:2px;-webkit-transform:rotate(-90deg);transform:rotate(-90deg);-webkit-transition:.5s all ease;transition:.5s all ease}.main-nav nav .navbar-nav .nav-item .dropdown-menu{background:#fff;padding:0;border:0;border-radius:0}.main-nav nav .navbar-nav .nav-item .dropdown-menu li{border-bottom:1px solid #0b032026;position:relative;-webkit-transition:.5s all ease;transition:.5s all ease}.main-nav nav .navbar-nav .nav-item .dropdown-menu li:hover{padding-left:10px}.main-nav nav .navbar-nav .nav-item .dropdown-menu li:hover:before{opacity:1}.main-nav nav .navbar-nav .nav-item .dropdown-menu li:hover a{color:#784400;text-transform:unset}.main-nav nav .navbar-nav .nav-item .dropdown-menu li:before{position:absolute;content:'';width:15px;height:1px;left:0;top:22px;background-color:#784400;opacity:0;-webkit-transition:.5s all ease;transition:.5s all ease}.main-nav nav .navbar-nav .nav-item .dropdown-menu li:last-child{border-bottom:0}.main-nav nav .navbar-nav .nav-item .dropdown-menu li a{color:#0b0320;padding:12px 15px}.main-nav nav .navbar-nav .nav-item .dropdown-menu li a:hover,.main-nav nav .navbar-nav .nav-item .dropdown-menu li a:focus,.main-nav nav .navbar-nav .nav-item .dropdown-menu li a.active{color:#784400}.main-nav .dropdown-toggle::after{display:none}.menu-shrink{background-color:#fff;padding-top:5px;padding-bottom:5px;-webkit-box-shadow:0 0 20px 0 #dddddd8c;box-shadow:0 0 20px 0 #dddddd8c}.banner-area{position:relative}.banner-area .banner-shape img{position:absolute;z-index:-1}.banner-area .banner-shape img:nth-child(1){bottom:125px;left:165px}.banner-area .banner-shape img:nth-child(2){top:130px;left:170px;margin-left:auto;margin-right:auto}.banner-area .banner-shape img:nth-child(3){bottom:30px;left:0;right:20px;margin-left:auto;margin-right:auto}.banner-area .banner-shape img:nth-child(4){top:275px;right:0;max-width:140px}.banner-area .banner-content h1{font-weight:700;font-size:60px;margin-bottom:20px}.banner-area .banner-content p{margin-bottom:30px}.banner-area .banner-content form{position:relative}.banner-area .banner-content form ::-webkit-input-placeholder{color:#a5a4a9}.banner-area .banner-content form :-ms-input-placeholder{color:#a5a4a9}.banner-area .banner-content form ::-ms-input-placeholder{color:#a5a4a9}.banner-area .banner-content form ::placeholder{color:#a5a4a9}.banner-area .banner-content form .form-control{font-size:16px;padding-left:25px;border:0;border-radius:50px;-webkit-box-shadow:0 0 20px 0 #dddddd5c;box-shadow:0 0 20px 0 #dddddd5c;height:70px;padding-top:10px;padding-bottom:10px}.banner-area .banner-content form .form-control:focus{-webkit-box-shadow:0 0 20px 0 #dddddd5c;box-shadow:0 0 20px 0 #dddddd5c;border:0}.banner-area .banner-content form .banner-form-btn{font-weight:500;font-size:15px;color:#f7e9ac;border-radius:50px;background-color:#784400;padding:12px 32px;position:absolute;top:10px;right:12px;-webkit-transition:.5s all ease;transition:.5s all ease}.banner-area .banner-content form .banner-form-btn:hover{color:#fff;background-color:#0b0320}.banner-area .owl-theme .owl-nav{margin-top:-5px;position:relative;right:0;left:0}.banner-area .banner-slider .owl-prev{font-size:55px!important;color:#784400!important;-webkit-transition:.5s all ease;transition:.5s all ease}.banner-area .banner-slider .owl-prev:hover{color:#0b0320!important;background-color:transparent!important}.banner-area .banner-slider .owl-next{font-size:55px!important;color:#784400!important;-webkit-transition:.5s all ease;transition:.5s all ease}.banner-area .banner-slider .owl-next:hover{color:#0b0320!important;background-color:transparent!important}.section-title{margin-bottom:50px;text-align:center;margin-top:-8px}.section-title .sub-title{font-weight:500;font-size:16px;color:#784400;display:block;margin-bottom:8px}.section-title h2{font-weight:700;font-size:35px;margin-bottom:16px}.section-title p{max-width:580px;margin-bottom:0;margin-left:auto;margin-right:auto}.feature-area .section-title{text-align:left}.feature-area .section-title p{margin-left:0}.feature-item{position:relative;margin-bottom:30px;-webkit-transition:.5s all ease;transition:.5s all ease}.feature-item img{width:100%;border-radius:25px}.feature-item:hover{-webkit-transform:translate(0,-10px);transform:translate(0,-10px)}.feature-item:hover .feature-inner ul li a{background-color:#0b0320}.feature-item .feature-inner{position:absolute;left:0;right:0;bottom:30px;background-color:#fff;max-width:310px;border-radius:50px;margin-left:auto;margin-right:auto;padding-left:25px;padding-top:12px;padding-bottom:12px}.feature-item .feature-inner ul{margin:0;padding:0;position:relative}.feature-item .feature-inner ul li{list-style-type:none;display:inline-block}.feature-item .feature-inner ul li img{position:relative;top:-2px}.feature-item .feature-inner ul li span{display:block;font-weight:500;font-size:18px;color:#0b0320;margin-left:12px}.feature-item .feature-inner ul li a{display:block;width:40px;height:40px;line-height:46px;border-radius:50%;text-align:center;font-size:23px;color:#fff;background-color:#784400;position:absolute;right:7px;top:-7px}.service-area{background-color:#fffdf9}.service-area .service-item{text-align:center;padding:32px 20px 30px;border-radius:25px;-webkit-transition:.5s all ease;transition:.5s all ease;position:relative}.service-area .service-item a{display:block}.service-area .service-item:hover{background-color:#784400}.service-area .service-item:hover .service-shape{opacity:1}.service-area .service-item:hover h3{color:#fff}.service-area .service-item:hover p{color:#fff}.service-area .service-item img{margin-bottom:20px;width:80px!important;height:80px;margin-left:auto;margin-right:auto;display:block}.service-area .service-item .service-shape{position:absolute;top:0;left:0;-webkit-transition:.5s all ease;transition:.5s all ease;opacity:0;width:100%!important;height:100%!important;margin-bottom:0}.service-area .service-item h3{font-weight:700;font-size:22px;margin-bottom:15px;-webkit-transition:.5s all ease;transition:.5s all ease;color:#0b0320}.service-area .service-item p{margin-bottom:0;-webkit-transition:.5s all ease;transition:.5s all ease;color:#2e2e30}.service-area .service-slider{margin-bottom:-9px}.service-area .service-slider .center{background-color:#784400;border-radius:25px}.service-area .service-slider .center .service-shape{opacity:1}.service-area .service-slider .center h3{color:#fff}.service-area .service-slider .center p{color:#fff}.service-area .service-slider .owl-prev{height:40px;width:40px;line-height:45px!important;border-radius:50%!important;text-align:center;color:#fff!important;background-color:#ffe7a2!important;font-size:25px!important;-webkit-transition:.5s all ease;transition:.5s all ease;position:absolute;top:45%;left:-55px;opacity:0}.service-area .service-slider .owl-prev:hover{background-color:#784400!important}.service-area .service-slider .owl-next{height:40px;width:40px;line-height:45px!important;border-radius:50%!important;text-align:center;color:#fff!important;background-color:#ffe7a2!important;font-size:25px!important;-webkit-transition:.5s all ease;transition:.5s all ease;position:absolute;top:45%;right:-55px;opacity:0}.service-area .service-slider .owl-next:hover{background-color:#784400!important}.service-area .service-slider:hover .owl-prev{opacity:1;left:-48px}.service-area .service-slider:hover .owl-next{opacity:1;right:-48px}.restant-area{ padding-top:150px;position:relative;padding-bottom:130px}.restant-area .restant-shape img{position:absolute;right:0;bottom:0;max-width:150px}.restant-area .restant-content{max-width:600px;padding-left:20px}.restant-area .restant-content .section-title{text-align:left;margin-bottom:35px}.restant-area .restant-content .section-title p{max-width:100%;margin-left:0;margin-bottom:10px}.restant-area .restant-img{text-align:center;position:relative;max-width:620px;margin-left:auto;margin-right:auto}.restant-area .restant-img img:nth-child(1){position:relative;max-width:520px;margin-left:auto;margin-right:auto}.restant-area .restant-img img:nth-child(2){position:absolute;left:0;right:0;top:-50px;margin-left:auto;margin-right:auto;max-width:280px;-webkit-animation:restant-amination 10s infinite linear;animation:restant-amination 10s infinite linear}.restant-area .restant-img img:nth-child(3){position:absolute;top:175px;right:-50px;max-width:260px;-webkit-animation:restant-amination 10s infinite linear;animation:restant-amination 10s infinite linear}.restant-area .restant-img img:nth-child(4){position:absolute;left:0;right:0;max-width:260px;bottom:-100px;margin-left:auto;margin-right:auto;-webkit-animation:restant-amination 10s infinite linear;animation:restant-amination 10s infinite linear}.restant-area .restant-img img:nth-child(5){position:absolute;top:160px;left:-65px;max-width:275px;-webkit-animation:restant-amination 10s infinite linear;animation:restant-amination 10s infinite linear}.cmn-btn{font-weight:500;font-size:15px;color:#f7e9ac;background-color:#784400;padding:12px 30px;border-radius:50px;display:inline-block}.cmn-btn:hover{background-color:#0b0320;color:#fff}@-webkit-keyframes restant-amination{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}40%{-webkit-transform:rotate(10deg);transform:rotate(10deg)}70%{-webkit-transform:rotate(-15deg);transform:rotate(-15deg)}100%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}}@keyframes restant-amination{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}40%{-webkit-transform:rotate(10deg);transform:rotate(10deg)}70%{-webkit-transform:rotate(-15deg);transform:rotate(-15deg)}100%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}}.collection-area .more-collection{text-align:center}.collection-area .more-collection a{display:inline-block;font-weight:500;color:#0b0320;font-size:18px;border-bottom:1px solid #0b0320}.collection-area .more-collection a:hover{color:#784400;border-bottom:1px solid #784400}.collection-item{margin-bottom:30px;-webkit-box-shadow:0 0 20px 0 #dddddd82;box-shadow:0 0 20px 0 #dddddd82;border-radius:10px 10px 0 0;overflow:hidden}.collection-item:hover .collection-top ul{opacity:1;bottom:0}.collection-item:hover .collection-top .add-cart{right:10px}.collection-item:hover .collection-top .add-cart a{opacity:1}.collection-item .collection-top{position:relative}.collection-item .collection-top img{width:100%;border-radius:10px 10px 0 0}.collection-item .collection-top ul{margin:0;padding:0;background-color:#fff;position:absolute;bottom:-10px;left:0;right:0;opacity:0;padding:4px 15px;border-radius:8px 8px 0 0;max-width:128px;text-align:center;margin-left:auto;margin-right:auto;-webkit-box-shadow:0 6px 15px 0 #dddddd40;box-shadow:0 6px 15px 0 #dddddd40;-webkit-transition:.5s all ease;transition:.5s all ease}.collection-item .collection-top ul li{list-style-type:none;display:inline-block}.collection-item .collection-top ul li i{color:#ddd;display:block}.collection-item .collection-top ul li .checked{color:#ffc107}.collection-item .collection-top .add-cart{display:inline-block;position:absolute;top:10px;right:-15px}.collection-item .collection-top .add-cart a{display:block;color:#0b0320;background-color:#fff;border-radius:5px;padding:4px 12px 8px;font-size:13px;opacity:0}.collection-item .collection-top .add-cart a i{display:inline-block;color:#784400;font-size:22px;position:relative;top:4px;margin-right:3px}.collection-item .collection-top .add-cart a:hover{color:#784400;background-color:#f7e9ac}.collection-item .collection-bottom{padding:15px 20px 17px}.collection-item .collection-bottom h3{font-weight:500;font-size:20px;margin-bottom:15px;font-family:poppins,sans-serif}.collection-item .collection-bottom ul{margin:0;padding:0;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.collection-item .collection-bottom ul li{list-style-type:none;display:inline-block}.collection-item .collection-bottom ul li:first-child{-webkit-box-flex:0;-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.collection-item .collection-bottom ul li:last-child{text-align:right;-webkit-box-flex:0;-ms-flex:0 0 80%;flex:0 0 80%;max-width:80%}.collection-item .collection-bottom ul li span{display:block;font-weight:500;font-size:20px;color:#fe3333;position:relative;top:4px}.collection-item .collection-bottom ul li .minus,.collection-item .collection-bottom ul li .plus{width:35px;height:35px;line-height:35px;color:#fff;background-color:#e8c940;display:inline-block;text-align:center;cursor:pointer;margin-bottom:0;vertical-align:middle;-webkit-transition:.5s all ease;transition:.5s all ease;border-radius:10px;top:0}.collection-item .collection-bottom ul li .minus:hover,.collection-item .collection-bottom ul li .plus:hover{background-color:#784400}.collection-item .collection-bottom ul li .form-control{height:25px;width:50px;text-align:center;font-size:20px;font-weight:500;border:0;color:#784400;display:inline-block;vertical-align:middle;margin-left:-4px;margin-right:-4px}.collection-item .collection-bottom ul li .form-control:focus{-webkit-box-shadow:none;box-shadow:none;border:0}.sorting-menu ul{margin:0;padding:0;text-align:center;margin-bottom:40px}.sorting-menu ul li{list-style-type:none;display:inline-block;font-size:15px;color:#0b0320;cursor:pointer;border-radius:50px;padding:10px 20px;margin-left:5px;margin-right:5px;-webkit-transition:.5s all ease;transition:.5s all ease;font-weight:500;background-color:#f5f5f5;margin-bottom:7px}.sorting-menu ul li:hover,.sorting-menu ul li.mixitup-control-active{color:#fff;background-color:#784400}.menu-area{background-color:#fffdf9}.menu-item{text-align:center;margin-bottom:30px;background-color:#fff;padding:45px 30px 42px;border-radius:18px;position:relative;-webkit-transition:.5s all ease;transition:.5s all ease;border:1px solid #784400}.menu-item:hover,.menu-item.active{background-color:#784400;-webkit-transform:translate(0,-10px);transform:translate(0,-10px)}.menu-item:hover .menu-shape,.menu-item.active .menu-shape{opacity:1}.menu-item:hover h3,.menu-item.active h3{color:#fff}.menu-item img{margin-bottom:22px}.menu-item .menu-shape{position:absolute;left:0;top:0;opacity:0;margin-bottom:0;-webkit-transition:.5s all ease;transition:.5s all ease;width:100%;height:100%}.menu-item h3{font-size:22px;font-weight:700;margin-bottom:0;-webkit-transition:.5s all ease;transition:.5s all ease}.reservation-area{background-color:#0b0320;border-radius:0 0 85px 0;position:relative}.reservation-area .reservation-shape img{position:absolute;top:0;left:0}.reservation-area .row{position:relative}.reservation-area .reservation-item{padding-top:100px;padding-bottom:100px;position:relative}.reservation-area .reservation-item .section-title{text-align:left;margin-bottom:40px}.reservation-area .reservation-item .section-title h2{color:#fff}.reservation-area .reservation-item .section-title p{margin-left:0;color:#fff}.reservation-area .reservation-item ul{margin:0;padding:10px 11px 10px 20px;background-color:#fff;border-radius:50px;text-align:center}.reservation-area .reservation-item ul li{list-style-type:none;display:inline-block}.reservation-area .reservation-item ul li .form-control{width:165px;font-size:13px;border:1px solid #0b0320;padding:12px}.reservation-area .reservation-item ul li .form-control:focus{-webkit-box-shadow:none;box-shadow:none;border:1px solid #0b0320}.reservation-area .reservation-item ul li:first-child{padding-right:10px;position:relative;margin-right:5px}.reservation-area .reservation-item ul li:first-child:before{position:absolute;content:'';width:1px;height:45px;right:0;top:0;background-color:#784400}.reservation-area .reservation-item ul li:last-child .cmn-btn{padding:12px 22px;margin-left:7px}.reservation-area .reservation-img{text-align:center;margin-right:auto;margin-left:auto}.chef-item{margin-bottom:30px;text-align:center}.chef-item:hover .chef-bottom ul li:nth-child(1),.chef-item.active .chef-bottom ul li:nth-child(1){bottom:0;opacity:1;z-index:1}.chef-item:hover .chef-bottom ul li:nth-child(2),.chef-item.active .chef-bottom ul li:nth-child(2){bottom:0;opacity:1;z-index:1}.chef-item:hover .chef-bottom ul li:nth-child(3),.chef-item.active .chef-bottom ul li:nth-child(3){bottom:0;opacity:1;z-index:1}.chef-item .chef-top{position:relative;border-radius:10px 10px 0 0}.chef-item .chef-top img{width:100%;border-radius:10px 10px 0 0}.chef-item .chef-top .chef-inner{background-color:#0b0320bf;border-radius:10px 10px 0 0;padding:10px 10px 12px;position:absolute;left:0;right:0;bottom:0}.chef-item .chef-top .chef-inner h3{color:#fff;font-family:poppins,sans-serif;font-weight:500;font-size:20px;margin-bottom:5px}.chef-item .chef-top .chef-inner span{display:block;font-size:14px;color:#fff}.chef-item .chef-bottom ul{margin:0;padding:0;margin-top:10px}.chef-item .chef-bottom ul li{list-style-type:none;display:inline-block;margin-left:5px;margin-right:5px}.chef-item .chef-bottom ul li:nth-child(1){position:relative;bottom:-10px;opacity:0;z-index:-1;-webkit-transition:.5s all ease;transition:.5s all ease}.chef-item .chef-bottom ul li:nth-child(2){position:relative;bottom:-10px;opacity:0;z-index:-1;-webkit-transition:.7s all ease;transition:.7s all ease}.chef-item .chef-bottom ul li:nth-child(3){position:relative;bottom:-10px;opacity:0;z-index:-1;-webkit-transition:.9s all ease;transition:.9s all ease}.chef-item .chef-bottom ul li a{display:block;width:35px;height:35px;line-height:40px;border-radius:50%;text-align:center;color:#fff;background-color:#0b0320;font-size:18px}.chef-item .chef-bottom ul li a:hover{background-color:#784400}.review-area{position:relative}.review-area .slick-prev{left:0}.review-area .slick-prev:before{display:none}.review-area .slick-prev i{color:#784400;display:block;font-size:35px;position:absolute;bottom:-98px;left:240px;-webkit-transition:.5s all ease;transition:.5s all ease}.review-area .slick-prev i:hover{color:#0b0320}.review-area .slick-next{right:0}.review-area .slick-next:before{display:none}.review-area .slick-next i{color:#784400;display:block;font-size:35px;position:absolute;bottom:-98px;right:240px;-webkit-transition:.5s all ease;transition:.5s all ease}.review-area .slick-next i:hover{color:#0b0320}.review-area .review-img{position:relative}.review-area .review-img img:nth-child(1){position:relative}.review-area .review-img img:nth-child(2){position:absolute;top:60px;right:0}.review-area .review-item{text-align:center;max-width:560px;padding-top:100px;padding-bottom:100px}.review-area .review-item .section-title{margin-bottom:30px}.review-area .review-item .slider-nav{margin-bottom:40px}.review-area .review-item .slider-nav .slick-center img{-webkit-transform:scale(1.3);transform:scale(1.3);margin-top:12px;margin-bottom:12px}.review-area .review-item .slider-nav img{margin-left:auto;margin-right:auto;margin-top:12px;-webkit-transition:.5s all ease;transition:.5s all ease;max-width:75px}.review-area .review-item .slider-nav img:focus{border:0;-webkit-box-shadow:none;box-shadow:none}.review-area .review-item .slider-for h3{font-weight:500;font-size:20px;font-family:poppins,sans-serif;margin-bottom:15px}.review-area .review-item .slider-for p{margin-bottom:0;max-width:500px;margin-left:auto;margin-right:auto}.blog-item{margin-bottom:50px;-webkit-box-shadow:0 0 20px 0 #dddddd8c;box-shadow:0 0 20px 0 #dddddd8c;border-radius:65px 65px 0 0}.blog-item:hover .blog-bottom .cmn-btn{right:0}.blog-item .blog-top{position:relative}.blog-item .blog-top a{display:block}.blog-item .blog-top span{display:inline-block;font-size:14px;color:#0b0320;background-color:#f6e8ab;padding:5px 12px;border-radius:6px;position:absolute;right:15px;bottom:-15px}.blog-item .blog-top img{border-radius:12px 12px 0 0;width:100%}.blog-item .blog-bottom{padding:35px 15px 50px 30px;position:relative}.blog-item .blog-bottom h3{font-family:poppins,sans-serif;font-weight:500;font-size:22px;margin-bottom:10px;line-height:1.4}.blog-item .blog-bottom h3 a{display:block;color:#0b0320}.blog-item .blog-bottom h3 a:hover{color:#784400}.blog-item .blog-bottom p{color:#2e2e30;margin-bottom:0}.blog-item .blog-bottom .cmn-btn{position:absolute;right:15px;bottom:-20px}.blog-area .read-blog-btn{text-align:center;display:inline-block;font-weight:500;font-size:18px;color:#0b0320;border-bottom:1px solid #0b0320}.blog-area .read-blog-btn:hover{color:#784400;border-bottom:1px solid #784400}.subscribe-area{background-color:#0b0320;border-radius:0 0 75px 0;position:relative}.subscribe-area .subscribe-shape img{position:absolute;top:0;left:0}.subscribe-item{padding-top:100px;padding-bottom:100px}.subscribe-item .section-title{text-align:left;margin-bottom:35px}.subscribe-item .section-title h2{color:#fff}.subscribe-item .section-title p{color:#fff;margin-left:0}.subscribe-item .newsletter-form{position:relative;margin-bottom:30px}.subscribe-item .newsletter-form .form-control{height:70px;padding:10px 15px 10px 30px;border-radius:50px;background-color:#fff;border:0;font-size:16px}.subscribe-item .newsletter-form .form-control ::-webkit-input-placeholder{color:#a5a4a9}.subscribe-item .newsletter-form .form-control :-ms-input-placeholder{color:#a5a4a9}.subscribe-item .newsletter-form .form-control ::-ms-input-placeholder{color:#a5a4a9}.subscribe-item .newsletter-form .form-control ::placeholder{color:#a5a4a9}.subscribe-item .newsletter-form .form-control:focus{-webkit-box-shadow:none;box-shadow:none;border:0}.subscribe-item .newsletter-form .cmn-btn{opacity:1;position:absolute;right:10px;top:10px;-webkit-transition:.5s all ease;transition:.5s all ease;padding:12px 40px}.subscribe-item .newsletter-form .validation-danger{font-size:14px;color:#fff;margin-top:10px}.subscribe-item .social-link ul{margin:0;padding:0}.subscribe-item .social-link ul li{list-style-type:none;display:inline-block;margin-right:12px}.subscribe-item .social-link ul li a{display:block;width:40px;height:40px;line-height:46px;border-radius:50%;text-align:center;font-size:20px;color:#fff;background-color:#1a1c3b}.subscribe-item .social-link ul li a:hover{background-color:#784400}.subscribe-img{text-align:center}.footer-item{margin-bottom:30px}.footer-item .footer-logo a{display:block;margin-bottom:25px}.footer-item .footer-logo a .footer-logo2{display:none}.footer-item .footer-logo p{color:#0b0320;margin-bottom:25px}.footer-item .footer-logo ul{margin:0;padding:0}.footer-item .footer-logo ul li{list-style-type:none;display:inline-block;margin-right:5px}.footer-item .footer-logo ul li a{display:block;width:35px;height:35px;line-height:39px;border-radius:50%;text-align:center;font-size:18px;color:#fff;background-color:#1a1c3b;margin-bottom:0}.footer-item .footer-logo ul li a:hover{background-color:#784400}.footer-item .footer-service h3{font-weight:500;font-size:22px;font-family:poppins,sans-serif;margin-bottom:30px}.footer-item .footer-service ul{margin:0;padding:0}.footer-item .footer-service ul li{list-style-type:none;display:block;margin-bottom:15px;color:#00011e;position:relative;padding-left:28px}.footer-item .footer-service ul li i{display:inline-block;font-size:20px;margin-right:3px;position:absolute;top:3px;left:0}.footer-item .footer-service ul li a{display:block;color:#00011e}.footer-item .footer-service ul li a:hover{margin-left:10px;color:#784400}.footer-item .footer-service ul li:last-child{margin-bottom:0}.copyright-area{background-color:#0b0320;padding-top:30px;padding-bottom:30px;border-radius:35px 35px 0 0}.copyright-area .copyright-item{text-align:center}.copyright-area .copyright-item p{color:#fff;font-size:15px;margin-bottom:0}.copyright-area .copyright-item p a{display:inline-block;color:#f7e9ac;font-weight:500}.copyright-area .copyright-item p a:hover{color:#fff}.main-nav-two .nav-two-logo-one{display:block}.main-nav-two .nav-two-logo-two{display:none}.main-nav-two nav .navbar-nav .nav-item a{color:#fff}.main-nav-two nav .navbar-nav .nav-item .dropdown-menu li a{color:#0b0320}.main-nav-two nav .side-nav .nav-cart{border:1px solid #fff;color:#fff}.main-nav-two nav .side-nav .nav-cart:hover{color:#fff;background-color:#0b0320;border:1px solid #0b0320}.main-nav-two nav .side-nav .nav-cart span{color:#0b0320;background-color:#fff}.main-nav-two nav .side-nav .modal-btn{width:42px;height:42px;line-height:48px;background-color:#784400;border-radius:50%;padding:0;font-size:28px;margin-left:15px;position:relative;top:-7px;-webkit-transition:.5s all ease;transition:.5s all ease}.main-nav-two nav .side-nav .modal-btn:hover{color:#fff;background-color:#0b0320}.main-nav-two.menu-shrink .nav-two-logo-one{display:none}.main-nav-two.menu-shrink .nav-two-logo-two{display:block}.main-nav-two.menu-shrink nav .navbar-nav .nav-item:hover a{color:#0b0320}.main-nav-two.menu-shrink nav .navbar-nav .nav-item a{color:#0b0320}.main-nav-two.menu-shrink nav .navbar-nav .nav-item a:focus,.main-nav-two.menu-shrink nav .navbar-nav .nav-item a:hover,.main-nav-two.menu-shrink nav .navbar-nav .nav-item a.active{color:#784400}.main-nav-two.menu-shrink nav .side-nav .nav-cart{border:1px solid #784400;color:#0b0320;background-color:#784400}.main-nav-two.menu-shrink nav .side-nav .nav-cart:hover{color:#fff;background-color:#0b0320;border:1px solid #0b0320}.main-nav-two.menu-shrink nav .side-nav .nav-cart span{color:#784400;background-color:#0b0320}#myModalRight{z-index:99999}#myModalRight .modal-content .modal-header img{max-width:120px}#myModalRight .modal-content .modal-header .modal-header-logo2{display:none}#myModalRight .modal-content .modal-header .close{position:relative;top:3px}#myModalRight .modal-content .modal-body{padding:40px 30px 50px}#myModalRight .modal-content .modal-body h2{font-size:20px;font-weight:600;margin-bottom:12px;color:#0b0320}#myModalRight .modal-content .modal-body p{color:#2e2e30;font-size:14px;margin-bottom:20px}#myModalRight .modal-content .modal-body .image-area{margin-bottom:10px}#myModalRight .modal-content .modal-body .image-area .col-lg-4{padding-right:5px;margin-right:-5px}#myModalRight .modal-content .modal-body .image-area a{display:block;margin-bottom:15px}#myModalRight .modal-content .modal-body .modal-item{margin-bottom:30px}#myModalRight .modal-content .modal-body .social-area{text-align:center}#myModalRight .modal-content .modal-body .social-area h3{font-size:20px;margin-bottom:12px;font-weight:600;color:#0b0320}#myModalRight .modal-content .modal-body .social-area ul{margin:0;padding:0}#myModalRight .modal-content .modal-body .social-area ul li{list-style-type:none;display:inline-block}#myModalRight .modal-content .modal-body .social-area ul li a{display:block;color:#0b0320;border:1px solid #0b0320;width:32px;height:32px;line-height:34px;border-radius:50%;margin-right:3px;margin-left:3px;font-size:16px;text-align:center}#myModalRight .modal-content .modal-body .social-area ul li a:hover{color:#fff;background-color:#0b0320}#myModalRight .btn-close:focus{-webkit-box-shadow:none;box-shadow:none;border:0}.modal.modal-right .modal-dialog{max-width:380px;min-height:100vh}.modal.modal-right.show .modal-dialog{-webkit-transform:translate(0,0);transform:translate(0,0)}.modal.modal-right .modal-content{height:100vh;overflow-y:auto;border-radius:0}.modal.modal-left .modal-dialog{-webkit-transform:translate(-100%,0);transform:translate(-100%,0);margin:0 auto 0 0}.modal.modal-right .modal-dialog{-webkit-transform:translate(100%,0);transform:translate(100%,0);margin:0 0 0 auto}.banner-area-two{background-image:url(../img/home-two/banner/1.jpg);background-size:cover;background-position:center center;background-repeat:no-repeat;position:relative}.banner-area-two:before{position:absolute;content:'';width:100%;height:100%;left:0;top:0;background-color:#0b0320;opacity:.6}.banner-area-two .banner-shape img{position:absolute}.banner-area-two .banner-shape img:nth-child(1){bottom:60px;left:70px;max-width:120px}.banner-area-two .banner-shape img:nth-child(2){top:100px;left:100px;max-width:120px}.banner-area-two .banner-shape img:nth-child(3){top:245px;left:680px;max-width:120px}.banner-area-two .banner-content{position:relative;padding-top:230px;padding-bottom:200px}.banner-area-two .banner-content h1{color:#fff;font-weight:700;font-size:65px;margin-bottom:20px;max-width:700px}.banner-area-two .banner-content p{color:#fff;margin-bottom:35px;max-width:575px;margin-left:0}.banner-area-two .banner-content .banner-btn-wrap .cmn-btn{margin-right:18px}.banner-area-two .banner-content .banner-btn-wrap .banner-btn-two{font-weight:500;color:#fff;border:1px solid #fff;border-radius:50px;display:inline-block;padding:11px 30px}.banner-area-two .banner-content .banner-btn-wrap .banner-btn-two:hover{background-color:#fff;color:#0b0320}.banner-area-two .banner-img{position:absolute;bottom:0;right:80px}.banner-area-two .banner-img img{max-width:415px}.food-img-area{margin-top:-55px}.food-img-area .food-img-item{text-align:center;-webkit-transition:.5s all ease;transition:.5s all ease;margin-bottom:30px}.food-img-area .food-img-item:hover{-webkit-transform:scale(1.1);transform:scale(1.1)}.food-img-area .food-img-item img{max-width:100px;margin-left:auto;margin-right:auto;z-index:1;position:relative}.about-area{position:relative}.about-area .about-shape img{position:absolute}.about-area .about-shape img:nth-child(1){right:0;top:95px;max-width:780px;z-index:-1}.about-area .about-shape img:nth-child(2){right:98px;top:92px}.about-area .about-shape img:nth-child(3){right:255px;top:138px}.about-area .about-shape img:nth-child(4){right:450px;top:108px}.about-area .about-shape img:nth-child(5){right:650px;top:88px}.about-area .about-img{position:relative;text-align:right}.about-area .about-img img:nth-child(1){position:absolute;left:0;bottom:0}.about-area .about-img img:nth-child(2){position:relative;max-width:335px;right:50px}.about-area .about-content{max-width:620px;margin-top:100px;padding-left:40px}.about-area .about-content .section-title{text-align:left;margin-bottom:35px}.about-area .about-content .section-title h2{margin-bottom:25px}.about-area .about-content .section-title p{margin-left:0;margin-bottom:10px}.service-area-two{background-color:unset}.collection-area-two{background-color:#fffdf9}.download-area .download-content{margin-bottom:30px}.download-area .download-content .section-title{text-align:left}.download-area .download-content .section-title p{margin-left:0}.download-area .download-content ul{margin:0;padding:0}.download-area .download-content ul li{list-style-type:none;display:block;padding:20px;background-color:#fff;border-radius:10px;-webkit-box-shadow:0 0 20px 0 #dddddd52;box-shadow:0 0 20px 0 #dddddd52;margin-bottom:30px;max-width:390px;font-weight:500;font-size:20px;color:#0b0320;-webkit-transition:.5s all ease;transition:.5s all ease;border:1px solid transparent}.download-area .download-content ul li:hover{margin-left:15px;border:1px solid #784400}.download-area .download-content ul li:last-child{margin-bottom:0}.download-area .download-content ul li span{width:50px;height:50px;line-height:50px;border-radius:50%;text-align:center;background-color:#784400;color:#0b0320;display:inline-block;font-weight:600;font-size:25px;margin-right:15px;-webkit-box-shadow:0 0 20px 0 #dddddd52;box-shadow:0 0 20px 0 #dddddd52}.download-area .download-content .app-wrap{margin-top:50px}.download-area .download-content .app-wrap a{display:inline-block;margin-right:20px;max-width:180px}.download-area .download-content .app-wrap a:hover{-webkit-transform:scale(1.1);transform:scale(1.1)}.download-area .download-img{margin-bottom:30px;text-align:center}.download-area .download-img img{-webkit-animation:download-animation 3s infinite linear;animation:download-animation 3s infinite linear}@-webkit-keyframes download-animation{0%{-webkit-transform:translate(0,0);transform:translate(0,0)}50%{-webkit-transform:translate(0,-20px);transform:translate(0,-20px)}100%{-webkit-transform:translate(0,0);transform:translate(0,0)}}@keyframes download-animation{0%{-webkit-transform:translate(0,0);transform:translate(0,0)}50%{-webkit-transform:translate(0,-20px);transform:translate(0,-20px)}100%{-webkit-transform:translate(0,0);transform:translate(0,0)}}.join-area{background-color:#0b0320;margin-top:130px}.join-area .join-img{position:relative;text-align:center}.join-area .join-img img{position:absolute;left:0;top:-132px;max-width:490px;right:0;margin-left:auto;margin-right:auto}.join-area .join-content{padding-top:100px;padding-bottom:100px}.join-area .join-content .section-title{margin-bottom:30px;text-align:left}.join-area .join-content .section-title h2{color:#784400;margin-bottom:22px}.join-area .join-content .section-title p{color:#fff;margin-left:0}.join-area .join-content .cmn-btn{border:1px solid #784400}.join-area .join-content .cmn-btn:hover{border:1px solid #fff}.chef-area-two{padding-top:100px}.review-area-two{background-color:#0b0320;position:relative}.review-area-two .review-shape img{position:absolute;bottom:0;right:0;max-width:200px;-webkit-animation:review-animation 8s infinite linear;animation:review-animation 8s infinite linear}.review-area-two .review-img{text-align:center}.review-area-two .review-img img{border-radius:42px}.review-area-two .review-item{padding-top:100px;padding-bottom:150px}.review-area-two .review-item .section-title h2{color:#784400}.review-area-two .review-item .section-title p{color:#fff}.review-area-two .review-item .slider-for h3{color:#784400}.review-area-two .review-item .slider-for p{color:#fff}.review-area-two .slick-prev i{left:230px}.review-area-two .slick-prev i:hover{color:#fff}.review-area-two .slick-next i{right:230px}.review-area-two .slick-next i:hover{color:#fff}@-webkit-keyframes review-animation{0%{-webkit-transform:translate(0,0);transform:translate(0,0)}50%{-webkit-transform:translate(-50px,0);transform:translate(-50px,0)}10%{-webkit-transform:translate(0,0);transform:translate(0,0)}}@keyframes review-animation{0%{-webkit-transform:translate(0,0);transform:translate(0,0)}50%{-webkit-transform:translate(-50px,0);transform:translate(-50px,0)}10%{-webkit-transform:translate(0,0);transform:translate(0,0)}}.footer-area-two{background-color:#0b0320;border-radius:45px 45px 0 0;border-bottom:1px solid #8788988c}.footer-area-two .footer-item .footer-logo p{color:#fff}.footer-area-two .footer-item .footer-service h3{color:#fff}.footer-area-two .footer-item .footer-service ul li{color:#fff}.footer-area-two .footer-item .footer-service ul li a{color:#fff}.footer-area-two .footer-item .footer-service ul li a:hover{color:#784400}.copyright-area-two{border-radius:0}.banner-area-three{background-image:url(../img/home-three/banner-main.jpg);background-size:cover;background-position:center center;background-repeat:no-repeat;position:relative}.banner-area-three:before{position:absolute;content:'';width:100%;height:100%;left:0;top:0;background-color:#fff;opacity:.9}.banner-area-three .banner-shape img{position:absolute}.banner-area-three .banner-shape img:nth-child(1){bottom:35px;left:140px;max-width:120px}.banner-area-three .banner-shape img:nth-child(2){top:85px;left:45px;max-width:120px}.banner-area-three .banner-shape img:nth-child(3){bottom:0;right:20px;max-width:120px}.banner-area-three .banner-content{position:relative;padding-top:230px;padding-bottom:150px}.banner-area-three .banner-content h1{color:#444;font-weight:700;font-size:65px;margin-bottom:20px;max-width:700px}.banner-area-three .banner-content p{color:#444;margin-bottom:35px;max-width:575px;margin-left:0}.banner-area-three .banner-content .banner-btn-wrap .cmn-btn{margin-right:18px}.banner-area-three .banner-content .banner-btn-wrap .banner-btn-two{font-weight:500;color:#444;border:1px solid #444;border-radius:50px;display:inline-block;padding:11px 30px}.banner-area-three .banner-content .banner-btn-wrap .banner-btn-two:hover{background-color:#0b0320;border:1px solid #0b0320;color:#fff}.banner-area-three .banner-img{position:absolute;bottom:140px;right:40px}.banner-area-three .banner-img img{max-width:505px;border-radius:12px}.about-area-two{position:relative}.about-area-two .about-shape img{position:absolute;bottom:60px;right:65px;max-width:190px;-webkit-animation:review-animation 8s infinite linear;animation:review-animation 8s infinite linear}.about-area-two .about-img{margin-bottom:80px;position:relative;text-align:center}.about-area-two .about-img img:nth-child(1){border-radius:10px;max-width:470px;margin-left:auto;margin-right:auto;top:28px;position:relative}.about-area-two .about-img img:nth-child(2){position:absolute;left:0;top:0}.about-area-two .about-img .video-wrap{position:absolute;bottom:0;right:-35px}.about-area-two .about-img .video-wrap a{z-index:10;display:block;width:100px;height:100px;line-height:110px;border-radius:50%;position:relative;font-size:60px;text-align:center;color:#fff}.about-area-two .about-img .video-wrap a:before{content:"";position:absolute;z-index:0;left:50%;top:50%;-webkit-transform:translateX(-50%) translateY(-50%);transform:translateX(-50%) translateY(-50%);display:block;width:100px;height:100px;background-color:#0b0320;border-radius:50%;z-index:-1;-webkit-animation:pulse-border 1500ms ease-out infinite;animation:pulse-border 1500ms ease-out infinite}.about-area-two .about-img .video-wrap a:after{content:"";position:absolute;z-index:1;left:50%;top:50%;-webkit-transform:translateX(-50%) translateY(-50%);transform:translateX(-50%) translateY(-50%);display:block;width:100px;height:100px;background-color:#0b0320;border-radius:50%;z-index:-1;-webkit-transition:.5s all ease;transition:.5s all ease}.about-area-two .about-img .video-wrap a:hover{color:#fff}.about-area-two .about-img .video-wrap a:hover:before{background-color:#784400}.about-area-two .about-img .video-wrap a:hover:after{background-color:#784400}.about-area-two .about-content{margin-bottom:30px;padding-left:60px}.about-area-two .about-content .section-title{text-align:left;margin-bottom:32px}.about-area-two .about-content .section-title p{margin-bottom:10px}@-webkit-keyframes pulse-border{0%{-webkit-transform:translateX(-50%) translateY(-50%) translateZ(0) scale(1);transform:translateX(-50%) translateY(-50%) translateZ(0) scale(1);opacity:1}100%{-webkit-transform:translateX(-50%) translateY(-50%) translateZ(0) scale(1.5);transform:translateX(-50%) translateY(-50%) translateZ(0) scale(1.5);opacity:0}}@keyframes pulse-border{0%{-webkit-transform:translateX(-50%) translateY(-50%) translateZ(0) scale(1);transform:translateX(-50%) translateY(-50%) translateZ(0) scale(1);opacity:1}100%{-webkit-transform:translateX(-50%) translateY(-50%) translateZ(0) scale(1.5);transform:translateX(-50%) translateY(-50%) translateZ(0) scale(1.5);opacity:0}}.service-area-three{background-color:#0b0320}.service-area-three .service-item .section-title{text-align:left}.service-area-three .service-item .section-title h2{color:#fff}.service-area-three .service-item .section-title p{color:#fff;margin-left:0}.service-area-three .service-item .accordion{padding-left:0;margin-bottom:0}.service-area-three .service-item .accordion a{padding:13px 15px;border-radius:8px;background-color:#fff;width:100%;font-weight:500;display:block;cursor:pointer;font-size:15px;color:#fff;color:#0b0320}.service-area-three .service-item .accordion a:after{position:absolute;content:'+';width:15px;height:2px;right:12px;top:10px;font-size:20px;color:#0b0320;background-color:transparent}.service-area-three .service-item .accordion a span{display:inline-block;font-weight:600;color:#0b0320;font-size:15px;margin-right:10px}.service-area-three .service-item .accordion .active:after{content:'x';top:11px;font-size:17px}.service-area-three .service-item .accordion p{display:none;margin-bottom:0;color:#fff;font-size:15px;padding:15px 8px 5px 10px}.service-area-three .service-item .accordion li{position:relative;list-style-type:none;margin-bottom:30px}.service-area-three .service-img{margin-bottom:30px;text-align:center}.service-area-three .service-img img{max-width:420px;margin-left:auto;margin-right:auto;-webkit-animation:service-two-animation 10s infinite linear;animation:service-two-animation 10s infinite linear}@-webkit-keyframes service-two-animation{0%{-webkit-transform:scale(1);transform:scale(1)}50%{-webkit-transform:scale(1.1);transform:scale(1.1)}100%{-webkit-transform:scale(1);transform:scale(1)}}@keyframes service-two-animation{0%{-webkit-transform:scale(1);transform:scale(1)}50%{-webkit-transform:scale(1.1);transform:scale(1.1)}100%{-webkit-transform:scale(1);transform:scale(1)}}.join-area-two{margin-top:132px}.footer-item .footer-logo .footer-subscribe{position:relative}.footer-item .footer-logo .footer-subscribe .form-control{height:45px;border-radius:50px;-webkit-box-shadow:0 0 20px 0 #dddddd7d;box-shadow:0 0 20px 0 #dddddd7d;border:0;padding-left:20px;font-size:15px}.footer-item .footer-logo .footer-subscribe ::-webkit-input-placeholder{color:#b2b2b2}.footer-item .footer-logo .footer-subscribe :-ms-input-placeholder{color:#b2b2b2}.footer-item .footer-logo .footer-subscribe ::-ms-input-placeholder{color:#b2b2b2}.footer-item .footer-logo .footer-subscribe ::placeholder{color:#b2b2b2}.footer-item .footer-logo .footer-subscribe .footer-btn{width:35px;height:35px;line-height:37px;font-size:18px;border-radius:50%;color:#0b0320;background-color:#eec900;-webkit-transition:.5s all ease;transition:.5s all ease;position:absolute;top:5px;right:6px;padding:0}.footer-item .footer-logo .footer-subscribe .footer-btn:hover{color:#fff;background-color:#0b0320}.main-nav-three{background-color:#fff}.page-title-img-one{background-image:url(../img/about/page-title.jpg)}.page-title-area{background-position:center center;background-repeat:no-repeat;background-size:cover;padding-top:230px;padding-bottom:135px}.page-title-item{padding-left:40px;position:relative}.page-title-item:before{position:absolute;content:'';width:10px;height:100px;left:0;top:0;border-radius:50px;background-color:#784400}.page-title-item h2{font-size:40px;color:#fff;margin-bottom:10px}.page-title-item ul{margin:0;padding:0}.page-title-item ul li{list-style-type:none;display:inline-block;color:#784400}.page-title-item ul li i{display:inline-block;font-size:25px;position:relative;top:6px;color:#fff}.page-title-item ul li a{display:inline-block;color:#fff}.page-title-item ul li a:hover{color:#784400}.story-area{text-align:center;position:relative}.story-area .story-shape img{position:absolute;right:0;bottom:0;max-width:135px}.story-area .story-head{margin-bottom:50px}.story-area .story-head h2{font-weight:700;font-size:36px;margin-bottom:25px;margin-top:-10px}.story-area .story-head p{max-width:825px;margin-left:auto;margin-right:auto;margin-bottom:0}.story-area .story-item{margin-bottom:30px;position:relative;-webkit-transition:.5s all ease;transition:.5s all ease}.story-area .story-item:hover{-webkit-transform:translate(0,-10px);transform:translate(0,-10px)}.story-area .story-item:hover h3{-webkit-transform:translate(0,10px);transform:translate(0,10px);color:#fff;background-color:#784400}.story-area .story-item img{width:100%;border-radius:12px}.story-area .story-item h3{text-align:center;font-weight:600;font-size:20px;font-family:poppins,sans-serif;background-color:#fff;border-radius:10px;max-width:370px;margin-left:auto;margin-right:auto;padding:18px;margin-bottom:0;position:absolute;left:0;right:0;bottom:30px;-webkit-transition:.5s all ease;transition:.5s all ease}.download-area-two{background-color:#0b0320;position:relative;border-radius:0 0 80px 0}.download-area-two .download-shape img{position:absolute}.download-area-two .download-shape img:nth-child(1){left:0;top:0}.download-area-two .download-shape img:nth-child(2){left:100px;bottom:20px;-webkit-animation:download-one 10s infinite linear;animation:download-one 10s infinite linear}.download-area-two .download-item{margin-bottom:30px}.download-area-two .download-item h2{color:#fff;font-weight:700;font-size:35px;margin-bottom:40px}.download-area-two .download-item ul{margin:0;padding:0}.download-area-two .download-item ul li{list-style-type:none;display:inline-block;margin-right:20px}.download-area-two .download-item ul li:last-child{margin-right:0}.download-area-two .download-item ul li a{display:block;max-width:180px}.download-area-two .download-item ul li a:hover{-webkit-transform:scale(1.1);transform:scale(1.1)}.download-area-two .download-img{position:relative}.download-area-two .download-img img{position:absolute;top:-55px;right:0;left:0;max-width:460px;margin-left:auto;margin-right:auto}@-webkit-keyframes download-one{0%{-webkit-transform:translate(0,0);transform:translate(0,0)}30%{-webkit-transform:translate(0,-100px);transform:translate(0,-100px)}70%{-webkit-transform:translate(100px,0);transform:translate(100px,0)}100%{-webkit-transform:translate(0,0);transform:translate(0,0)}}@keyframes download-one{0%{-webkit-transform:translate(0,0);transform:translate(0,0)}30%{-webkit-transform:translate(0,-100px);transform:translate(0,-100px)}70%{-webkit-transform:translate(100px,0);transform:translate(100px,0)}100%{-webkit-transform:translate(0,0);transform:translate(0,0)}}.footer-item .footer-logo .footer-subscriber-two .form-control{-webkit-box-shadow:none;box-shadow:none}.service-area-four .service-item{border:1px solid #784400;margin-bottom:30px}.page-title-area-two{background-color:#0b0320;position:relative}.page-title-area-two:before{position:absolute;content:'';width:100%;height:100%;top:0;right:0;background-color:#784400;-webkit-clip-path:polygon(70% 0,100% 0%,100% 100%,40% 100%);clip-path:polygon(70% 0,100% 0%,100% 100%,40% 100%)}.page-title-area-two .page-title-plate ul{margin:0;padding:0;margin-left:75px}.page-title-area-two .page-title-plate ul li{list-style-type:none;display:inline-block;position:relative}.page-title-area-two .page-title-plate ul li img{display:inline-block;max-width:130px}.page-title-area-two .page-title-plate ul li:nth-child(1){z-index:4}.page-title-area-two .page-title-plate ul li:nth-child(2){left:-25px;top:0;z-index:3}.page-title-area-two .page-title-plate ul li:nth-child(3){left:-55px;top:0;z-index:2}.page-title-area-two .page-title-plate ul li:nth-child(4){left:-85px;top:0}.service-details-area .service-details-item{margin-bottom:30px}.service-details-area .service-details-item .service-details-more h3{font-weight:700;font-size:25px;margin-bottom:30px}.service-details-area .service-details-item .service-details-more ul{margin:0;padding:0}.service-details-area .service-details-item .service-details-more ul li{list-style-type:none;display:block;-webkit-box-shadow:0 0 20px 0 #dddddd8a;box-shadow:0 0 20px 0 #dddddd8a;margin-bottom:20px;position:relative;border-radius:10px;-webkit-transition:.5s all ease;transition:.5s all ease}.service-details-area .service-details-item .service-details-more ul li:hover{-webkit-box-shadow:none;box-shadow:none}.service-details-area .service-details-item .service-details-more ul li:hover a{color:#fff;background-color:#784400}.service-details-area .service-details-item .service-details-more ul li a{display:block;font-weight:500;font-size:15px;color:#0b0320;padding:15px 20px;border-radius:10px}.service-details-area .service-details-item .service-details-more ul li a i{display:inline-block;position:absolute;top:18px;right:17px;font-weight:700}.service-details-area .service-details-item .service-details-order{text-align:center;background-color:#0b0320;border-radius:10px;padding:30px 10px 0;position:relative;margin-top:30px}.service-details-area .service-details-item .service-details-order h3{font-weight:700;font-size:20px;color:#fff;margin-bottom:10px}.service-details-area .service-details-item .service-details-order span{display:block;color:#fff;margin-bottom:30px}.service-details-area .service-details-item .service-details-order .offer-off{background-color:#784400;width:70px;height:70px;border-radius:50%;text-align:center;padding-top:8px;position:absolute;bottom:135px;right:40px}.service-details-area .service-details-item .service-details-order .offer-off span{display:block;font-weight:600;font-size:18px;color:#0b0320;margin-bottom:0}.service-details-area .service-details-item .service-details-fresh h2{font-weight:700;font-size:35px;margin-bottom:15px}.service-details-area .service-details-item .service-details-fresh p{margin-bottom:25px}.service-details-area .service-details-item .service-details-fresh img{margin-bottom:25px;border-radius:18px}.service-details-area .service-details-item .service-details-fresh .service-details-p{margin-top:-15px}.page-title-img-two{background-image:url(../img/blog-details/1.jpg);position:relative}.page-title-img-two:before{position:absolute;content:'';width:100%;height:100%;left:0;top:0;background-color:#0b0320;-webkit-clip-path:polygon(0 0,60% 0%,40% 100%,0% 100%);clip-path:polygon(0 0,60% 0%,40% 100%,0% 100%);opacity:.9}.blog-details-more{padding-bottom:20px}.blog-details-tags h3{font-weight:700;font-size:25px;margin-bottom:30px}.blog-details-tags ul{margin:0;padding:0}.blog-details-tags ul li{list-style-type:none;display:inline-block;margin-right:5px;margin-bottom:10px}.blog-details-tags ul li a{display:block;font-size:12px;color:#696969;background-color:#f0f0f0;border-radius:30px;padding:7px 16px}.blog-details-tags ul li a:hover{color:#fff;background-color:#0b0320}.blog-details-nav ul{margin:0;padding:0}.blog-details-nav ul li{list-style-type:none;display:inline-block;margin-right:12px}.blog-details-nav ul li a{display:block;font-weight:500;font-size:15px;color:#0b0320;border:1px solid #0b0320;padding:9px 25px;border-radius:50px}.blog-details-nav ul li a:hover{border:1px solid #784400;background-color:#784400;color:#0b0320}.cart-wrap{text-align:center}.cart-wrap .table{margin:0}.cart-wrap .table .thead{background-color:#784400}.cart-wrap .table .thead tr .table-head{color:#fff;font-size:18px;padding-top:18px;padding-bottom:18px}.cart-wrap .table tr .table-item img{width:50px}.cart-wrap .table tr td{font-size:15px;color:#0b0320}.cart-wrap .table tr td a{color:#0b0320;display:block;font-weight:700}.cart-wrap .table tr td a:hover{color:#784400}.cart-wrap .table td,.cart-wrap .table th{border:1px solid #dee2e6;vertical-align:middle}.cart-wrap .table>:not(:first-child){border-top:0}.cart-wrap .shop-back{margin-bottom:20px}.cart-wrap .shop-back a{display:block;color:#0b0320;font-weight:500;margin-top:20px;font-size:15px}.cart-wrap .shop-back a:hover{color:#784400}.cart-wrap .total-shopping h2{color:#0b0320;font-size:35px;padding-bottom:5px;border-bottom:1px solid #000;display:inline-block;margin:0;margin-bottom:20px;font-weight:700}.cart-wrap .total-shopping h3{color:#0b0320;font-size:20px}.cart-wrap .total-shopping h3 span{display:inline-block;margin-left:70px}.cart-wrap .total-shopping a{margin-top:20px;display:inline-block;color:#fff;background-color:#784400;font-size:17px;padding:15px 60px}.cart-wrap .total-shopping a:hover{background-color:#0b0320}.checkout-area .cmn-btn{padding:12px 45px;-webkit-transition:.5s all ease;transition:.5s all ease}.checkout-item{border:1px solid #dddddd40;margin-bottom:30px}.checkout-item h2{text-align:center;font-size:35px;margin-bottom:50px;background-color:#784400;padding-top:15px;padding-bottom:15px;font-weight:700;color:#fff}.checkout-item .checkout-one{max-width:515px;margin:auto;padding-bottom:20px}.checkout-item .checkout-one label{color:#0b0320;font-size:15px;margin-right:10px;position:relative;top:2px;font-weight:500}.checkout-item .checkout-one .form-group{margin:0;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-bottom:30px}.checkout-item .checkout-one .form-group .form-control{display:inline-block;width:78%;background-color:#dddddd40;border:1px solid transparent;border-radius:0;height:45px;font-size:15px}.checkout-item .checkout-one .form-group .form-control:focus{border:1px solid #784400;-webkit-box-shadow:none;box-shadow:none}.checkout-item .checkout-two{max-width:460px;margin:auto;padding-bottom:45px}.checkout-item .checkout-two h3{margin:0;margin-bottom:15px;font-size:22px;font-weight:500;font-family:poppins,sans-serif}.checkout-item .checkout-two p{font-size:15px;margin-bottom:10px}.checkout-item .checkout-two .form-check{margin-top:15px;margin-bottom:15px}.checkout-item .checkout-two .form-check span{display:inline-block;color:#797979;font-size:16px}.checkout-item .checkout-two .form-check span a{color:#797979}.checkout-item .checkout-two .form-check span a:hover{color:#0b0320}.checkout-item .checkout-two .form-check .form-check-input{margin-top:5px;width:14px;height:14px}.checkout-item .checkout-two .text-center{position:relative}.checkout-item .checkout-two .text-center:before{position:absolute;content:'';width:100%;height:1px;left:0;top:14px;background-color:#ddd}.checkout-item .checkout-two .text-center span{display:inline-block;color:#0b0320;font-size:15px;margin-bottom:20px;background-color:#fff;position:relative;padding-left:10px;padding-right:10px}.checkout-item .checkout-two ul{margin:0;padding:0;text-align:center}.checkout-item .checkout-two ul li{list-style-type:none;display:inline-block;margin-right:4px;margin-left:4px}.checkout-item .checkout-two ul li a{border:1px solid transparent;display:block}.checkout-item .checkout-two ul li a:hover{border:1px solid #784400}.coming-item{height:100vh;text-align:center}.coming-item h1{font-size:75px;font-weight:700;font-style:italic;margin-bottom:20px}.coming-item p{margin-bottom:40px;max-width:865px;margin-left:auto;margin-right:auto}.coming-item .coming-wrap{max-width:700px;margin-left:auto;margin-right:auto;margin-bottom:30px}.coming-item .coming-wrap .coming-inner{text-align:center;background-color:#efefef;padding-top:15px;padding-bottom:12px;margin-bottom:30px}.coming-item .coming-wrap .coming-inner h3{font-size:40px;font-weight:600;color:#232323;margin-bottom:5px}.coming-item .coming-wrap .coming-inner p{font-size:16px;margin-bottom:0}.coming-item ul{margin:0;padding:0}.coming-item ul li{list-style-type:none;display:inline-block;margin-right:2px;margin-left:2px}.coming-item ul li a{display:block;color:#fff;background-color:#784400;width:35px;height:35px;line-height:39px;border-radius:50%;font-size:16px}.coming-item ul li a:hover{background-color:#0b0320}.faq-area .faq-head h2{margin-bottom:35px;font-weight:600;font-size:30px;margin-top:-7px}.faq-area .faq-wrap{margin-bottom:50px}.faq-area .faq-wrap:last-child{margin-bottom:30px}.faq-area .accordion{padding-left:0;margin:0;padding:0}.faq-area .accordion p{font-size:15px;display:none;padding:20px 45px 15px 20px;margin-bottom:0}.faq-area .accordion a{color:#232323;font-size:17px;width:100%;display:block;cursor:pointer;font-weight:600;padding:15px 0 15px 18px;border:1px solid #232323;border-radius:8px 8px 0 0}.faq-area .accordion a:hover{color:#0b0320}.faq-area .accordion a:after{position:absolute;right:20px;content:"+";top:10px;color:#232323;font-size:25px;font-weight:700}.faq-area .accordion li{position:relative;list-style-type:none;margin-bottom:30px}.faq-area .accordion li:first-child{border-top:0}.faq-area .accordion li:last-child{margin-bottom:0}.faq-area .accordion li a.active{color:#fff;background-color:#0b0320;border:1px solid #0b0320}.faq-area .accordion li a.active:after{content:"-";font-size:25px;color:#fff}.privacy-area{padding-bottom:50px}.privacy-item{margin-bottom:50px}.privacy-item h2{font-size:26px;margin-bottom:15px;font-weight:600;font-family:poppins,sans-serif}.privacy-item p{margin-bottom:0}.privacy-item ul{margin:0;padding:0}.privacy-item ul li{list-style-type:none;display:block;margin-bottom:18px}.privacy-item ul li i{display:inline-block;font-size:20px;position:relative;bottom:-2px}.privacy-item ul li:last-child{margin-bottom:0}.error-item{height:700px;text-align:center;margin-top:25px}.error-item h1{font-size:130px;font-weight:700;margin-bottom:8px;font-family:poppins,sans-serif}.error-item p{margin-bottom:10px;font-weight:600;font-size:35px}.error-item span{display:block}.error-item a{display:inline-block;color:#fff;background-color:#784400;border-radius:10px;padding:16px 40px;margin-top:70px;font-size:18px}.error-item a:hover{background-color:#0b0320}.page-title-img-three{background-image:url(../img/contact-bg.jpg);position:relative}.page-title-img-three:before{position:absolute;content:'';width:100%;height:100%;top:0;left:0;opacity:.7;background-color:#0b0320}.contact-location-area{background-color:#fffdf9}.contact-location-area .location-item{text-align:center;background-color:#fff;padding:40px 20px;border-radius:20px;position:relative;margin-bottom:30px;-webkit-transition:.5s all ease;transition:.5s all ease;z-index:1}.contact-location-area .location-item:hover,.contact-location-area .location-item.active{background-color:#784400}.contact-location-area .location-item:hover img,.contact-location-area .location-item.active img{opacity:1}.contact-location-area .location-item:hover i,.contact-location-area .location-item.active i{color:#fff;background-color:#0b0320}.contact-location-area .location-item:hover ul li,.contact-location-area .location-item.active ul li{color:#fff}.contact-location-area .location-item img{position:absolute;top:0;left:0;width:100%;height:100%;opacity:0;-webkit-transition:.5s all ease;transition:.5s all ease;z-index:-1}.contact-location-area .location-item i{width:65px;height:65px;line-height:65px;border-radius:50%;text-align:center;display:block;font-size:33px;color:#0b0320;margin-bottom:15px;margin-left:auto;margin-right:auto;-webkit-transition:.5s all ease;transition:.5s all ease;background-color:#784400}.contact-location-area .location-item ul{margin:0;padding:0}.contact-location-area .location-item ul li{list-style-type:none;display:block;color:#0b0320;font-weight:500;font-size:16px;-webkit-transition:.5s all ease;transition:.5s all ease;margin-bottom:5px}.contact-location-area .location-item ul li:last-child{margin-bottom:0}.contact-location-area .location-item ul li a{display:block;color:#0b0320}.contact-location-area .location-item ul li a:hover{color:#fff}.contact-form-area{background-image:url(../img/contact-form-bg.jpg);background-size:cover;background-position:center center;background-repeat:no-repeat}.contact-form-area .contact-item{background-color:#ffffff9e;padding:70px 50px;border-radius:15px}.contact-form-area .contact-item #contactForm{margin-bottom:40px}.contact-form-area .contact-item #contactForm .form-group{margin-bottom:20px}.contact-form-area .contact-item #contactForm .form-group .form-control{height:55px;border-radius:30px;padding-left:30px;border:0;background-color:#fff;font-size:15px}.contact-form-area .contact-item #contactForm .form-group .form-control:focus{border:0;-webkit-box-shadow:none;box-shadow:none}.contact-form-area .contact-item #contactForm .form-group ::-webkit-input-placeholder{color:#b2b2b2}.contact-form-area .contact-item #contactForm .form-group :-ms-input-placeholder{color:#b2b2b2}.contact-form-area .contact-item #contactForm .form-group ::-ms-input-placeholder{color:#b2b2b2}.contact-form-area .contact-item #contactForm .form-group ::placeholder{color:#b2b2b2}.contact-form-area .contact-item #contactForm .form-group textarea{height:auto!important;padding-top:10px}.contact-form-area .contact-item #contactForm .cmn-btn{opacity:1;-webkit-transition:.5s all ease;transition:.5s all ease;margin-top:10px}.contact-form-area .contact-item .contact-social span{display:block;font-size:16px;color:#0b0320;margin-bottom:15px}.contact-form-area .contact-item .contact-social ul{margin:0;padding:0}.contact-form-area .contact-item .contact-social ul li{list-style-type:none;display:inline-block;margin-right:10px}.contact-form-area .contact-item .contact-social ul li:last-child{margin-right:0}.contact-form-area .contact-item .contact-social ul li a{display:block;width:40px;height:40px;line-height:45px;text-align:center;border-radius:50%;color:#fff;background-color:#0b0320;font-size:20px}.contact-form-area .contact-item .contact-social ul li a:hover{background-color:#784400}.contact-form-area .contact-item .list-unstyled{color:#dc3545;margin-bottom:0;margin-top:10px}.contact-form-area .contact-item .text-danger{color:#dc3545;margin-top:14px;margin-bottom:0}.contact-form-area .contact-item .text-success{color:#28a745;margin-top:14px;margin-bottom:0}.contact-form-area .contact-img{text-align:center;position:relative}.contact-form-area .contact-img img{position:absolute;left:0;right:0;top:43px;margin-left:auto;margin-right:auto}.book-table-area .book-table-wrap{background-color:#f5f5f5;padding:60px 80px 65px;border-radius:10px}.book-table-area .form-group{margin-bottom:30px}.book-table-area .form-group .form-control{height:50px;border-radius:5px;border:1px solid #ddd;padding-left:25px;font-size:15px}.book-table-area .form-group .form-control:focus{-webkit-box-shadow:none;box-shadow:none;border:1px solid #784400}.book-table-area .form-group textarea{height:auto!important;padding-top:15px}.book-table-area .cmn-btn{-webkit-transition:.5s all ease;transition:.5s all ease}.loader{position:fixed;top:0;left:0;width:100%;height:100%;z-index:99999;background:#0b0320}.spinner{width:50px;height:50px;margin:100px auto;background-color:#333;border-radius:100%;-webkit-animation:sk-scaleout 1s infinite ease-in-out;animation:sk-scaleout 1s infinite ease-in-out}@-webkit-keyframes sk-scaleout{0%{-webkit-transform:scale(0);transform:scale(0)}100%{-webkit-transform:scale(1);transform:scale(1);opacity:0}}@keyframes sk-scaleout{0%{-webkit-transform:scale(0);transform:scale(0)}100%{-webkit-transform:scale(1);transform:scale(1);opacity:0}}#toTop{position:fixed;bottom:30px;right:0;cursor:pointer;display:none;z-index:10}.back-to-top-btn i{background:#784400;color:#fff;height:50px;width:50px;line-height:50px;display:inline-block;text-align:center;font-size:30px;border-radius:50%;-webkit-transition:.5s all ease;transition:.5s all ease;margin-right:28px;-webkit-box-shadow:0 0 14px 0 #784400;box-shadow:0 0 14px 0 #784400}.back-to-top-btn i:hover{background-color:#0b0320;color:#fff;-webkit-box-shadow:0 0 14px 0 #0b0320;box-shadow:0 0 14px 0 #0b0320}.buy-now-btn{right:20px;z-index:99;top:50%;position:fixed;-webkit-transform:translateY(-50%);transform:translateY(-50%);border-radius:30px;display:inline-block;color:#fff;background-color:#82b440;padding:10px 20px 10px 42px;-webkit-box-shadow:0 1px 20px 1px #82b440;box-shadow:0 1px 20px 1px #82b440;font-size:13px;font-weight:600}.buy-now-btn img{top:50%;left:20px;width:15px;position:absolute;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.buy-now-btn:hover{color:#fff;background-color:#94be5d}
\ No newline at end of file
diff --git a/themes/30coffe/assets/css/theme-dark.css b/themes/30coffe/assets/css/theme-dark.css
new file mode 100644
index 00000000..f31811fe
--- /dev/null
+++ b/themes/30coffe/assets/css/theme-dark.css
@@ -0,0 +1 @@
+.switch-box{position:fixed;bottom:15px;right:120px;z-index:9999}.switch-box .slider{position:absolute;cursor:pointer;top:0;left:0;right:0;bottom:0;background-color:transparent;border:1.5px solid #fff;-webkit-transition:.4s;transition:.4s}.switch-box .slider::before{position:absolute;content:"";height:25px;width:25px;left:0;bottom:4px;top:0;bottom:0;margin:auto 0;-webkit-transition:.4s;transition:.4s;-webkit-box-shadow:0 0 15px #2020203d;box-shadow:0 0 15px #2020203d;background:#fff url(../img/night.png);background-repeat:no-repeat;background-position:center}.switch-box .slider.round{border-radius:34px}.switch-box .slider.round::before{border-radius:50%}.switch-box .switch{position:relative;display:inline-block;width:50px;height:30px}.switch-box .switch input{opacity:0;width:0;height:0}.switch-box input:checked+.slider{background-color:transparent;border:1.5px solid #fdb819}.switch-box input:focus+.slider{-webkit-box-shadow:0 0 1px #fdb819;box-shadow:0 0 1px #fdb819}.switch-box input:checked+.slider:before{-webkit-transform:translateX(24px);transform:translateX(24px);background:#fff url(../img/sunny.png);background-repeat:no-repeat;background-position:center}.theme-dark h1,.theme-dark h2,.theme-dark h3,.theme-dark h4,.theme-dark h5,.theme-dark h6{color:#fff}.theme-dark h3{color:#fff!important}.theme-dark h3 a{color:#fff!important;-webkit-transition:.7s;transition:.7s}.theme-dark h3 a:hover{color:#fdb819!important}.theme-dark p{color:#fff!important}.theme-dark .content h3{color:#fff}.theme-dark .content h3 a{color:#fff}.theme-dark .content span{color:#fff}.theme-dark .content .read-btn{color:#fff}.theme-dark body{background:#1d1d1d;color:#fff}.theme-dark .navbar-area .side-nav .nav-cart{border:1px solid #fff;color:#fff}.theme-dark .navbar-area .side-nav .nav-cart:hover{color:#fff;background-color:#fdb819}.theme-dark .navbar-area .side-nav .nav-cart span{color:#fdb819;background-color:#fff}.theme-dark .navbar-light .navbar-brand .logo-one{display:none}.theme-dark .navbar-light .navbar-brand .logo-two{display:inline-block}.theme-dark .main-nav{background-color:transparent}.theme-dark .main-nav nav .navbar-nav .nav-item a{color:#fff}.theme-dark .main-nav nav .navbar-nav .nav-item a i{color:#fff}.theme-dark .main-nav nav .navbar-nav .nav-item a:hover{color:#fdb819!important}.theme-dark .main-nav nav .navbar-nav .nav-item a.active{color:#fdb819!important}.theme-dark .main-nav nav .navbar-nav .nav-item .dropdown-menu{background-color:#252525!important}.theme-dark .main-nav nav .navbar-nav .nav-item .dropdown-menu li a{color:#fff!important}.theme-dark .main-nav nav .navbar-nav .nav-item .dropdown-menu li a.active{color:#fdb819!important}.theme-dark .main-nav nav .navbar-nav .nav-item .dropdown-menu li a:hover{color:#fdb819!important}.theme-dark .menu-shrink{background-color:#252525}.theme-dark .main-nav-two.menu-shrink .nav-two-logo-one{display:block}.theme-dark .main-nav-two.menu-shrink .nav-two-logo-two{display:none}.theme-dark #myModalRight .modal-header .btn-close{background-image:none;color:#fff;position:relative}.theme-dark #myModalRight .modal-header .btn-close::before{content:'\ec8d';font-family:boxicons!important;font-size:30px;color:#fff;position:absolute;top:-7px;left:0;right:0}.theme-dark #myModalRight .modal-header .modal-header-logo1{display:none}.theme-dark #myModalRight .modal-header .modal-header-logo2{display:inline-block}.theme-dark #myModalRight .modal-content .modal-body h2{color:#fff}.theme-dark #myModalRight .modal-content .modal-body .social-area ul li a{color:#fff;border-color:#fff;-webkit-transition:.7s;transition:.7s}.theme-dark #myModalRight .modal-content .modal-body .social-area ul li a:hover{background-color:#fdb819}.theme-dark .modal.modal-right .modal-content{background-color:#252525}.theme-dark .banner-area .banner-content form .form-control{color:#fff;background-color:#0e0e0e}.theme-dark .banner-area-three::before{background-color:#252525}.theme-dark .banner-area-three .banner-content h1{color:#fff}.theme-dark .banner-area-three .banner-content .banner-btn-wrap .banner-btn-two{color:#fff;border-color:#fff}.theme-dark .page-title-img-three:before{background-color:#0e0e0e;opacity:.8}.theme-dark .service-area{background-color:#252525}.theme-dark .service-area-three{background-color:#000}.theme-dark .service-area-three .service-item .accordion a{background-color:#252525;color:#fff}.theme-dark .service-area-three .service-item .accordion a::after{color:#fff}.theme-dark .service-area-three .service-item .accordion a span{color:#fff}.theme-dark .sorting-menu ul li{color:#fff;background-color:#0e0e0e}.theme-dark .sorting-menu ul li:hover,.theme-dark .sorting-menu ul li.active{color:#fff;background-color:#fdb819}.theme-dark .collection-item .collection-bottom ul li .form-control{background-color:transparent}.theme-dark .collection-area .more-collection a{color:#fff;border-color:#fff;-webkit-transition:.7s;transition:.7s}.theme-dark .collection-area .more-collection a:hover{color:#fdb819;border-color:#fdb819}.theme-dark .collection-area-two{background-color:#0f0f0f}.theme-dark .download-area .download-content ul li{background-color:#252525;color:#fff}.theme-dark .menu-area{background-color:#252525}.theme-dark .menu-item{background-color:#0e0e0e}.theme-dark .reservation-area{background-color:#0e0e0e}.theme-dark .reservation-area .reservation-item ul{background-color:#252525}.theme-dark .reservation-area .reservation-item ul li .form-control{background-color:#252525;color:#fff;border-color:#fff}.theme-dark .review-area-two{background-color:#000}.theme-dark .story-area .story-item h3{background-color:#252525}.theme-dark .blog-area .read-blog-btn{color:#fff;border-color:#fff;-webkit-transition:.7s;transition:.7s}.theme-dark .blog-area .read-blog-btn:hover{color:#fdb819;border-color:#fdb819}.theme-dark .service-details-area .service-details-item .service-details-more ul li a{color:#fff}.theme-dark .blog-details-tags ul li a{background-color:#252525;color:#fff;-webkit-transition:.7s;transition:.7s}.theme-dark .blog-details-tags ul li a:hover{background-color:#fdb819;color:#fff}.theme-dark .blog-details-nav ul li a{color:#fff;border-color:#fff}.theme-dark .subscribe-area{background-color:#0e0e0e}.theme-dark .subscribe-item .newsletter-form .form-control{color:#fff;background-color:#252525}.theme-dark .footer-item .footer-logo a .footer-logo1{display:none}.theme-dark .footer-item .footer-logo a .footer-logo2{display:inline-block}.theme-dark .footer-item .footer-logo .footer-subscriber-two .form-control{background-color:#252525;color:#fff}.theme-dark .footer-item .footer-service ul li{color:#fff}.theme-dark .footer-item .footer-service ul li a{color:#fff;-webkit-transition:.7s;transition:.7s}.theme-dark .footer-item .footer-service ul li a:hover{color:#fdb819}.theme-dark .contact-location-area{background-color:#252525}.theme-dark .contact-location-area .location-item{background-color:#0e0e0e}.theme-dark .contact-location-area .location-item ul li{color:#fff}.theme-dark .contact-location-area .location-item ul li a{color:#fff}.theme-dark .contact-location-area .location-item:hover,.theme-dark .contact-location-area .location-item.active{background-color:#784400}.theme-dark .contact-form-area .contact-item{background-color:#00000082}.theme-dark .contact-form-area .contact-item #contactForm .form-group .form-control{background-color:#0e0e0e;color:#fff}.theme-dark .contact-form-area .contact-item .contact-social span{color:#fff}.theme-dark .book-table-area .book-table-wrap{background-color:#252525}.theme-dark .book-table-area .book-table-wrap .form-group .form-control{background-color:#0e0e0e;color:#fff}.theme-dark .cart-wrap .table tr td{color:#fff}.theme-dark .cart-wrap .table tr td a{color:#fff}.theme-dark .cart-wrap .table tr td a:hover{color:#fdb819}.theme-dark .cart-wrap .shop-back a{color:#fff}.theme-dark .cart-wrap .shop-back a:hover{color:#fdb819}.theme-dark .cart-wrap .total-shopping h2{color:#fff;border-color:#fff}.theme-dark .cart-wrap .total-shopping h3{color:#fff}.theme-dark .cart-wrap .total-shopping a{color:#fff}.theme-dark .checkout-item .checkout-one label{color:#fff}.theme-dark .checkout-item .checkout-two .form-check span a{color:#fdb819;-webkit-transition:.7s;transition:.7s}.theme-dark .checkout-item .checkout-two .form-check span a:hover{color:#fff}.theme-dark .faq-area .accordion a{color:#fff;border-color:#fff}.theme-dark .faq-area .accordion a::after{color:#fff}.theme-dark .coming-item .coming-wrap .coming-inner{background-color:#252525}.theme-dark .coming-item .coming-wrap .coming-inner h3{color:#fff}.theme-dark .copyright-area,.theme-dark .footer-area-two{background-color:#000}
\ No newline at end of file
diff --git a/themes/30coffe/assets/fonts/ajax-loader.gif b/themes/30coffe/assets/fonts/ajax-loader.gif
new file mode 100644
index 00000000..d3cb0f1e
Binary files /dev/null and b/themes/30coffe/assets/fonts/ajax-loader.gif differ
diff --git a/themes/30coffe/assets/fonts/boxicons.eot b/themes/30coffe/assets/fonts/boxicons.eot
new file mode 100644
index 00000000..9b0bf769
Binary files /dev/null and b/themes/30coffe/assets/fonts/boxicons.eot differ
diff --git a/themes/30coffe/assets/fonts/boxicons.ttf b/themes/30coffe/assets/fonts/boxicons.ttf
new file mode 100644
index 00000000..0b52d5cb
Binary files /dev/null and b/themes/30coffe/assets/fonts/boxicons.ttf differ
diff --git a/themes/30coffe/assets/fonts/boxicons.woff b/themes/30coffe/assets/fonts/boxicons.woff
new file mode 100644
index 00000000..429c00df
Binary files /dev/null and b/themes/30coffe/assets/fonts/boxicons.woff differ
diff --git a/themes/30coffe/assets/fonts/boxicons.woff2 b/themes/30coffe/assets/fonts/boxicons.woff2
new file mode 100644
index 00000000..9179e64b
Binary files /dev/null and b/themes/30coffe/assets/fonts/boxicons.woff2 differ
diff --git a/themes/30coffe/assets/fonts/boxiconsd41d.svg b/themes/30coffe/assets/fonts/boxiconsd41d.svg
new file mode 100644
index 00000000..7732686b
--- /dev/null
+++ b/themes/30coffe/assets/fonts/boxiconsd41d.svg
@@ -0,0 +1,1626 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/themes/30coffe/assets/fonts/slick.eot b/themes/30coffe/assets/fonts/slick.eot
new file mode 100644
index 00000000..2cbab9ca
Binary files /dev/null and b/themes/30coffe/assets/fonts/slick.eot differ
diff --git a/themes/30coffe/assets/fonts/slick.svg b/themes/30coffe/assets/fonts/slick.svg
new file mode 100644
index 00000000..b36a66a6
--- /dev/null
+++ b/themes/30coffe/assets/fonts/slick.svg
@@ -0,0 +1,14 @@
+
+
+
+Generated by Fontastic.me
+
+
+
+
+
+
+
+
+
+
diff --git a/themes/30coffe/assets/fonts/slick.ttf b/themes/30coffe/assets/fonts/slick.ttf
new file mode 100644
index 00000000..9d03461b
Binary files /dev/null and b/themes/30coffe/assets/fonts/slick.ttf differ
diff --git a/themes/30coffe/assets/fonts/slick.woff b/themes/30coffe/assets/fonts/slick.woff
new file mode 100644
index 00000000..8ee99721
Binary files /dev/null and b/themes/30coffe/assets/fonts/slick.woff differ
diff --git a/themes/30coffe/assets/fonts/slickd41d.eot b/themes/30coffe/assets/fonts/slickd41d.eot
new file mode 100644
index 00000000..2cbab9ca
Binary files /dev/null and b/themes/30coffe/assets/fonts/slickd41d.eot differ
diff --git a/themes/30coffe/assets/img/1.png b/themes/30coffe/assets/img/1.png
new file mode 100644
index 00000000..91c77817
Binary files /dev/null and b/themes/30coffe/assets/img/1.png differ
diff --git a/themes/30coffe/assets/img/2.png b/themes/30coffe/assets/img/2.png
new file mode 100644
index 00000000..534c687a
Binary files /dev/null and b/themes/30coffe/assets/img/2.png differ
diff --git a/themes/30coffe/assets/img/3.png b/themes/30coffe/assets/img/3.png
new file mode 100644
index 00000000..307fa0e3
Binary files /dev/null and b/themes/30coffe/assets/img/3.png differ
diff --git a/themes/30coffe/assets/img/about/app.png b/themes/30coffe/assets/img/about/app.png
new file mode 100644
index 00000000..740286c2
Binary files /dev/null and b/themes/30coffe/assets/img/about/app.png differ
diff --git a/themes/30coffe/assets/img/about/download2.png b/themes/30coffe/assets/img/about/download2.png
new file mode 100644
index 00000000..6b16a551
Binary files /dev/null and b/themes/30coffe/assets/img/about/download2.png differ
diff --git a/themes/30coffe/assets/img/about/page-title.jpg b/themes/30coffe/assets/img/about/page-title.jpg
new file mode 100644
index 00000000..3c950bca
Binary files /dev/null and b/themes/30coffe/assets/img/about/page-title.jpg differ
diff --git a/themes/30coffe/assets/img/about/story1.jpg b/themes/30coffe/assets/img/about/story1.jpg
new file mode 100644
index 00000000..7dbb2143
Binary files /dev/null and b/themes/30coffe/assets/img/about/story1.jpg differ
diff --git a/themes/30coffe/assets/img/about/story2.jpg b/themes/30coffe/assets/img/about/story2.jpg
new file mode 100644
index 00000000..e2548d77
Binary files /dev/null and b/themes/30coffe/assets/img/about/story2.jpg differ
diff --git a/themes/30coffe/assets/img/about/story3.png b/themes/30coffe/assets/img/about/story3.png
new file mode 100644
index 00000000..274bf9b1
Binary files /dev/null and b/themes/30coffe/assets/img/about/story3.png differ
diff --git a/themes/30coffe/assets/img/blog-details/1.jpg b/themes/30coffe/assets/img/blog-details/1.jpg
new file mode 100644
index 00000000..f58a7fe9
Binary files /dev/null and b/themes/30coffe/assets/img/blog-details/1.jpg differ
diff --git a/themes/30coffe/assets/img/blog-details/2.jpg b/themes/30coffe/assets/img/blog-details/2.jpg
new file mode 100644
index 00000000..eb5cda1c
Binary files /dev/null and b/themes/30coffe/assets/img/blog-details/2.jpg differ
diff --git a/themes/30coffe/assets/img/contact-bg.jpg b/themes/30coffe/assets/img/contact-bg.jpg
new file mode 100644
index 00000000..4a2c5b0f
Binary files /dev/null and b/themes/30coffe/assets/img/contact-bg.jpg differ
diff --git a/themes/30coffe/assets/img/contact-form-bg.jpg b/themes/30coffe/assets/img/contact-form-bg.jpg
new file mode 100644
index 00000000..e3be79b2
Binary files /dev/null and b/themes/30coffe/assets/img/contact-form-bg.jpg differ
diff --git a/themes/30coffe/assets/img/contact-man.png b/themes/30coffe/assets/img/contact-man.png
new file mode 100644
index 00000000..8606d4a3
Binary files /dev/null and b/themes/30coffe/assets/img/contact-man.png differ
diff --git a/themes/30coffe/assets/img/favicon.png b/themes/30coffe/assets/img/favicon.png
new file mode 100644
index 00000000..f87f0138
Binary files /dev/null and b/themes/30coffe/assets/img/favicon.png differ
diff --git a/themes/30coffe/assets/img/home-one/banner/banner-shape.png b/themes/30coffe/assets/img/home-one/banner/banner-shape.png
new file mode 100644
index 00000000..f9115566
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/banner/banner-shape.png differ
diff --git a/themes/30coffe/assets/img/home-one/banner/banner-slider1.png b/themes/30coffe/assets/img/home-one/banner/banner-slider1.png
new file mode 100644
index 00000000..a05078e7
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/banner/banner-slider1.png differ
diff --git a/themes/30coffe/assets/img/home-one/banner/banner-slider2.png b/themes/30coffe/assets/img/home-one/banner/banner-slider2.png
new file mode 100644
index 00000000..2a510716
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/banner/banner-slider2.png differ
diff --git a/themes/30coffe/assets/img/home-one/banner/banner-slider3.png b/themes/30coffe/assets/img/home-one/banner/banner-slider3.png
new file mode 100644
index 00000000..bf509b13
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/banner/banner-slider3.png differ
diff --git a/themes/30coffe/assets/img/home-one/banner/shape1.png b/themes/30coffe/assets/img/home-one/banner/shape1.png
new file mode 100644
index 00000000..58acd104
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/banner/shape1.png differ
diff --git a/themes/30coffe/assets/img/home-one/banner/shape2.png b/themes/30coffe/assets/img/home-one/banner/shape2.png
new file mode 100644
index 00000000..94b35505
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/banner/shape2.png differ
diff --git a/themes/30coffe/assets/img/home-one/banner/shape3.png b/themes/30coffe/assets/img/home-one/banner/shape3.png
new file mode 100644
index 00000000..4fca02c8
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/banner/shape3.png differ
diff --git a/themes/30coffe/assets/img/home-one/blog1.jpg b/themes/30coffe/assets/img/home-one/blog1.jpg
new file mode 100644
index 00000000..4ee8f37b
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/blog1.jpg differ
diff --git a/themes/30coffe/assets/img/home-one/blog2.jpg b/themes/30coffe/assets/img/home-one/blog2.jpg
new file mode 100644
index 00000000..c2351ad5
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/blog2.jpg differ
diff --git a/themes/30coffe/assets/img/home-one/blog3.jpg b/themes/30coffe/assets/img/home-one/blog3.jpg
new file mode 100644
index 00000000..cd6cc67e
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/blog3.jpg differ
diff --git a/themes/30coffe/assets/img/home-one/blog4.jpg b/themes/30coffe/assets/img/home-one/blog4.jpg
new file mode 100644
index 00000000..8b213a38
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/blog4.jpg differ
diff --git a/themes/30coffe/assets/img/home-one/blog5.jpg b/themes/30coffe/assets/img/home-one/blog5.jpg
new file mode 100644
index 00000000..39479918
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/blog5.jpg differ
diff --git a/themes/30coffe/assets/img/home-one/blog6.jpg b/themes/30coffe/assets/img/home-one/blog6.jpg
new file mode 100644
index 00000000..0cd761fc
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/blog6.jpg differ
diff --git a/themes/30coffe/assets/img/home-one/chef/1.jpg b/themes/30coffe/assets/img/home-one/chef/1.jpg
new file mode 100644
index 00000000..106699a2
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/chef/1.jpg differ
diff --git a/themes/30coffe/assets/img/home-one/chef/2.jpg b/themes/30coffe/assets/img/home-one/chef/2.jpg
new file mode 100644
index 00000000..94103a3d
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/chef/2.jpg differ
diff --git a/themes/30coffe/assets/img/home-one/chef/3.jpg b/themes/30coffe/assets/img/home-one/chef/3.jpg
new file mode 100644
index 00000000..9f565cf8
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/chef/3.jpg differ
diff --git a/themes/30coffe/assets/img/home-one/chef/4.jpg b/themes/30coffe/assets/img/home-one/chef/4.jpg
new file mode 100644
index 00000000..181d2eb3
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/chef/4.jpg differ
diff --git a/themes/30coffe/assets/img/home-one/chef/5.jpg b/themes/30coffe/assets/img/home-one/chef/5.jpg
new file mode 100644
index 00000000..ed114548
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/chef/5.jpg differ
diff --git a/themes/30coffe/assets/img/home-one/chef/6.jpg b/themes/30coffe/assets/img/home-one/chef/6.jpg
new file mode 100644
index 00000000..9f6c480c
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/chef/6.jpg differ
diff --git a/themes/30coffe/assets/img/home-one/chef/7.jpg b/themes/30coffe/assets/img/home-one/chef/7.jpg
new file mode 100644
index 00000000..8e41843e
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/chef/7.jpg differ
diff --git a/themes/30coffe/assets/img/home-one/chef/8.jpg b/themes/30coffe/assets/img/home-one/chef/8.jpg
new file mode 100644
index 00000000..82d38bb4
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/chef/8.jpg differ
diff --git a/themes/30coffe/assets/img/home-one/collection/1.jpg b/themes/30coffe/assets/img/home-one/collection/1.jpg
new file mode 100644
index 00000000..3c1ff164
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/collection/1.jpg differ
diff --git a/themes/30coffe/assets/img/home-one/collection/2.jpg b/themes/30coffe/assets/img/home-one/collection/2.jpg
new file mode 100644
index 00000000..8122f4ae
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/collection/2.jpg differ
diff --git a/themes/30coffe/assets/img/home-one/collection/3.jpg b/themes/30coffe/assets/img/home-one/collection/3.jpg
new file mode 100644
index 00000000..eae54b63
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/collection/3.jpg differ
diff --git a/themes/30coffe/assets/img/home-one/collection/4.jpg b/themes/30coffe/assets/img/home-one/collection/4.jpg
new file mode 100644
index 00000000..bb600d60
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/collection/4.jpg differ
diff --git a/themes/30coffe/assets/img/home-one/collection/5.jpg b/themes/30coffe/assets/img/home-one/collection/5.jpg
new file mode 100644
index 00000000..081774da
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/collection/5.jpg differ
diff --git a/themes/30coffe/assets/img/home-one/collection/6.jpg b/themes/30coffe/assets/img/home-one/collection/6.jpg
new file mode 100644
index 00000000..5517f22e
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/collection/6.jpg differ
diff --git a/themes/30coffe/assets/img/home-one/collection/7.jpg b/themes/30coffe/assets/img/home-one/collection/7.jpg
new file mode 100644
index 00000000..4f458587
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/collection/7.jpg differ
diff --git a/themes/30coffe/assets/img/home-one/collection/8.jpg b/themes/30coffe/assets/img/home-one/collection/8.jpg
new file mode 100644
index 00000000..7b6575b2
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/collection/8.jpg differ
diff --git a/themes/30coffe/assets/img/home-one/feature1.jpg b/themes/30coffe/assets/img/home-one/feature1.jpg
new file mode 100644
index 00000000..c3d77cb9
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/feature1.jpg differ
diff --git a/themes/30coffe/assets/img/home-one/feature1.png b/themes/30coffe/assets/img/home-one/feature1.png
new file mode 100644
index 00000000..fe9a0a9b
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/feature1.png differ
diff --git a/themes/30coffe/assets/img/home-one/feature2.jpg b/themes/30coffe/assets/img/home-one/feature2.jpg
new file mode 100644
index 00000000..0ad20bf3
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/feature2.jpg differ
diff --git a/themes/30coffe/assets/img/home-one/feature2.png b/themes/30coffe/assets/img/home-one/feature2.png
new file mode 100644
index 00000000..d9f93db1
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/feature2.png differ
diff --git a/themes/30coffe/assets/img/home-one/feature3.jpg b/themes/30coffe/assets/img/home-one/feature3.jpg
new file mode 100644
index 00000000..2ac9713e
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/feature3.jpg differ
diff --git a/themes/30coffe/assets/img/home-one/feature3.png b/themes/30coffe/assets/img/home-one/feature3.png
new file mode 100644
index 00000000..6e258629
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/feature3.png differ
diff --git a/themes/30coffe/assets/img/home-one/feature4.jpg b/themes/30coffe/assets/img/home-one/feature4.jpg
new file mode 100644
index 00000000..af133e75
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/feature4.jpg differ
diff --git a/themes/30coffe/assets/img/home-one/feature5.jpg b/themes/30coffe/assets/img/home-one/feature5.jpg
new file mode 100644
index 00000000..cf9a77d6
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/feature5.jpg differ
diff --git a/themes/30coffe/assets/img/home-one/feature6.jpg b/themes/30coffe/assets/img/home-one/feature6.jpg
new file mode 100644
index 00000000..a9506a08
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/feature6.jpg differ
diff --git a/themes/30coffe/assets/img/home-one/menu1.png b/themes/30coffe/assets/img/home-one/menu1.png
new file mode 100644
index 00000000..844ae081
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/menu1.png differ
diff --git a/themes/30coffe/assets/img/home-one/menu2.png b/themes/30coffe/assets/img/home-one/menu2.png
new file mode 100644
index 00000000..94ae6a1c
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/menu2.png differ
diff --git a/themes/30coffe/assets/img/home-one/menu3.png b/themes/30coffe/assets/img/home-one/menu3.png
new file mode 100644
index 00000000..64ef7282
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/menu3.png differ
diff --git a/themes/30coffe/assets/img/home-one/reservation-shape.png b/themes/30coffe/assets/img/home-one/reservation-shape.png
new file mode 100644
index 00000000..11aa2f47
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/reservation-shape.png differ
diff --git a/themes/30coffe/assets/img/home-one/reservation.png b/themes/30coffe/assets/img/home-one/reservation.png
new file mode 100644
index 00000000..9eda434e
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/reservation.png differ
diff --git a/themes/30coffe/assets/img/home-one/restant.png b/themes/30coffe/assets/img/home-one/restant.png
new file mode 100644
index 00000000..2fe00828
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/restant.png differ
diff --git a/themes/30coffe/assets/img/home-one/restant2.png b/themes/30coffe/assets/img/home-one/restant2.png
new file mode 100644
index 00000000..cb6bac92
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/restant2.png differ
diff --git a/themes/30coffe/assets/img/home-one/restant3.png b/themes/30coffe/assets/img/home-one/restant3.png
new file mode 100644
index 00000000..30940178
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/restant3.png differ
diff --git a/themes/30coffe/assets/img/home-one/restant4.png b/themes/30coffe/assets/img/home-one/restant4.png
new file mode 100644
index 00000000..0a58d90a
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/restant4.png differ
diff --git a/themes/30coffe/assets/img/home-one/restant5.png b/themes/30coffe/assets/img/home-one/restant5.png
new file mode 100644
index 00000000..3d657a89
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/restant5.png differ
diff --git a/themes/30coffe/assets/img/home-one/review1.png b/themes/30coffe/assets/img/home-one/review1.png
new file mode 100644
index 00000000..fdd758cd
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/review1.png differ
diff --git a/themes/30coffe/assets/img/home-one/review2.png b/themes/30coffe/assets/img/home-one/review2.png
new file mode 100644
index 00000000..187d8ec8
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/review2.png differ
diff --git a/themes/30coffe/assets/img/home-one/review3.png b/themes/30coffe/assets/img/home-one/review3.png
new file mode 100644
index 00000000..a9325e00
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/review3.png differ
diff --git a/themes/30coffe/assets/img/home-one/review4.png b/themes/30coffe/assets/img/home-one/review4.png
new file mode 100644
index 00000000..8f53b63b
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/review4.png differ
diff --git a/themes/30coffe/assets/img/home-one/review5.png b/themes/30coffe/assets/img/home-one/review5.png
new file mode 100644
index 00000000..e54b55f6
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/review5.png differ
diff --git a/themes/30coffe/assets/img/home-one/review6.png b/themes/30coffe/assets/img/home-one/review6.png
new file mode 100644
index 00000000..4ea50626
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/review6.png differ
diff --git a/themes/30coffe/assets/img/home-one/review7.png b/themes/30coffe/assets/img/home-one/review7.png
new file mode 100644
index 00000000..ee064da9
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/review7.png differ
diff --git a/themes/30coffe/assets/img/home-one/review8.png b/themes/30coffe/assets/img/home-one/review8.png
new file mode 100644
index 00000000..a63c943f
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/review8.png differ
diff --git a/themes/30coffe/assets/img/home-one/service-shape.png b/themes/30coffe/assets/img/home-one/service-shape.png
new file mode 100644
index 00000000..795370d2
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/service-shape.png differ
diff --git a/themes/30coffe/assets/img/home-one/service-shape2.png b/themes/30coffe/assets/img/home-one/service-shape2.png
new file mode 100644
index 00000000..db364450
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/service-shape2.png differ
diff --git a/themes/30coffe/assets/img/home-one/service1.png b/themes/30coffe/assets/img/home-one/service1.png
new file mode 100644
index 00000000..f88c5740
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/service1.png differ
diff --git a/themes/30coffe/assets/img/home-one/service2.png b/themes/30coffe/assets/img/home-one/service2.png
new file mode 100644
index 00000000..c01f72cb
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/service2.png differ
diff --git a/themes/30coffe/assets/img/home-one/service3.png b/themes/30coffe/assets/img/home-one/service3.png
new file mode 100644
index 00000000..c5c1ec75
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/service3.png differ
diff --git a/themes/30coffe/assets/img/home-one/service4.png b/themes/30coffe/assets/img/home-one/service4.png
new file mode 100644
index 00000000..1ad24178
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/service4.png differ
diff --git a/themes/30coffe/assets/img/home-one/service5.png b/themes/30coffe/assets/img/home-one/service5.png
new file mode 100644
index 00000000..22c71223
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/service5.png differ
diff --git a/themes/30coffe/assets/img/home-one/service6.png b/themes/30coffe/assets/img/home-one/service6.png
new file mode 100644
index 00000000..c366c640
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/service6.png differ
diff --git a/themes/30coffe/assets/img/home-one/subscribe-main.png b/themes/30coffe/assets/img/home-one/subscribe-main.png
new file mode 100644
index 00000000..4fc61d61
Binary files /dev/null and b/themes/30coffe/assets/img/home-one/subscribe-main.png differ
diff --git a/themes/30coffe/assets/img/home-three/about1.jpg b/themes/30coffe/assets/img/home-three/about1.jpg
new file mode 100644
index 00000000..012284c1
Binary files /dev/null and b/themes/30coffe/assets/img/home-three/about1.jpg differ
diff --git a/themes/30coffe/assets/img/home-three/about2.png b/themes/30coffe/assets/img/home-three/about2.png
new file mode 100644
index 00000000..3211bbe7
Binary files /dev/null and b/themes/30coffe/assets/img/home-three/about2.png differ
diff --git a/themes/30coffe/assets/img/home-three/about3.png b/themes/30coffe/assets/img/home-three/about3.png
new file mode 100644
index 00000000..964b0f54
Binary files /dev/null and b/themes/30coffe/assets/img/home-three/about3.png differ
diff --git a/themes/30coffe/assets/img/home-three/banner-main.jpg b/themes/30coffe/assets/img/home-three/banner-main.jpg
new file mode 100644
index 00000000..4818bbda
Binary files /dev/null and b/themes/30coffe/assets/img/home-three/banner-main.jpg differ
diff --git a/themes/30coffe/assets/img/home-three/banner1.jpg b/themes/30coffe/assets/img/home-three/banner1.jpg
new file mode 100644
index 00000000..78f5025c
Binary files /dev/null and b/themes/30coffe/assets/img/home-three/banner1.jpg differ
diff --git a/themes/30coffe/assets/img/home-three/banner2.png b/themes/30coffe/assets/img/home-three/banner2.png
new file mode 100644
index 00000000..aab92b4a
Binary files /dev/null and b/themes/30coffe/assets/img/home-three/banner2.png differ
diff --git a/themes/30coffe/assets/img/home-three/banner3.png b/themes/30coffe/assets/img/home-three/banner3.png
new file mode 100644
index 00000000..8703a8ac
Binary files /dev/null and b/themes/30coffe/assets/img/home-three/banner3.png differ
diff --git a/themes/30coffe/assets/img/home-three/banner4.png b/themes/30coffe/assets/img/home-three/banner4.png
new file mode 100644
index 00000000..303f5a6e
Binary files /dev/null and b/themes/30coffe/assets/img/home-three/banner4.png differ
diff --git a/themes/30coffe/assets/img/home-three/service1.png b/themes/30coffe/assets/img/home-three/service1.png
new file mode 100644
index 00000000..77138602
Binary files /dev/null and b/themes/30coffe/assets/img/home-three/service1.png differ
diff --git a/themes/30coffe/assets/img/home-two/about1.png b/themes/30coffe/assets/img/home-two/about1.png
new file mode 100644
index 00000000..92b19680
Binary files /dev/null and b/themes/30coffe/assets/img/home-two/about1.png differ
diff --git a/themes/30coffe/assets/img/home-two/about2.png b/themes/30coffe/assets/img/home-two/about2.png
new file mode 100644
index 00000000..9dfbf8ca
Binary files /dev/null and b/themes/30coffe/assets/img/home-two/about2.png differ
diff --git a/themes/30coffe/assets/img/home-two/about3.png b/themes/30coffe/assets/img/home-two/about3.png
new file mode 100644
index 00000000..d6395500
Binary files /dev/null and b/themes/30coffe/assets/img/home-two/about3.png differ
diff --git a/themes/30coffe/assets/img/home-two/about4.png b/themes/30coffe/assets/img/home-two/about4.png
new file mode 100644
index 00000000..7fade5c7
Binary files /dev/null and b/themes/30coffe/assets/img/home-two/about4.png differ
diff --git a/themes/30coffe/assets/img/home-two/about5.png b/themes/30coffe/assets/img/home-two/about5.png
new file mode 100644
index 00000000..3748fa96
Binary files /dev/null and b/themes/30coffe/assets/img/home-two/about5.png differ
diff --git a/themes/30coffe/assets/img/home-two/about6.png b/themes/30coffe/assets/img/home-two/about6.png
new file mode 100644
index 00000000..3323d6e1
Binary files /dev/null and b/themes/30coffe/assets/img/home-two/about6.png differ
diff --git a/themes/30coffe/assets/img/home-two/about7.png b/themes/30coffe/assets/img/home-two/about7.png
new file mode 100644
index 00000000..fc4788cb
Binary files /dev/null and b/themes/30coffe/assets/img/home-two/about7.png differ
diff --git a/themes/30coffe/assets/img/home-two/app-store.png b/themes/30coffe/assets/img/home-two/app-store.png
new file mode 100644
index 00000000..d13e7898
Binary files /dev/null and b/themes/30coffe/assets/img/home-two/app-store.png differ
diff --git a/themes/30coffe/assets/img/home-two/banner/1.jpg b/themes/30coffe/assets/img/home-two/banner/1.jpg
new file mode 100644
index 00000000..9c32b90f
Binary files /dev/null and b/themes/30coffe/assets/img/home-two/banner/1.jpg differ
diff --git a/themes/30coffe/assets/img/home-two/banner/1.png b/themes/30coffe/assets/img/home-two/banner/1.png
new file mode 100644
index 00000000..9a3416d5
Binary files /dev/null and b/themes/30coffe/assets/img/home-two/banner/1.png differ
diff --git a/themes/30coffe/assets/img/home-two/banner/2.png b/themes/30coffe/assets/img/home-two/banner/2.png
new file mode 100644
index 00000000..540d0705
Binary files /dev/null and b/themes/30coffe/assets/img/home-two/banner/2.png differ
diff --git a/themes/30coffe/assets/img/home-two/banner/3.png b/themes/30coffe/assets/img/home-two/banner/3.png
new file mode 100644
index 00000000..e803b909
Binary files /dev/null and b/themes/30coffe/assets/img/home-two/banner/3.png differ
diff --git a/themes/30coffe/assets/img/home-two/banner/banner-main.png b/themes/30coffe/assets/img/home-two/banner/banner-main.png
new file mode 100644
index 00000000..96a31b27
Binary files /dev/null and b/themes/30coffe/assets/img/home-two/banner/banner-main.png differ
diff --git a/themes/30coffe/assets/img/home-two/banner/food1.png b/themes/30coffe/assets/img/home-two/banner/food1.png
new file mode 100644
index 00000000..e25a53eb
Binary files /dev/null and b/themes/30coffe/assets/img/home-two/banner/food1.png differ
diff --git a/themes/30coffe/assets/img/home-two/banner/food2.png b/themes/30coffe/assets/img/home-two/banner/food2.png
new file mode 100644
index 00000000..065efe30
Binary files /dev/null and b/themes/30coffe/assets/img/home-two/banner/food2.png differ
diff --git a/themes/30coffe/assets/img/home-two/banner/food3.png b/themes/30coffe/assets/img/home-two/banner/food3.png
new file mode 100644
index 00000000..8c5c407b
Binary files /dev/null and b/themes/30coffe/assets/img/home-two/banner/food3.png differ
diff --git a/themes/30coffe/assets/img/home-two/banner/food4.png b/themes/30coffe/assets/img/home-two/banner/food4.png
new file mode 100644
index 00000000..490653d8
Binary files /dev/null and b/themes/30coffe/assets/img/home-two/banner/food4.png differ
diff --git a/themes/30coffe/assets/img/home-two/banner/food5.png b/themes/30coffe/assets/img/home-two/banner/food5.png
new file mode 100644
index 00000000..1b7fb963
Binary files /dev/null and b/themes/30coffe/assets/img/home-two/banner/food5.png differ
diff --git a/themes/30coffe/assets/img/home-two/banner/food6.png b/themes/30coffe/assets/img/home-two/banner/food6.png
new file mode 100644
index 00000000..f1438ee5
Binary files /dev/null and b/themes/30coffe/assets/img/home-two/banner/food6.png differ
diff --git a/themes/30coffe/assets/img/home-two/download1.png b/themes/30coffe/assets/img/home-two/download1.png
new file mode 100644
index 00000000..e0f63635
Binary files /dev/null and b/themes/30coffe/assets/img/home-two/download1.png differ
diff --git a/themes/30coffe/assets/img/home-two/google-store.png b/themes/30coffe/assets/img/home-two/google-store.png
new file mode 100644
index 00000000..62dc2d32
Binary files /dev/null and b/themes/30coffe/assets/img/home-two/google-store.png differ
diff --git a/themes/30coffe/assets/img/home-two/join1.png b/themes/30coffe/assets/img/home-two/join1.png
new file mode 100644
index 00000000..e43c1396
Binary files /dev/null and b/themes/30coffe/assets/img/home-two/join1.png differ
diff --git a/themes/30coffe/assets/img/home-two/review1.jpg b/themes/30coffe/assets/img/home-two/review1.jpg
new file mode 100644
index 00000000..eba90b12
Binary files /dev/null and b/themes/30coffe/assets/img/home-two/review1.jpg differ
diff --git a/themes/30coffe/assets/img/home-two/review2.png b/themes/30coffe/assets/img/home-two/review2.png
new file mode 100644
index 00000000..9fbc65ac
Binary files /dev/null and b/themes/30coffe/assets/img/home-two/review2.png differ
diff --git a/themes/30coffe/assets/img/logo-two.png b/themes/30coffe/assets/img/logo-two.png
new file mode 100644
index 00000000..a95b7570
Binary files /dev/null and b/themes/30coffe/assets/img/logo-two.png differ
diff --git a/themes/30coffe/assets/img/logo.png b/themes/30coffe/assets/img/logo.png
new file mode 100644
index 00000000..ad24dc12
Binary files /dev/null and b/themes/30coffe/assets/img/logo.png differ
diff --git a/themes/30coffe/assets/img/night.png b/themes/30coffe/assets/img/night.png
new file mode 100644
index 00000000..dc378383
Binary files /dev/null and b/themes/30coffe/assets/img/night.png differ
diff --git a/themes/30coffe/assets/img/service-details/2.jpg b/themes/30coffe/assets/img/service-details/2.jpg
new file mode 100644
index 00000000..80bcd483
Binary files /dev/null and b/themes/30coffe/assets/img/service-details/2.jpg differ
diff --git a/themes/30coffe/assets/img/service-details/food1.png b/themes/30coffe/assets/img/service-details/food1.png
new file mode 100644
index 00000000..e25a53eb
Binary files /dev/null and b/themes/30coffe/assets/img/service-details/food1.png differ
diff --git a/themes/30coffe/assets/img/service-details/food2.png b/themes/30coffe/assets/img/service-details/food2.png
new file mode 100644
index 00000000..d6c535f5
Binary files /dev/null and b/themes/30coffe/assets/img/service-details/food2.png differ
diff --git a/themes/30coffe/assets/img/service-details/food3.png b/themes/30coffe/assets/img/service-details/food3.png
new file mode 100644
index 00000000..f1438ee5
Binary files /dev/null and b/themes/30coffe/assets/img/service-details/food3.png differ
diff --git a/themes/30coffe/assets/img/service-details/food4.png b/themes/30coffe/assets/img/service-details/food4.png
new file mode 100644
index 00000000..1b7fb963
Binary files /dev/null and b/themes/30coffe/assets/img/service-details/food4.png differ
diff --git a/themes/30coffe/assets/img/service-details/order.png b/themes/30coffe/assets/img/service-details/order.png
new file mode 100644
index 00000000..b5ebaa59
Binary files /dev/null and b/themes/30coffe/assets/img/service-details/order.png differ
diff --git a/themes/30coffe/assets/img/slider/1.jpg b/themes/30coffe/assets/img/slider/1.jpg
new file mode 100644
index 00000000..2d73b2b3
Binary files /dev/null and b/themes/30coffe/assets/img/slider/1.jpg differ
diff --git a/themes/30coffe/assets/img/slider/2.jpg b/themes/30coffe/assets/img/slider/2.jpg
new file mode 100644
index 00000000..bea479b1
Binary files /dev/null and b/themes/30coffe/assets/img/slider/2.jpg differ
diff --git a/themes/30coffe/assets/img/slider/3.jpg b/themes/30coffe/assets/img/slider/3.jpg
new file mode 100644
index 00000000..6ee96280
Binary files /dev/null and b/themes/30coffe/assets/img/slider/3.jpg differ
diff --git a/themes/30coffe/assets/img/slider/4.jpg b/themes/30coffe/assets/img/slider/4.jpg
new file mode 100644
index 00000000..21c917e1
Binary files /dev/null and b/themes/30coffe/assets/img/slider/4.jpg differ
diff --git a/themes/30coffe/assets/img/sunny.png b/themes/30coffe/assets/img/sunny.png
new file mode 100644
index 00000000..89eabae6
Binary files /dev/null and b/themes/30coffe/assets/img/sunny.png differ
diff --git a/themes/30coffe/assets/js/bootstrap.bundle.min.js b/themes/30coffe/assets/js/bootstrap.bundle.min.js
new file mode 100644
index 00000000..dbfe7129
--- /dev/null
+++ b/themes/30coffe/assets/js/bootstrap.bundle.min.js
@@ -0,0 +1,6 @@
+/*!
+ * Bootstrap v5.1.3 (https://getbootstrap.com/)
+ * Copyright 2011-2021 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
+ */
+!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap=e()}(this,(function(){"use strict";const t="transitionend",e=t=>{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let i=t.getAttribute("href");if(!i||!i.includes("#")&&!i.startsWith("."))return null;i.includes("#")&&!i.startsWith("#")&&(i=`#${i.split("#")[1]}`),e=i&&"#"!==i?i.trim():null}return e},i=t=>{const i=e(t);return i&&document.querySelector(i)?i:null},n=t=>{const i=e(t);return i?document.querySelector(i):null},s=e=>{e.dispatchEvent(new Event(t))},o=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),r=t=>o(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?document.querySelector(t):null,a=(t,e,i)=>{Object.keys(i).forEach((n=>{const s=i[n],r=e[n],a=r&&o(r)?"element":null==(l=r)?`${l}`:{}.toString.call(l).match(/\s([a-z]+)/i)[1].toLowerCase();var l;if(!new RegExp(s).test(a))throw new TypeError(`${t.toUpperCase()}: Option "${n}" provided type "${a}" but expected type "${s}".`)}))},l=t=>!(!o(t)||0===t.getClientRects().length)&&"visible"===getComputedStyle(t).getPropertyValue("visibility"),c=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),h=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?h(t.parentNode):null},d=()=>{},u=t=>{t.offsetHeight},f=()=>{const{jQuery:t}=window;return t&&!document.body.hasAttribute("data-bs-no-jquery")?t:null},p=[],m=()=>"rtl"===document.documentElement.dir,g=t=>{var e;e=()=>{const e=f();if(e){const i=t.NAME,n=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=n,t.jQueryInterface)}},"loading"===document.readyState?(p.length||document.addEventListener("DOMContentLoaded",(()=>{p.forEach((t=>t()))})),p.push(e)):e()},_=t=>{"function"==typeof t&&t()},b=(e,i,n=!0)=>{if(!n)return void _(e);const o=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const n=Number.parseFloat(e),s=Number.parseFloat(i);return n||s?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(i)+5;let r=!1;const a=({target:n})=>{n===i&&(r=!0,i.removeEventListener(t,a),_(e))};i.addEventListener(t,a),setTimeout((()=>{r||s(i)}),o)},v=(t,e,i,n)=>{let s=t.indexOf(e);if(-1===s)return t[!i&&n?t.length-1:0];const o=t.length;return s+=i?1:-1,n&&(s=(s+o)%o),t[Math.max(0,Math.min(s,o-1))]},y=/[^.]*(?=\..*)\.|.*/,w=/\..*/,E=/::\d+$/,A={};let T=1;const O={mouseenter:"mouseover",mouseleave:"mouseout"},C=/^(mouseenter|mouseleave)/i,k=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function L(t,e){return e&&`${e}::${T++}`||t.uidEvent||T++}function x(t){const e=L(t);return t.uidEvent=e,A[e]=A[e]||{},A[e]}function D(t,e,i=null){const n=Object.keys(t);for(let s=0,o=n.length;sfunction(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};n?n=t(n):i=t(i)}const[o,r,a]=S(e,i,n),l=x(t),c=l[a]||(l[a]={}),h=D(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&s);const d=L(r,e.replace(y,"")),u=o?function(t,e,i){return function n(s){const o=t.querySelectorAll(e);for(let{target:r}=s;r&&r!==this;r=r.parentNode)for(let a=o.length;a--;)if(o[a]===r)return s.delegateTarget=r,n.oneOff&&j.off(t,s.type,e,i),i.apply(r,[s]);return null}}(t,i,n):function(t,e){return function i(n){return n.delegateTarget=t,i.oneOff&&j.off(t,n.type,e),e.apply(t,[n])}}(t,i);u.delegationSelector=o?i:null,u.originalHandler=r,u.oneOff=s,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function I(t,e,i,n,s){const o=D(e[i],n,s);o&&(t.removeEventListener(i,o,Boolean(s)),delete e[i][o.uidEvent])}function P(t){return t=t.replace(w,""),O[t]||t}const j={on(t,e,i,n){N(t,e,i,n,!1)},one(t,e,i,n){N(t,e,i,n,!0)},off(t,e,i,n){if("string"!=typeof e||!t)return;const[s,o,r]=S(e,i,n),a=r!==e,l=x(t),c=e.startsWith(".");if(void 0!==o){if(!l||!l[r])return;return void I(t,l,r,o,s?i:null)}c&&Object.keys(l).forEach((i=>{!function(t,e,i,n){const s=e[i]||{};Object.keys(s).forEach((o=>{if(o.includes(n)){const n=s[o];I(t,e,i,n.originalHandler,n.delegationSelector)}}))}(t,l,i,e.slice(1))}));const h=l[r]||{};Object.keys(h).forEach((i=>{const n=i.replace(E,"");if(!a||e.includes(n)){const e=h[i];I(t,l,r,e.originalHandler,e.delegationSelector)}}))},trigger(t,e,i){if("string"!=typeof e||!t)return null;const n=f(),s=P(e),o=e!==s,r=k.has(s);let a,l=!0,c=!0,h=!1,d=null;return o&&n&&(a=n.Event(e,i),n(t).trigger(a),l=!a.isPropagationStopped(),c=!a.isImmediatePropagationStopped(),h=a.isDefaultPrevented()),r?(d=document.createEvent("HTMLEvents"),d.initEvent(s,l,!0)):d=new CustomEvent(e,{bubbles:l,cancelable:!0}),void 0!==i&&Object.keys(i).forEach((t=>{Object.defineProperty(d,t,{get:()=>i[t]})})),h&&d.preventDefault(),c&&t.dispatchEvent(d),d.defaultPrevented&&void 0!==a&&a.preventDefault(),d}},M=new Map,H={set(t,e,i){M.has(t)||M.set(t,new Map);const n=M.get(t);n.has(e)||0===n.size?n.set(e,i):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(n.keys())[0]}.`)},get:(t,e)=>M.has(t)&&M.get(t).get(e)||null,remove(t,e){if(!M.has(t))return;const i=M.get(t);i.delete(e),0===i.size&&M.delete(t)}};class B{constructor(t){(t=r(t))&&(this._element=t,H.set(this._element,this.constructor.DATA_KEY,this))}dispose(){H.remove(this._element,this.constructor.DATA_KEY),j.off(this._element,this.constructor.EVENT_KEY),Object.getOwnPropertyNames(this).forEach((t=>{this[t]=null}))}_queueCallback(t,e,i=!0){b(t,e,i)}static getInstance(t){return H.get(r(t),this.DATA_KEY)}static getOrCreateInstance(t,e={}){return this.getInstance(t)||new this(t,"object"==typeof e?e:null)}static get VERSION(){return"5.1.3"}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}static get DATA_KEY(){return`bs.${this.NAME}`}static get EVENT_KEY(){return`.${this.DATA_KEY}`}}const R=(t,e="hide")=>{const i=`click.dismiss${t.EVENT_KEY}`,s=t.NAME;j.on(document,i,`[data-bs-dismiss="${s}"]`,(function(i){if(["A","AREA"].includes(this.tagName)&&i.preventDefault(),c(this))return;const o=n(this)||this.closest(`.${s}`);t.getOrCreateInstance(o)[e]()}))};class W extends B{static get NAME(){return"alert"}close(){if(j.trigger(this._element,"close.bs.alert").defaultPrevented)return;this._element.classList.remove("show");const t=this._element.classList.contains("fade");this._queueCallback((()=>this._destroyElement()),this._element,t)}_destroyElement(){this._element.remove(),j.trigger(this._element,"closed.bs.alert"),this.dispose()}static jQueryInterface(t){return this.each((function(){const e=W.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}R(W,"close"),g(W);const $='[data-bs-toggle="button"]';class z extends B{static get NAME(){return"button"}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle("active"))}static jQueryInterface(t){return this.each((function(){const e=z.getOrCreateInstance(this);"toggle"===t&&e[t]()}))}}function q(t){return"true"===t||"false"!==t&&(t===Number(t).toString()?Number(t):""===t||"null"===t?null:t)}function F(t){return t.replace(/[A-Z]/g,(t=>`-${t.toLowerCase()}`))}j.on(document,"click.bs.button.data-api",$,(t=>{t.preventDefault();const e=t.target.closest($);z.getOrCreateInstance(e).toggle()})),g(z);const U={setDataAttribute(t,e,i){t.setAttribute(`data-bs-${F(e)}`,i)},removeDataAttribute(t,e){t.removeAttribute(`data-bs-${F(e)}`)},getDataAttributes(t){if(!t)return{};const e={};return Object.keys(t.dataset).filter((t=>t.startsWith("bs"))).forEach((i=>{let n=i.replace(/^bs/,"");n=n.charAt(0).toLowerCase()+n.slice(1,n.length),e[n]=q(t.dataset[i])})),e},getDataAttribute:(t,e)=>q(t.getAttribute(`data-bs-${F(e)}`)),offset(t){const e=t.getBoundingClientRect();return{top:e.top+window.pageYOffset,left:e.left+window.pageXOffset}},position:t=>({top:t.offsetTop,left:t.offsetLeft})},V={find:(t,e=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(e,t)),findOne:(t,e=document.documentElement)=>Element.prototype.querySelector.call(e,t),children:(t,e)=>[].concat(...t.children).filter((t=>t.matches(e))),parents(t,e){const i=[];let n=t.parentNode;for(;n&&n.nodeType===Node.ELEMENT_NODE&&3!==n.nodeType;)n.matches(e)&&i.push(n),n=n.parentNode;return i},prev(t,e){let i=t.previousElementSibling;for(;i;){if(i.matches(e))return[i];i=i.previousElementSibling}return[]},next(t,e){let i=t.nextElementSibling;for(;i;){if(i.matches(e))return[i];i=i.nextElementSibling}return[]},focusableChildren(t){const e=["a","button","input","textarea","select","details","[tabindex]",'[contenteditable="true"]'].map((t=>`${t}:not([tabindex^="-"])`)).join(", ");return this.find(e,t).filter((t=>!c(t)&&l(t)))}},K="carousel",X={interval:5e3,keyboard:!0,slide:!1,pause:"hover",wrap:!0,touch:!0},Y={interval:"(number|boolean)",keyboard:"boolean",slide:"(boolean|string)",pause:"(string|boolean)",wrap:"boolean",touch:"boolean"},Q="next",G="prev",Z="left",J="right",tt={ArrowLeft:J,ArrowRight:Z},et="slid.bs.carousel",it="active",nt=".active.carousel-item";class st extends B{constructor(t,e){super(t),this._items=null,this._interval=null,this._activeElement=null,this._isPaused=!1,this._isSliding=!1,this.touchTimeout=null,this.touchStartX=0,this.touchDeltaX=0,this._config=this._getConfig(e),this._indicatorsElement=V.findOne(".carousel-indicators",this._element),this._touchSupported="ontouchstart"in document.documentElement||navigator.maxTouchPoints>0,this._pointerEvent=Boolean(window.PointerEvent),this._addEventListeners()}static get Default(){return X}static get NAME(){return K}next(){this._slide(Q)}nextWhenVisible(){!document.hidden&&l(this._element)&&this.next()}prev(){this._slide(G)}pause(t){t||(this._isPaused=!0),V.findOne(".carousel-item-next, .carousel-item-prev",this._element)&&(s(this._element),this.cycle(!0)),clearInterval(this._interval),this._interval=null}cycle(t){t||(this._isPaused=!1),this._interval&&(clearInterval(this._interval),this._interval=null),this._config&&this._config.interval&&!this._isPaused&&(this._updateInterval(),this._interval=setInterval((document.visibilityState?this.nextWhenVisible:this.next).bind(this),this._config.interval))}to(t){this._activeElement=V.findOne(nt,this._element);const e=this._getItemIndex(this._activeElement);if(t>this._items.length-1||t<0)return;if(this._isSliding)return void j.one(this._element,et,(()=>this.to(t)));if(e===t)return this.pause(),void this.cycle();const i=t>e?Q:G;this._slide(i,this._items[t])}_getConfig(t){return t={...X,...U.getDataAttributes(this._element),..."object"==typeof t?t:{}},a(K,t,Y),t}_handleSwipe(){const t=Math.abs(this.touchDeltaX);if(t<=40)return;const e=t/this.touchDeltaX;this.touchDeltaX=0,e&&this._slide(e>0?J:Z)}_addEventListeners(){this._config.keyboard&&j.on(this._element,"keydown.bs.carousel",(t=>this._keydown(t))),"hover"===this._config.pause&&(j.on(this._element,"mouseenter.bs.carousel",(t=>this.pause(t))),j.on(this._element,"mouseleave.bs.carousel",(t=>this.cycle(t)))),this._config.touch&&this._touchSupported&&this._addTouchEventListeners()}_addTouchEventListeners(){const t=t=>this._pointerEvent&&("pen"===t.pointerType||"touch"===t.pointerType),e=e=>{t(e)?this.touchStartX=e.clientX:this._pointerEvent||(this.touchStartX=e.touches[0].clientX)},i=t=>{this.touchDeltaX=t.touches&&t.touches.length>1?0:t.touches[0].clientX-this.touchStartX},n=e=>{t(e)&&(this.touchDeltaX=e.clientX-this.touchStartX),this._handleSwipe(),"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout((t=>this.cycle(t)),500+this._config.interval))};V.find(".carousel-item img",this._element).forEach((t=>{j.on(t,"dragstart.bs.carousel",(t=>t.preventDefault()))})),this._pointerEvent?(j.on(this._element,"pointerdown.bs.carousel",(t=>e(t))),j.on(this._element,"pointerup.bs.carousel",(t=>n(t))),this._element.classList.add("pointer-event")):(j.on(this._element,"touchstart.bs.carousel",(t=>e(t))),j.on(this._element,"touchmove.bs.carousel",(t=>i(t))),j.on(this._element,"touchend.bs.carousel",(t=>n(t))))}_keydown(t){if(/input|textarea/i.test(t.target.tagName))return;const e=tt[t.key];e&&(t.preventDefault(),this._slide(e))}_getItemIndex(t){return this._items=t&&t.parentNode?V.find(".carousel-item",t.parentNode):[],this._items.indexOf(t)}_getItemByOrder(t,e){const i=t===Q;return v(this._items,e,i,this._config.wrap)}_triggerSlideEvent(t,e){const i=this._getItemIndex(t),n=this._getItemIndex(V.findOne(nt,this._element));return j.trigger(this._element,"slide.bs.carousel",{relatedTarget:t,direction:e,from:n,to:i})}_setActiveIndicatorElement(t){if(this._indicatorsElement){const e=V.findOne(".active",this._indicatorsElement);e.classList.remove(it),e.removeAttribute("aria-current");const i=V.find("[data-bs-target]",this._indicatorsElement);for(let e=0;e{j.trigger(this._element,et,{relatedTarget:o,direction:d,from:s,to:r})};if(this._element.classList.contains("slide")){o.classList.add(h),u(o),n.classList.add(c),o.classList.add(c);const t=()=>{o.classList.remove(c,h),o.classList.add(it),n.classList.remove(it,h,c),this._isSliding=!1,setTimeout(f,0)};this._queueCallback(t,n,!0)}else n.classList.remove(it),o.classList.add(it),this._isSliding=!1,f();a&&this.cycle()}_directionToOrder(t){return[J,Z].includes(t)?m()?t===Z?G:Q:t===Z?Q:G:t}_orderToDirection(t){return[Q,G].includes(t)?m()?t===G?Z:J:t===G?J:Z:t}static carouselInterface(t,e){const i=st.getOrCreateInstance(t,e);let{_config:n}=i;"object"==typeof e&&(n={...n,...e});const s="string"==typeof e?e:n.slide;if("number"==typeof e)i.to(e);else if("string"==typeof s){if(void 0===i[s])throw new TypeError(`No method named "${s}"`);i[s]()}else n.interval&&n.ride&&(i.pause(),i.cycle())}static jQueryInterface(t){return this.each((function(){st.carouselInterface(this,t)}))}static dataApiClickHandler(t){const e=n(this);if(!e||!e.classList.contains("carousel"))return;const i={...U.getDataAttributes(e),...U.getDataAttributes(this)},s=this.getAttribute("data-bs-slide-to");s&&(i.interval=!1),st.carouselInterface(e,i),s&&st.getInstance(e).to(s),t.preventDefault()}}j.on(document,"click.bs.carousel.data-api","[data-bs-slide], [data-bs-slide-to]",st.dataApiClickHandler),j.on(window,"load.bs.carousel.data-api",(()=>{const t=V.find('[data-bs-ride="carousel"]');for(let e=0,i=t.length;et===this._element));null!==s&&o.length&&(this._selector=s,this._triggerArray.push(e))}this._initializeChildren(),this._config.parent||this._addAriaAndCollapsedClass(this._triggerArray,this._isShown()),this._config.toggle&&this.toggle()}static get Default(){return rt}static get NAME(){return ot}toggle(){this._isShown()?this.hide():this.show()}show(){if(this._isTransitioning||this._isShown())return;let t,e=[];if(this._config.parent){const t=V.find(ut,this._config.parent);e=V.find(".collapse.show, .collapse.collapsing",this._config.parent).filter((e=>!t.includes(e)))}const i=V.findOne(this._selector);if(e.length){const n=e.find((t=>i!==t));if(t=n?pt.getInstance(n):null,t&&t._isTransitioning)return}if(j.trigger(this._element,"show.bs.collapse").defaultPrevented)return;e.forEach((e=>{i!==e&&pt.getOrCreateInstance(e,{toggle:!1}).hide(),t||H.set(e,"bs.collapse",null)}));const n=this._getDimension();this._element.classList.remove(ct),this._element.classList.add(ht),this._element.style[n]=0,this._addAriaAndCollapsedClass(this._triggerArray,!0),this._isTransitioning=!0;const s=`scroll${n[0].toUpperCase()+n.slice(1)}`;this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(ht),this._element.classList.add(ct,lt),this._element.style[n]="",j.trigger(this._element,"shown.bs.collapse")}),this._element,!0),this._element.style[n]=`${this._element[s]}px`}hide(){if(this._isTransitioning||!this._isShown())return;if(j.trigger(this._element,"hide.bs.collapse").defaultPrevented)return;const t=this._getDimension();this._element.style[t]=`${this._element.getBoundingClientRect()[t]}px`,u(this._element),this._element.classList.add(ht),this._element.classList.remove(ct,lt);const e=this._triggerArray.length;for(let t=0;t{this._isTransitioning=!1,this._element.classList.remove(ht),this._element.classList.add(ct),j.trigger(this._element,"hidden.bs.collapse")}),this._element,!0)}_isShown(t=this._element){return t.classList.contains(lt)}_getConfig(t){return(t={...rt,...U.getDataAttributes(this._element),...t}).toggle=Boolean(t.toggle),t.parent=r(t.parent),a(ot,t,at),t}_getDimension(){return this._element.classList.contains("collapse-horizontal")?"width":"height"}_initializeChildren(){if(!this._config.parent)return;const t=V.find(ut,this._config.parent);V.find(ft,this._config.parent).filter((e=>!t.includes(e))).forEach((t=>{const e=n(t);e&&this._addAriaAndCollapsedClass([t],this._isShown(e))}))}_addAriaAndCollapsedClass(t,e){t.length&&t.forEach((t=>{e?t.classList.remove(dt):t.classList.add(dt),t.setAttribute("aria-expanded",e)}))}static jQueryInterface(t){return this.each((function(){const e={};"string"==typeof t&&/show|hide/.test(t)&&(e.toggle=!1);const i=pt.getOrCreateInstance(this,e);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t]()}}))}}j.on(document,"click.bs.collapse.data-api",ft,(function(t){("A"===t.target.tagName||t.delegateTarget&&"A"===t.delegateTarget.tagName)&&t.preventDefault();const e=i(this);V.find(e).forEach((t=>{pt.getOrCreateInstance(t,{toggle:!1}).toggle()}))})),g(pt);var mt="top",gt="bottom",_t="right",bt="left",vt="auto",yt=[mt,gt,_t,bt],wt="start",Et="end",At="clippingParents",Tt="viewport",Ot="popper",Ct="reference",kt=yt.reduce((function(t,e){return t.concat([e+"-"+wt,e+"-"+Et])}),[]),Lt=[].concat(yt,[vt]).reduce((function(t,e){return t.concat([e,e+"-"+wt,e+"-"+Et])}),[]),xt="beforeRead",Dt="read",St="afterRead",Nt="beforeMain",It="main",Pt="afterMain",jt="beforeWrite",Mt="write",Ht="afterWrite",Bt=[xt,Dt,St,Nt,It,Pt,jt,Mt,Ht];function Rt(t){return t?(t.nodeName||"").toLowerCase():null}function Wt(t){if(null==t)return window;if("[object Window]"!==t.toString()){var e=t.ownerDocument;return e&&e.defaultView||window}return t}function $t(t){return t instanceof Wt(t).Element||t instanceof Element}function zt(t){return t instanceof Wt(t).HTMLElement||t instanceof HTMLElement}function qt(t){return"undefined"!=typeof ShadowRoot&&(t instanceof Wt(t).ShadowRoot||t instanceof ShadowRoot)}const Ft={name:"applyStyles",enabled:!0,phase:"write",fn:function(t){var e=t.state;Object.keys(e.elements).forEach((function(t){var i=e.styles[t]||{},n=e.attributes[t]||{},s=e.elements[t];zt(s)&&Rt(s)&&(Object.assign(s.style,i),Object.keys(n).forEach((function(t){var e=n[t];!1===e?s.removeAttribute(t):s.setAttribute(t,!0===e?"":e)})))}))},effect:function(t){var e=t.state,i={popper:{position:e.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(e.elements.popper.style,i.popper),e.styles=i,e.elements.arrow&&Object.assign(e.elements.arrow.style,i.arrow),function(){Object.keys(e.elements).forEach((function(t){var n=e.elements[t],s=e.attributes[t]||{},o=Object.keys(e.styles.hasOwnProperty(t)?e.styles[t]:i[t]).reduce((function(t,e){return t[e]="",t}),{});zt(n)&&Rt(n)&&(Object.assign(n.style,o),Object.keys(s).forEach((function(t){n.removeAttribute(t)})))}))}},requires:["computeStyles"]};function Ut(t){return t.split("-")[0]}function Vt(t,e){var i=t.getBoundingClientRect();return{width:i.width/1,height:i.height/1,top:i.top/1,right:i.right/1,bottom:i.bottom/1,left:i.left/1,x:i.left/1,y:i.top/1}}function Kt(t){var e=Vt(t),i=t.offsetWidth,n=t.offsetHeight;return Math.abs(e.width-i)<=1&&(i=e.width),Math.abs(e.height-n)<=1&&(n=e.height),{x:t.offsetLeft,y:t.offsetTop,width:i,height:n}}function Xt(t,e){var i=e.getRootNode&&e.getRootNode();if(t.contains(e))return!0;if(i&&qt(i)){var n=e;do{if(n&&t.isSameNode(n))return!0;n=n.parentNode||n.host}while(n)}return!1}function Yt(t){return Wt(t).getComputedStyle(t)}function Qt(t){return["table","td","th"].indexOf(Rt(t))>=0}function Gt(t){return(($t(t)?t.ownerDocument:t.document)||window.document).documentElement}function Zt(t){return"html"===Rt(t)?t:t.assignedSlot||t.parentNode||(qt(t)?t.host:null)||Gt(t)}function Jt(t){return zt(t)&&"fixed"!==Yt(t).position?t.offsetParent:null}function te(t){for(var e=Wt(t),i=Jt(t);i&&Qt(i)&&"static"===Yt(i).position;)i=Jt(i);return i&&("html"===Rt(i)||"body"===Rt(i)&&"static"===Yt(i).position)?e:i||function(t){var e=-1!==navigator.userAgent.toLowerCase().indexOf("firefox");if(-1!==navigator.userAgent.indexOf("Trident")&&zt(t)&&"fixed"===Yt(t).position)return null;for(var i=Zt(t);zt(i)&&["html","body"].indexOf(Rt(i))<0;){var n=Yt(i);if("none"!==n.transform||"none"!==n.perspective||"paint"===n.contain||-1!==["transform","perspective"].indexOf(n.willChange)||e&&"filter"===n.willChange||e&&n.filter&&"none"!==n.filter)return i;i=i.parentNode}return null}(t)||e}function ee(t){return["top","bottom"].indexOf(t)>=0?"x":"y"}var ie=Math.max,ne=Math.min,se=Math.round;function oe(t,e,i){return ie(t,ne(e,i))}function re(t){return Object.assign({},{top:0,right:0,bottom:0,left:0},t)}function ae(t,e){return e.reduce((function(e,i){return e[i]=t,e}),{})}const le={name:"arrow",enabled:!0,phase:"main",fn:function(t){var e,i=t.state,n=t.name,s=t.options,o=i.elements.arrow,r=i.modifiersData.popperOffsets,a=Ut(i.placement),l=ee(a),c=[bt,_t].indexOf(a)>=0?"height":"width";if(o&&r){var h=function(t,e){return re("number"!=typeof(t="function"==typeof t?t(Object.assign({},e.rects,{placement:e.placement})):t)?t:ae(t,yt))}(s.padding,i),d=Kt(o),u="y"===l?mt:bt,f="y"===l?gt:_t,p=i.rects.reference[c]+i.rects.reference[l]-r[l]-i.rects.popper[c],m=r[l]-i.rects.reference[l],g=te(o),_=g?"y"===l?g.clientHeight||0:g.clientWidth||0:0,b=p/2-m/2,v=h[u],y=_-d[c]-h[f],w=_/2-d[c]/2+b,E=oe(v,w,y),A=l;i.modifiersData[n]=((e={})[A]=E,e.centerOffset=E-w,e)}},effect:function(t){var e=t.state,i=t.options.element,n=void 0===i?"[data-popper-arrow]":i;null!=n&&("string"!=typeof n||(n=e.elements.popper.querySelector(n)))&&Xt(e.elements.popper,n)&&(e.elements.arrow=n)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function ce(t){return t.split("-")[1]}var he={top:"auto",right:"auto",bottom:"auto",left:"auto"};function de(t){var e,i=t.popper,n=t.popperRect,s=t.placement,o=t.variation,r=t.offsets,a=t.position,l=t.gpuAcceleration,c=t.adaptive,h=t.roundOffsets,d=!0===h?function(t){var e=t.x,i=t.y,n=window.devicePixelRatio||1;return{x:se(se(e*n)/n)||0,y:se(se(i*n)/n)||0}}(r):"function"==typeof h?h(r):r,u=d.x,f=void 0===u?0:u,p=d.y,m=void 0===p?0:p,g=r.hasOwnProperty("x"),_=r.hasOwnProperty("y"),b=bt,v=mt,y=window;if(c){var w=te(i),E="clientHeight",A="clientWidth";w===Wt(i)&&"static"!==Yt(w=Gt(i)).position&&"absolute"===a&&(E="scrollHeight",A="scrollWidth"),w=w,s!==mt&&(s!==bt&&s!==_t||o!==Et)||(v=gt,m-=w[E]-n.height,m*=l?1:-1),s!==bt&&(s!==mt&&s!==gt||o!==Et)||(b=_t,f-=w[A]-n.width,f*=l?1:-1)}var T,O=Object.assign({position:a},c&&he);return l?Object.assign({},O,((T={})[v]=_?"0":"",T[b]=g?"0":"",T.transform=(y.devicePixelRatio||1)<=1?"translate("+f+"px, "+m+"px)":"translate3d("+f+"px, "+m+"px, 0)",T)):Object.assign({},O,((e={})[v]=_?m+"px":"",e[b]=g?f+"px":"",e.transform="",e))}const ue={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(t){var e=t.state,i=t.options,n=i.gpuAcceleration,s=void 0===n||n,o=i.adaptive,r=void 0===o||o,a=i.roundOffsets,l=void 0===a||a,c={placement:Ut(e.placement),variation:ce(e.placement),popper:e.elements.popper,popperRect:e.rects.popper,gpuAcceleration:s};null!=e.modifiersData.popperOffsets&&(e.styles.popper=Object.assign({},e.styles.popper,de(Object.assign({},c,{offsets:e.modifiersData.popperOffsets,position:e.options.strategy,adaptive:r,roundOffsets:l})))),null!=e.modifiersData.arrow&&(e.styles.arrow=Object.assign({},e.styles.arrow,de(Object.assign({},c,{offsets:e.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:l})))),e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-placement":e.placement})},data:{}};var fe={passive:!0};const pe={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(t){var e=t.state,i=t.instance,n=t.options,s=n.scroll,o=void 0===s||s,r=n.resize,a=void 0===r||r,l=Wt(e.elements.popper),c=[].concat(e.scrollParents.reference,e.scrollParents.popper);return o&&c.forEach((function(t){t.addEventListener("scroll",i.update,fe)})),a&&l.addEventListener("resize",i.update,fe),function(){o&&c.forEach((function(t){t.removeEventListener("scroll",i.update,fe)})),a&&l.removeEventListener("resize",i.update,fe)}},data:{}};var me={left:"right",right:"left",bottom:"top",top:"bottom"};function ge(t){return t.replace(/left|right|bottom|top/g,(function(t){return me[t]}))}var _e={start:"end",end:"start"};function be(t){return t.replace(/start|end/g,(function(t){return _e[t]}))}function ve(t){var e=Wt(t);return{scrollLeft:e.pageXOffset,scrollTop:e.pageYOffset}}function ye(t){return Vt(Gt(t)).left+ve(t).scrollLeft}function we(t){var e=Yt(t),i=e.overflow,n=e.overflowX,s=e.overflowY;return/auto|scroll|overlay|hidden/.test(i+s+n)}function Ee(t){return["html","body","#document"].indexOf(Rt(t))>=0?t.ownerDocument.body:zt(t)&&we(t)?t:Ee(Zt(t))}function Ae(t,e){var i;void 0===e&&(e=[]);var n=Ee(t),s=n===(null==(i=t.ownerDocument)?void 0:i.body),o=Wt(n),r=s?[o].concat(o.visualViewport||[],we(n)?n:[]):n,a=e.concat(r);return s?a:a.concat(Ae(Zt(r)))}function Te(t){return Object.assign({},t,{left:t.x,top:t.y,right:t.x+t.width,bottom:t.y+t.height})}function Oe(t,e){return e===Tt?Te(function(t){var e=Wt(t),i=Gt(t),n=e.visualViewport,s=i.clientWidth,o=i.clientHeight,r=0,a=0;return n&&(s=n.width,o=n.height,/^((?!chrome|android).)*safari/i.test(navigator.userAgent)||(r=n.offsetLeft,a=n.offsetTop)),{width:s,height:o,x:r+ye(t),y:a}}(t)):zt(e)?function(t){var e=Vt(t);return e.top=e.top+t.clientTop,e.left=e.left+t.clientLeft,e.bottom=e.top+t.clientHeight,e.right=e.left+t.clientWidth,e.width=t.clientWidth,e.height=t.clientHeight,e.x=e.left,e.y=e.top,e}(e):Te(function(t){var e,i=Gt(t),n=ve(t),s=null==(e=t.ownerDocument)?void 0:e.body,o=ie(i.scrollWidth,i.clientWidth,s?s.scrollWidth:0,s?s.clientWidth:0),r=ie(i.scrollHeight,i.clientHeight,s?s.scrollHeight:0,s?s.clientHeight:0),a=-n.scrollLeft+ye(t),l=-n.scrollTop;return"rtl"===Yt(s||i).direction&&(a+=ie(i.clientWidth,s?s.clientWidth:0)-o),{width:o,height:r,x:a,y:l}}(Gt(t)))}function Ce(t){var e,i=t.reference,n=t.element,s=t.placement,o=s?Ut(s):null,r=s?ce(s):null,a=i.x+i.width/2-n.width/2,l=i.y+i.height/2-n.height/2;switch(o){case mt:e={x:a,y:i.y-n.height};break;case gt:e={x:a,y:i.y+i.height};break;case _t:e={x:i.x+i.width,y:l};break;case bt:e={x:i.x-n.width,y:l};break;default:e={x:i.x,y:i.y}}var c=o?ee(o):null;if(null!=c){var h="y"===c?"height":"width";switch(r){case wt:e[c]=e[c]-(i[h]/2-n[h]/2);break;case Et:e[c]=e[c]+(i[h]/2-n[h]/2)}}return e}function ke(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=void 0===n?t.placement:n,o=i.boundary,r=void 0===o?At:o,a=i.rootBoundary,l=void 0===a?Tt:a,c=i.elementContext,h=void 0===c?Ot:c,d=i.altBoundary,u=void 0!==d&&d,f=i.padding,p=void 0===f?0:f,m=re("number"!=typeof p?p:ae(p,yt)),g=h===Ot?Ct:Ot,_=t.rects.popper,b=t.elements[u?g:h],v=function(t,e,i){var n="clippingParents"===e?function(t){var e=Ae(Zt(t)),i=["absolute","fixed"].indexOf(Yt(t).position)>=0&&zt(t)?te(t):t;return $t(i)?e.filter((function(t){return $t(t)&&Xt(t,i)&&"body"!==Rt(t)})):[]}(t):[].concat(e),s=[].concat(n,[i]),o=s[0],r=s.reduce((function(e,i){var n=Oe(t,i);return e.top=ie(n.top,e.top),e.right=ne(n.right,e.right),e.bottom=ne(n.bottom,e.bottom),e.left=ie(n.left,e.left),e}),Oe(t,o));return r.width=r.right-r.left,r.height=r.bottom-r.top,r.x=r.left,r.y=r.top,r}($t(b)?b:b.contextElement||Gt(t.elements.popper),r,l),y=Vt(t.elements.reference),w=Ce({reference:y,element:_,strategy:"absolute",placement:s}),E=Te(Object.assign({},_,w)),A=h===Ot?E:y,T={top:v.top-A.top+m.top,bottom:A.bottom-v.bottom+m.bottom,left:v.left-A.left+m.left,right:A.right-v.right+m.right},O=t.modifiersData.offset;if(h===Ot&&O){var C=O[s];Object.keys(T).forEach((function(t){var e=[_t,gt].indexOf(t)>=0?1:-1,i=[mt,gt].indexOf(t)>=0?"y":"x";T[t]+=C[i]*e}))}return T}function Le(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=i.boundary,o=i.rootBoundary,r=i.padding,a=i.flipVariations,l=i.allowedAutoPlacements,c=void 0===l?Lt:l,h=ce(n),d=h?a?kt:kt.filter((function(t){return ce(t)===h})):yt,u=d.filter((function(t){return c.indexOf(t)>=0}));0===u.length&&(u=d);var f=u.reduce((function(e,i){return e[i]=ke(t,{placement:i,boundary:s,rootBoundary:o,padding:r})[Ut(i)],e}),{});return Object.keys(f).sort((function(t,e){return f[t]-f[e]}))}const xe={name:"flip",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name;if(!e.modifiersData[n]._skip){for(var s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0===r||r,l=i.fallbackPlacements,c=i.padding,h=i.boundary,d=i.rootBoundary,u=i.altBoundary,f=i.flipVariations,p=void 0===f||f,m=i.allowedAutoPlacements,g=e.options.placement,_=Ut(g),b=l||(_!==g&&p?function(t){if(Ut(t)===vt)return[];var e=ge(t);return[be(t),e,be(e)]}(g):[ge(g)]),v=[g].concat(b).reduce((function(t,i){return t.concat(Ut(i)===vt?Le(e,{placement:i,boundary:h,rootBoundary:d,padding:c,flipVariations:p,allowedAutoPlacements:m}):i)}),[]),y=e.rects.reference,w=e.rects.popper,E=new Map,A=!0,T=v[0],O=0;O=0,D=x?"width":"height",S=ke(e,{placement:C,boundary:h,rootBoundary:d,altBoundary:u,padding:c}),N=x?L?_t:bt:L?gt:mt;y[D]>w[D]&&(N=ge(N));var I=ge(N),P=[];if(o&&P.push(S[k]<=0),a&&P.push(S[N]<=0,S[I]<=0),P.every((function(t){return t}))){T=C,A=!1;break}E.set(C,P)}if(A)for(var j=function(t){var e=v.find((function(e){var i=E.get(e);if(i)return i.slice(0,t).every((function(t){return t}))}));if(e)return T=e,"break"},M=p?3:1;M>0&&"break"!==j(M);M--);e.placement!==T&&(e.modifiersData[n]._skip=!0,e.placement=T,e.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function De(t,e,i){return void 0===i&&(i={x:0,y:0}),{top:t.top-e.height-i.y,right:t.right-e.width+i.x,bottom:t.bottom-e.height+i.y,left:t.left-e.width-i.x}}function Se(t){return[mt,_t,gt,bt].some((function(e){return t[e]>=0}))}const Ne={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(t){var e=t.state,i=t.name,n=e.rects.reference,s=e.rects.popper,o=e.modifiersData.preventOverflow,r=ke(e,{elementContext:"reference"}),a=ke(e,{altBoundary:!0}),l=De(r,n),c=De(a,s,o),h=Se(l),d=Se(c);e.modifiersData[i]={referenceClippingOffsets:l,popperEscapeOffsets:c,isReferenceHidden:h,hasPopperEscaped:d},e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-reference-hidden":h,"data-popper-escaped":d})}},Ie={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.offset,o=void 0===s?[0,0]:s,r=Lt.reduce((function(t,i){return t[i]=function(t,e,i){var n=Ut(t),s=[bt,mt].indexOf(n)>=0?-1:1,o="function"==typeof i?i(Object.assign({},e,{placement:t})):i,r=o[0],a=o[1];return r=r||0,a=(a||0)*s,[bt,_t].indexOf(n)>=0?{x:a,y:r}:{x:r,y:a}}(i,e.rects,o),t}),{}),a=r[e.placement],l=a.x,c=a.y;null!=e.modifiersData.popperOffsets&&(e.modifiersData.popperOffsets.x+=l,e.modifiersData.popperOffsets.y+=c),e.modifiersData[n]=r}},Pe={name:"popperOffsets",enabled:!0,phase:"read",fn:function(t){var e=t.state,i=t.name;e.modifiersData[i]=Ce({reference:e.rects.reference,element:e.rects.popper,strategy:"absolute",placement:e.placement})},data:{}},je={name:"preventOverflow",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0!==r&&r,l=i.boundary,c=i.rootBoundary,h=i.altBoundary,d=i.padding,u=i.tether,f=void 0===u||u,p=i.tetherOffset,m=void 0===p?0:p,g=ke(e,{boundary:l,rootBoundary:c,padding:d,altBoundary:h}),_=Ut(e.placement),b=ce(e.placement),v=!b,y=ee(_),w="x"===y?"y":"x",E=e.modifiersData.popperOffsets,A=e.rects.reference,T=e.rects.popper,O="function"==typeof m?m(Object.assign({},e.rects,{placement:e.placement})):m,C={x:0,y:0};if(E){if(o||a){var k="y"===y?mt:bt,L="y"===y?gt:_t,x="y"===y?"height":"width",D=E[y],S=E[y]+g[k],N=E[y]-g[L],I=f?-T[x]/2:0,P=b===wt?A[x]:T[x],j=b===wt?-T[x]:-A[x],M=e.elements.arrow,H=f&&M?Kt(M):{width:0,height:0},B=e.modifiersData["arrow#persistent"]?e.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},R=B[k],W=B[L],$=oe(0,A[x],H[x]),z=v?A[x]/2-I-$-R-O:P-$-R-O,q=v?-A[x]/2+I+$+W+O:j+$+W+O,F=e.elements.arrow&&te(e.elements.arrow),U=F?"y"===y?F.clientTop||0:F.clientLeft||0:0,V=e.modifiersData.offset?e.modifiersData.offset[e.placement][y]:0,K=E[y]+z-V-U,X=E[y]+q-V;if(o){var Y=oe(f?ne(S,K):S,D,f?ie(N,X):N);E[y]=Y,C[y]=Y-D}if(a){var Q="x"===y?mt:bt,G="x"===y?gt:_t,Z=E[w],J=Z+g[Q],tt=Z-g[G],et=oe(f?ne(J,K):J,Z,f?ie(tt,X):tt);E[w]=et,C[w]=et-Z}}e.modifiersData[n]=C}},requiresIfExists:["offset"]};function Me(t,e,i){void 0===i&&(i=!1);var n=zt(e);zt(e)&&function(t){var e=t.getBoundingClientRect();e.width,t.offsetWidth,e.height,t.offsetHeight}(e);var s,o,r=Gt(e),a=Vt(t),l={scrollLeft:0,scrollTop:0},c={x:0,y:0};return(n||!n&&!i)&&(("body"!==Rt(e)||we(r))&&(l=(s=e)!==Wt(s)&&zt(s)?{scrollLeft:(o=s).scrollLeft,scrollTop:o.scrollTop}:ve(s)),zt(e)?((c=Vt(e)).x+=e.clientLeft,c.y+=e.clientTop):r&&(c.x=ye(r))),{x:a.left+l.scrollLeft-c.x,y:a.top+l.scrollTop-c.y,width:a.width,height:a.height}}function He(t){var e=new Map,i=new Set,n=[];function s(t){i.add(t.name),[].concat(t.requires||[],t.requiresIfExists||[]).forEach((function(t){if(!i.has(t)){var n=e.get(t);n&&s(n)}})),n.push(t)}return t.forEach((function(t){e.set(t.name,t)})),t.forEach((function(t){i.has(t.name)||s(t)})),n}var Be={placement:"bottom",modifiers:[],strategy:"absolute"};function Re(){for(var t=arguments.length,e=new Array(t),i=0;ij.on(t,"mouseover",d))),this._element.focus(),this._element.setAttribute("aria-expanded",!0),this._menu.classList.add(Je),this._element.classList.add(Je),j.trigger(this._element,"shown.bs.dropdown",t)}hide(){if(c(this._element)||!this._isShown(this._menu))return;const t={relatedTarget:this._element};this._completeHide(t)}dispose(){this._popper&&this._popper.destroy(),super.dispose()}update(){this._inNavbar=this._detectNavbar(),this._popper&&this._popper.update()}_completeHide(t){j.trigger(this._element,"hide.bs.dropdown",t).defaultPrevented||("ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach((t=>j.off(t,"mouseover",d))),this._popper&&this._popper.destroy(),this._menu.classList.remove(Je),this._element.classList.remove(Je),this._element.setAttribute("aria-expanded","false"),U.removeDataAttribute(this._menu,"popper"),j.trigger(this._element,"hidden.bs.dropdown",t))}_getConfig(t){if(t={...this.constructor.Default,...U.getDataAttributes(this._element),...t},a(Ue,t,this.constructor.DefaultType),"object"==typeof t.reference&&!o(t.reference)&&"function"!=typeof t.reference.getBoundingClientRect)throw new TypeError(`${Ue.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`);return t}_createPopper(t){if(void 0===Fe)throw new TypeError("Bootstrap's dropdowns require Popper (https://popper.js.org)");let e=this._element;"parent"===this._config.reference?e=t:o(this._config.reference)?e=r(this._config.reference):"object"==typeof this._config.reference&&(e=this._config.reference);const i=this._getPopperConfig(),n=i.modifiers.find((t=>"applyStyles"===t.name&&!1===t.enabled));this._popper=qe(e,this._menu,i),n&&U.setDataAttribute(this._menu,"popper","static")}_isShown(t=this._element){return t.classList.contains(Je)}_getMenuElement(){return V.next(this._element,ei)[0]}_getPlacement(){const t=this._element.parentNode;if(t.classList.contains("dropend"))return ri;if(t.classList.contains("dropstart"))return ai;const e="end"===getComputedStyle(this._menu).getPropertyValue("--bs-position").trim();return t.classList.contains("dropup")?e?ni:ii:e?oi:si}_detectNavbar(){return null!==this._element.closest(".navbar")}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(){const t={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return"static"===this._config.display&&(t.modifiers=[{name:"applyStyles",enabled:!1}]),{...t,..."function"==typeof this._config.popperConfig?this._config.popperConfig(t):this._config.popperConfig}}_selectMenuItem({key:t,target:e}){const i=V.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter(l);i.length&&v(i,e,t===Ye,!i.includes(e)).focus()}static jQueryInterface(t){return this.each((function(){const e=hi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}static clearMenus(t){if(t&&(2===t.button||"keyup"===t.type&&"Tab"!==t.key))return;const e=V.find(ti);for(let i=0,n=e.length;ie+t)),this._setElementAttributes(di,"paddingRight",(e=>e+t)),this._setElementAttributes(ui,"marginRight",(e=>e-t))}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(t,e,i){const n=this.getWidth();this._applyManipulationCallback(t,(t=>{if(t!==this._element&&window.innerWidth>t.clientWidth+n)return;this._saveInitialAttribute(t,e);const s=window.getComputedStyle(t)[e];t.style[e]=`${i(Number.parseFloat(s))}px`}))}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,"paddingRight"),this._resetElementAttributes(di,"paddingRight"),this._resetElementAttributes(ui,"marginRight")}_saveInitialAttribute(t,e){const i=t.style[e];i&&U.setDataAttribute(t,e,i)}_resetElementAttributes(t,e){this._applyManipulationCallback(t,(t=>{const i=U.getDataAttribute(t,e);void 0===i?t.style.removeProperty(e):(U.removeDataAttribute(t,e),t.style[e]=i)}))}_applyManipulationCallback(t,e){o(t)?e(t):V.find(t,this._element).forEach(e)}isOverflowing(){return this.getWidth()>0}}const pi={className:"modal-backdrop",isVisible:!0,isAnimated:!1,rootElement:"body",clickCallback:null},mi={className:"string",isVisible:"boolean",isAnimated:"boolean",rootElement:"(element|string)",clickCallback:"(function|null)"},gi="show",_i="mousedown.bs.backdrop";class bi{constructor(t){this._config=this._getConfig(t),this._isAppended=!1,this._element=null}show(t){this._config.isVisible?(this._append(),this._config.isAnimated&&u(this._getElement()),this._getElement().classList.add(gi),this._emulateAnimation((()=>{_(t)}))):_(t)}hide(t){this._config.isVisible?(this._getElement().classList.remove(gi),this._emulateAnimation((()=>{this.dispose(),_(t)}))):_(t)}_getElement(){if(!this._element){const t=document.createElement("div");t.className=this._config.className,this._config.isAnimated&&t.classList.add("fade"),this._element=t}return this._element}_getConfig(t){return(t={...pi,..."object"==typeof t?t:{}}).rootElement=r(t.rootElement),a("backdrop",t,mi),t}_append(){this._isAppended||(this._config.rootElement.append(this._getElement()),j.on(this._getElement(),_i,(()=>{_(this._config.clickCallback)})),this._isAppended=!0)}dispose(){this._isAppended&&(j.off(this._element,_i),this._element.remove(),this._isAppended=!1)}_emulateAnimation(t){b(t,this._getElement(),this._config.isAnimated)}}const vi={trapElement:null,autofocus:!0},yi={trapElement:"element",autofocus:"boolean"},wi=".bs.focustrap",Ei="backward";class Ai{constructor(t){this._config=this._getConfig(t),this._isActive=!1,this._lastTabNavDirection=null}activate(){const{trapElement:t,autofocus:e}=this._config;this._isActive||(e&&t.focus(),j.off(document,wi),j.on(document,"focusin.bs.focustrap",(t=>this._handleFocusin(t))),j.on(document,"keydown.tab.bs.focustrap",(t=>this._handleKeydown(t))),this._isActive=!0)}deactivate(){this._isActive&&(this._isActive=!1,j.off(document,wi))}_handleFocusin(t){const{target:e}=t,{trapElement:i}=this._config;if(e===document||e===i||i.contains(e))return;const n=V.focusableChildren(i);0===n.length?i.focus():this._lastTabNavDirection===Ei?n[n.length-1].focus():n[0].focus()}_handleKeydown(t){"Tab"===t.key&&(this._lastTabNavDirection=t.shiftKey?Ei:"forward")}_getConfig(t){return t={...vi,..."object"==typeof t?t:{}},a("focustrap",t,yi),t}}const Ti="modal",Oi="Escape",Ci={backdrop:!0,keyboard:!0,focus:!0},ki={backdrop:"(boolean|string)",keyboard:"boolean",focus:"boolean"},Li="hidden.bs.modal",xi="show.bs.modal",Di="resize.bs.modal",Si="click.dismiss.bs.modal",Ni="keydown.dismiss.bs.modal",Ii="mousedown.dismiss.bs.modal",Pi="modal-open",ji="show",Mi="modal-static";class Hi extends B{constructor(t,e){super(t),this._config=this._getConfig(e),this._dialog=V.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._isShown=!1,this._ignoreBackdropClick=!1,this._isTransitioning=!1,this._scrollBar=new fi}static get Default(){return Ci}static get NAME(){return Ti}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||this._isTransitioning||j.trigger(this._element,xi,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._isAnimated()&&(this._isTransitioning=!0),this._scrollBar.hide(),document.body.classList.add(Pi),this._adjustDialog(),this._setEscapeEvent(),this._setResizeEvent(),j.on(this._dialog,Ii,(()=>{j.one(this._element,"mouseup.dismiss.bs.modal",(t=>{t.target===this._element&&(this._ignoreBackdropClick=!0)}))})),this._showBackdrop((()=>this._showElement(t))))}hide(){if(!this._isShown||this._isTransitioning)return;if(j.trigger(this._element,"hide.bs.modal").defaultPrevented)return;this._isShown=!1;const t=this._isAnimated();t&&(this._isTransitioning=!0),this._setEscapeEvent(),this._setResizeEvent(),this._focustrap.deactivate(),this._element.classList.remove(ji),j.off(this._element,Si),j.off(this._dialog,Ii),this._queueCallback((()=>this._hideModal()),this._element,t)}dispose(){[window,this._dialog].forEach((t=>j.off(t,".bs.modal"))),this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new bi({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_initializeFocusTrap(){return new Ai({trapElement:this._element})}_getConfig(t){return t={...Ci,...U.getDataAttributes(this._element),..."object"==typeof t?t:{}},a(Ti,t,ki),t}_showElement(t){const e=this._isAnimated(),i=V.findOne(".modal-body",this._dialog);this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE||document.body.append(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0,i&&(i.scrollTop=0),e&&u(this._element),this._element.classList.add(ji),this._queueCallback((()=>{this._config.focus&&this._focustrap.activate(),this._isTransitioning=!1,j.trigger(this._element,"shown.bs.modal",{relatedTarget:t})}),this._dialog,e)}_setEscapeEvent(){this._isShown?j.on(this._element,Ni,(t=>{this._config.keyboard&&t.key===Oi?(t.preventDefault(),this.hide()):this._config.keyboard||t.key!==Oi||this._triggerBackdropTransition()})):j.off(this._element,Ni)}_setResizeEvent(){this._isShown?j.on(window,Di,(()=>this._adjustDialog())):j.off(window,Di)}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide((()=>{document.body.classList.remove(Pi),this._resetAdjustments(),this._scrollBar.reset(),j.trigger(this._element,Li)}))}_showBackdrop(t){j.on(this._element,Si,(t=>{this._ignoreBackdropClick?this._ignoreBackdropClick=!1:t.target===t.currentTarget&&(!0===this._config.backdrop?this.hide():"static"===this._config.backdrop&&this._triggerBackdropTransition())})),this._backdrop.show(t)}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(j.trigger(this._element,"hidePrevented.bs.modal").defaultPrevented)return;const{classList:t,scrollHeight:e,style:i}=this._element,n=e>document.documentElement.clientHeight;!n&&"hidden"===i.overflowY||t.contains(Mi)||(n||(i.overflowY="hidden"),t.add(Mi),this._queueCallback((()=>{t.remove(Mi),n||this._queueCallback((()=>{i.overflowY=""}),this._dialog)}),this._dialog),this._element.focus())}_adjustDialog(){const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._scrollBar.getWidth(),i=e>0;(!i&&t&&!m()||i&&!t&&m())&&(this._element.style.paddingLeft=`${e}px`),(i&&!t&&!m()||!i&&t&&m())&&(this._element.style.paddingRight=`${e}px`)}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(t,e){return this.each((function(){const i=Hi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t](e)}}))}}j.on(document,"click.bs.modal.data-api",'[data-bs-toggle="modal"]',(function(t){const e=n(this);["A","AREA"].includes(this.tagName)&&t.preventDefault(),j.one(e,xi,(t=>{t.defaultPrevented||j.one(e,Li,(()=>{l(this)&&this.focus()}))}));const i=V.findOne(".modal.show");i&&Hi.getInstance(i).hide(),Hi.getOrCreateInstance(e).toggle(this)})),R(Hi),g(Hi);const Bi="offcanvas",Ri={backdrop:!0,keyboard:!0,scroll:!1},Wi={backdrop:"boolean",keyboard:"boolean",scroll:"boolean"},$i="show",zi=".offcanvas.show",qi="hidden.bs.offcanvas";class Fi extends B{constructor(t,e){super(t),this._config=this._getConfig(e),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._addEventListeners()}static get NAME(){return Bi}static get Default(){return Ri}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||j.trigger(this._element,"show.bs.offcanvas",{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._element.style.visibility="visible",this._backdrop.show(),this._config.scroll||(new fi).hide(),this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add($i),this._queueCallback((()=>{this._config.scroll||this._focustrap.activate(),j.trigger(this._element,"shown.bs.offcanvas",{relatedTarget:t})}),this._element,!0))}hide(){this._isShown&&(j.trigger(this._element,"hide.bs.offcanvas").defaultPrevented||(this._focustrap.deactivate(),this._element.blur(),this._isShown=!1,this._element.classList.remove($i),this._backdrop.hide(),this._queueCallback((()=>{this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._element.style.visibility="hidden",this._config.scroll||(new fi).reset(),j.trigger(this._element,qi)}),this._element,!0)))}dispose(){this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}_getConfig(t){return t={...Ri,...U.getDataAttributes(this._element),..."object"==typeof t?t:{}},a(Bi,t,Wi),t}_initializeBackDrop(){return new bi({className:"offcanvas-backdrop",isVisible:this._config.backdrop,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:()=>this.hide()})}_initializeFocusTrap(){return new Ai({trapElement:this._element})}_addEventListeners(){j.on(this._element,"keydown.dismiss.bs.offcanvas",(t=>{this._config.keyboard&&"Escape"===t.key&&this.hide()}))}static jQueryInterface(t){return this.each((function(){const e=Fi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}j.on(document,"click.bs.offcanvas.data-api",'[data-bs-toggle="offcanvas"]',(function(t){const e=n(this);if(["A","AREA"].includes(this.tagName)&&t.preventDefault(),c(this))return;j.one(e,qi,(()=>{l(this)&&this.focus()}));const i=V.findOne(zi);i&&i!==e&&Fi.getInstance(i).hide(),Fi.getOrCreateInstance(e).toggle(this)})),j.on(window,"load.bs.offcanvas.data-api",(()=>V.find(zi).forEach((t=>Fi.getOrCreateInstance(t).show())))),R(Fi),g(Fi);const Ui=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),Vi=/^(?:(?:https?|mailto|ftp|tel|file|sms):|[^#&/:?]*(?:[#/?]|$))/i,Ki=/^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[\d+/a-z]+=*$/i,Xi=(t,e)=>{const i=t.nodeName.toLowerCase();if(e.includes(i))return!Ui.has(i)||Boolean(Vi.test(t.nodeValue)||Ki.test(t.nodeValue));const n=e.filter((t=>t instanceof RegExp));for(let t=0,e=n.length;t{Xi(t,r)||i.removeAttribute(t.nodeName)}))}return n.body.innerHTML}const Qi="tooltip",Gi=new Set(["sanitize","allowList","sanitizeFn"]),Zi={animation:"boolean",template:"string",title:"(string|element|function)",trigger:"string",delay:"(number|object)",html:"boolean",selector:"(string|boolean)",placement:"(string|function)",offset:"(array|string|function)",container:"(string|element|boolean)",fallbackPlacements:"array",boundary:"(string|element)",customClass:"(string|function)",sanitize:"boolean",sanitizeFn:"(null|function)",allowList:"object",popperConfig:"(null|object|function)"},Ji={AUTO:"auto",TOP:"top",RIGHT:m()?"left":"right",BOTTOM:"bottom",LEFT:m()?"right":"left"},tn={animation:!0,template:'',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:[0,0],container:!1,fallbackPlacements:["top","right","bottom","left"],boundary:"clippingParents",customClass:"",sanitize:!0,sanitizeFn:null,allowList:{"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},popperConfig:null},en={HIDE:"hide.bs.tooltip",HIDDEN:"hidden.bs.tooltip",SHOW:"show.bs.tooltip",SHOWN:"shown.bs.tooltip",INSERTED:"inserted.bs.tooltip",CLICK:"click.bs.tooltip",FOCUSIN:"focusin.bs.tooltip",FOCUSOUT:"focusout.bs.tooltip",MOUSEENTER:"mouseenter.bs.tooltip",MOUSELEAVE:"mouseleave.bs.tooltip"},nn="fade",sn="show",on="show",rn="out",an=".tooltip-inner",ln=".modal",cn="hide.bs.modal",hn="hover",dn="focus";class un extends B{constructor(t,e){if(void 0===Fe)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");super(t),this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this._config=this._getConfig(e),this.tip=null,this._setListeners()}static get Default(){return tn}static get NAME(){return Qi}static get Event(){return en}static get DefaultType(){return Zi}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(t){if(this._isEnabled)if(t){const e=this._initializeOnDelegatedTarget(t);e._activeTrigger.click=!e._activeTrigger.click,e._isWithActiveTrigger()?e._enter(null,e):e._leave(null,e)}else{if(this.getTipElement().classList.contains(sn))return void this._leave(null,this);this._enter(null,this)}}dispose(){clearTimeout(this._timeout),j.off(this._element.closest(ln),cn,this._hideModalHandler),this.tip&&this.tip.remove(),this._disposePopper(),super.dispose()}show(){if("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(!this.isWithContent()||!this._isEnabled)return;const t=j.trigger(this._element,this.constructor.Event.SHOW),e=h(this._element),i=null===e?this._element.ownerDocument.documentElement.contains(this._element):e.contains(this._element);if(t.defaultPrevented||!i)return;"tooltip"===this.constructor.NAME&&this.tip&&this.getTitle()!==this.tip.querySelector(an).innerHTML&&(this._disposePopper(),this.tip.remove(),this.tip=null);const n=this.getTipElement(),s=(t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t})(this.constructor.NAME);n.setAttribute("id",s),this._element.setAttribute("aria-describedby",s),this._config.animation&&n.classList.add(nn);const o="function"==typeof this._config.placement?this._config.placement.call(this,n,this._element):this._config.placement,r=this._getAttachment(o);this._addAttachmentClass(r);const{container:a}=this._config;H.set(n,this.constructor.DATA_KEY,this),this._element.ownerDocument.documentElement.contains(this.tip)||(a.append(n),j.trigger(this._element,this.constructor.Event.INSERTED)),this._popper?this._popper.update():this._popper=qe(this._element,n,this._getPopperConfig(r)),n.classList.add(sn);const l=this._resolvePossibleFunction(this._config.customClass);l&&n.classList.add(...l.split(" ")),"ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach((t=>{j.on(t,"mouseover",d)}));const c=this.tip.classList.contains(nn);this._queueCallback((()=>{const t=this._hoverState;this._hoverState=null,j.trigger(this._element,this.constructor.Event.SHOWN),t===rn&&this._leave(null,this)}),this.tip,c)}hide(){if(!this._popper)return;const t=this.getTipElement();if(j.trigger(this._element,this.constructor.Event.HIDE).defaultPrevented)return;t.classList.remove(sn),"ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach((t=>j.off(t,"mouseover",d))),this._activeTrigger.click=!1,this._activeTrigger.focus=!1,this._activeTrigger.hover=!1;const e=this.tip.classList.contains(nn);this._queueCallback((()=>{this._isWithActiveTrigger()||(this._hoverState!==on&&t.remove(),this._cleanTipClass(),this._element.removeAttribute("aria-describedby"),j.trigger(this._element,this.constructor.Event.HIDDEN),this._disposePopper())}),this.tip,e),this._hoverState=""}update(){null!==this._popper&&this._popper.update()}isWithContent(){return Boolean(this.getTitle())}getTipElement(){if(this.tip)return this.tip;const t=document.createElement("div");t.innerHTML=this._config.template;const e=t.children[0];return this.setContent(e),e.classList.remove(nn,sn),this.tip=e,this.tip}setContent(t){this._sanitizeAndSetContent(t,this.getTitle(),an)}_sanitizeAndSetContent(t,e,i){const n=V.findOne(i,t);e||!n?this.setElementContent(n,e):n.remove()}setElementContent(t,e){if(null!==t)return o(e)?(e=r(e),void(this._config.html?e.parentNode!==t&&(t.innerHTML="",t.append(e)):t.textContent=e.textContent)):void(this._config.html?(this._config.sanitize&&(e=Yi(e,this._config.allowList,this._config.sanitizeFn)),t.innerHTML=e):t.textContent=e)}getTitle(){const t=this._element.getAttribute("data-bs-original-title")||this._config.title;return this._resolvePossibleFunction(t)}updateAttachment(t){return"right"===t?"end":"left"===t?"start":t}_initializeOnDelegatedTarget(t,e){return e||this.constructor.getOrCreateInstance(t.delegateTarget,this._getDelegateConfig())}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_resolvePossibleFunction(t){return"function"==typeof t?t.call(this._element):t}_getPopperConfig(t){const e={placement:t,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"onChange",enabled:!0,phase:"afterWrite",fn:t=>this._handlePopperPlacementChange(t)}],onFirstUpdate:t=>{t.options.placement!==t.placement&&this._handlePopperPlacementChange(t)}};return{...e,..."function"==typeof this._config.popperConfig?this._config.popperConfig(e):this._config.popperConfig}}_addAttachmentClass(t){this.getTipElement().classList.add(`${this._getBasicClassPrefix()}-${this.updateAttachment(t)}`)}_getAttachment(t){return Ji[t.toUpperCase()]}_setListeners(){this._config.trigger.split(" ").forEach((t=>{if("click"===t)j.on(this._element,this.constructor.Event.CLICK,this._config.selector,(t=>this.toggle(t)));else if("manual"!==t){const e=t===hn?this.constructor.Event.MOUSEENTER:this.constructor.Event.FOCUSIN,i=t===hn?this.constructor.Event.MOUSELEAVE:this.constructor.Event.FOCUSOUT;j.on(this._element,e,this._config.selector,(t=>this._enter(t))),j.on(this._element,i,this._config.selector,(t=>this._leave(t)))}})),this._hideModalHandler=()=>{this._element&&this.hide()},j.on(this._element.closest(ln),cn,this._hideModalHandler),this._config.selector?this._config={...this._config,trigger:"manual",selector:""}:this._fixTitle()}_fixTitle(){const t=this._element.getAttribute("title"),e=typeof this._element.getAttribute("data-bs-original-title");(t||"string"!==e)&&(this._element.setAttribute("data-bs-original-title",t||""),!t||this._element.getAttribute("aria-label")||this._element.textContent||this._element.setAttribute("aria-label",t),this._element.setAttribute("title",""))}_enter(t,e){e=this._initializeOnDelegatedTarget(t,e),t&&(e._activeTrigger["focusin"===t.type?dn:hn]=!0),e.getTipElement().classList.contains(sn)||e._hoverState===on?e._hoverState=on:(clearTimeout(e._timeout),e._hoverState=on,e._config.delay&&e._config.delay.show?e._timeout=setTimeout((()=>{e._hoverState===on&&e.show()}),e._config.delay.show):e.show())}_leave(t,e){e=this._initializeOnDelegatedTarget(t,e),t&&(e._activeTrigger["focusout"===t.type?dn:hn]=e._element.contains(t.relatedTarget)),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState=rn,e._config.delay&&e._config.delay.hide?e._timeout=setTimeout((()=>{e._hoverState===rn&&e.hide()}),e._config.delay.hide):e.hide())}_isWithActiveTrigger(){for(const t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1}_getConfig(t){const e=U.getDataAttributes(this._element);return Object.keys(e).forEach((t=>{Gi.has(t)&&delete e[t]})),(t={...this.constructor.Default,...e,..."object"==typeof t&&t?t:{}}).container=!1===t.container?document.body:r(t.container),"number"==typeof t.delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),a(Qi,t,this.constructor.DefaultType),t.sanitize&&(t.template=Yi(t.template,t.allowList,t.sanitizeFn)),t}_getDelegateConfig(){const t={};for(const e in this._config)this.constructor.Default[e]!==this._config[e]&&(t[e]=this._config[e]);return t}_cleanTipClass(){const t=this.getTipElement(),e=new RegExp(`(^|\\s)${this._getBasicClassPrefix()}\\S+`,"g"),i=t.getAttribute("class").match(e);null!==i&&i.length>0&&i.map((t=>t.trim())).forEach((e=>t.classList.remove(e)))}_getBasicClassPrefix(){return"bs-tooltip"}_handlePopperPlacementChange(t){const{state:e}=t;e&&(this.tip=e.elements.popper,this._cleanTipClass(),this._addAttachmentClass(this._getAttachment(e.placement)))}_disposePopper(){this._popper&&(this._popper.destroy(),this._popper=null)}static jQueryInterface(t){return this.each((function(){const e=un.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}g(un);const fn={...un.Default,placement:"right",offset:[0,8],trigger:"click",content:"",template:''},pn={...un.DefaultType,content:"(string|element|function)"},mn={HIDE:"hide.bs.popover",HIDDEN:"hidden.bs.popover",SHOW:"show.bs.popover",SHOWN:"shown.bs.popover",INSERTED:"inserted.bs.popover",CLICK:"click.bs.popover",FOCUSIN:"focusin.bs.popover",FOCUSOUT:"focusout.bs.popover",MOUSEENTER:"mouseenter.bs.popover",MOUSELEAVE:"mouseleave.bs.popover"};class gn extends un{static get Default(){return fn}static get NAME(){return"popover"}static get Event(){return mn}static get DefaultType(){return pn}isWithContent(){return this.getTitle()||this._getContent()}setContent(t){this._sanitizeAndSetContent(t,this.getTitle(),".popover-header"),this._sanitizeAndSetContent(t,this._getContent(),".popover-body")}_getContent(){return this._resolvePossibleFunction(this._config.content)}_getBasicClassPrefix(){return"bs-popover"}static jQueryInterface(t){return this.each((function(){const e=gn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}g(gn);const _n="scrollspy",bn={offset:10,method:"auto",target:""},vn={offset:"number",method:"string",target:"(string|element)"},yn="active",wn=".nav-link, .list-group-item, .dropdown-item",En="position";class An extends B{constructor(t,e){super(t),this._scrollElement="BODY"===this._element.tagName?window:this._element,this._config=this._getConfig(e),this._offsets=[],this._targets=[],this._activeTarget=null,this._scrollHeight=0,j.on(this._scrollElement,"scroll.bs.scrollspy",(()=>this._process())),this.refresh(),this._process()}static get Default(){return bn}static get NAME(){return _n}refresh(){const t=this._scrollElement===this._scrollElement.window?"offset":En,e="auto"===this._config.method?t:this._config.method,n=e===En?this._getScrollTop():0;this._offsets=[],this._targets=[],this._scrollHeight=this._getScrollHeight(),V.find(wn,this._config.target).map((t=>{const s=i(t),o=s?V.findOne(s):null;if(o){const t=o.getBoundingClientRect();if(t.width||t.height)return[U[e](o).top+n,s]}return null})).filter((t=>t)).sort(((t,e)=>t[0]-e[0])).forEach((t=>{this._offsets.push(t[0]),this._targets.push(t[1])}))}dispose(){j.off(this._scrollElement,".bs.scrollspy"),super.dispose()}_getConfig(t){return(t={...bn,...U.getDataAttributes(this._element),..."object"==typeof t&&t?t:{}}).target=r(t.target)||document.documentElement,a(_n,t,vn),t}_getScrollTop(){return this._scrollElement===window?this._scrollElement.pageYOffset:this._scrollElement.scrollTop}_getScrollHeight(){return this._scrollElement.scrollHeight||Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)}_getOffsetHeight(){return this._scrollElement===window?window.innerHeight:this._scrollElement.getBoundingClientRect().height}_process(){const t=this._getScrollTop()+this._config.offset,e=this._getScrollHeight(),i=this._config.offset+e-this._getOffsetHeight();if(this._scrollHeight!==e&&this.refresh(),t>=i){const t=this._targets[this._targets.length-1];this._activeTarget!==t&&this._activate(t)}else{if(this._activeTarget&&t0)return this._activeTarget=null,void this._clear();for(let e=this._offsets.length;e--;)this._activeTarget!==this._targets[e]&&t>=this._offsets[e]&&(void 0===this._offsets[e+1]||t`${e}[data-bs-target="${t}"],${e}[href="${t}"]`)),i=V.findOne(e.join(","),this._config.target);i.classList.add(yn),i.classList.contains("dropdown-item")?V.findOne(".dropdown-toggle",i.closest(".dropdown")).classList.add(yn):V.parents(i,".nav, .list-group").forEach((t=>{V.prev(t,".nav-link, .list-group-item").forEach((t=>t.classList.add(yn))),V.prev(t,".nav-item").forEach((t=>{V.children(t,".nav-link").forEach((t=>t.classList.add(yn)))}))})),j.trigger(this._scrollElement,"activate.bs.scrollspy",{relatedTarget:t})}_clear(){V.find(wn,this._config.target).filter((t=>t.classList.contains(yn))).forEach((t=>t.classList.remove(yn)))}static jQueryInterface(t){return this.each((function(){const e=An.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}j.on(window,"load.bs.scrollspy.data-api",(()=>{V.find('[data-bs-spy="scroll"]').forEach((t=>new An(t)))})),g(An);const Tn="active",On="fade",Cn="show",kn=".active",Ln=":scope > li > .active";class xn extends B{static get NAME(){return"tab"}show(){if(this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE&&this._element.classList.contains(Tn))return;let t;const e=n(this._element),i=this._element.closest(".nav, .list-group");if(i){const e="UL"===i.nodeName||"OL"===i.nodeName?Ln:kn;t=V.find(e,i),t=t[t.length-1]}const s=t?j.trigger(t,"hide.bs.tab",{relatedTarget:this._element}):null;if(j.trigger(this._element,"show.bs.tab",{relatedTarget:t}).defaultPrevented||null!==s&&s.defaultPrevented)return;this._activate(this._element,i);const o=()=>{j.trigger(t,"hidden.bs.tab",{relatedTarget:this._element}),j.trigger(this._element,"shown.bs.tab",{relatedTarget:t})};e?this._activate(e,e.parentNode,o):o()}_activate(t,e,i){const n=(!e||"UL"!==e.nodeName&&"OL"!==e.nodeName?V.children(e,kn):V.find(Ln,e))[0],s=i&&n&&n.classList.contains(On),o=()=>this._transitionComplete(t,n,i);n&&s?(n.classList.remove(Cn),this._queueCallback(o,t,!0)):o()}_transitionComplete(t,e,i){if(e){e.classList.remove(Tn);const t=V.findOne(":scope > .dropdown-menu .active",e.parentNode);t&&t.classList.remove(Tn),"tab"===e.getAttribute("role")&&e.setAttribute("aria-selected",!1)}t.classList.add(Tn),"tab"===t.getAttribute("role")&&t.setAttribute("aria-selected",!0),u(t),t.classList.contains(On)&&t.classList.add(Cn);let n=t.parentNode;if(n&&"LI"===n.nodeName&&(n=n.parentNode),n&&n.classList.contains("dropdown-menu")){const e=t.closest(".dropdown");e&&V.find(".dropdown-toggle",e).forEach((t=>t.classList.add(Tn))),t.setAttribute("aria-expanded",!0)}i&&i()}static jQueryInterface(t){return this.each((function(){const e=xn.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}j.on(document,"click.bs.tab.data-api",'[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',(function(t){["A","AREA"].includes(this.tagName)&&t.preventDefault(),c(this)||xn.getOrCreateInstance(this).show()})),g(xn);const Dn="toast",Sn="hide",Nn="show",In="showing",Pn={animation:"boolean",autohide:"boolean",delay:"number"},jn={animation:!0,autohide:!0,delay:5e3};class Mn extends B{constructor(t,e){super(t),this._config=this._getConfig(e),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get DefaultType(){return Pn}static get Default(){return jn}static get NAME(){return Dn}show(){j.trigger(this._element,"show.bs.toast").defaultPrevented||(this._clearTimeout(),this._config.animation&&this._element.classList.add("fade"),this._element.classList.remove(Sn),u(this._element),this._element.classList.add(Nn),this._element.classList.add(In),this._queueCallback((()=>{this._element.classList.remove(In),j.trigger(this._element,"shown.bs.toast"),this._maybeScheduleHide()}),this._element,this._config.animation))}hide(){this._element.classList.contains(Nn)&&(j.trigger(this._element,"hide.bs.toast").defaultPrevented||(this._element.classList.add(In),this._queueCallback((()=>{this._element.classList.add(Sn),this._element.classList.remove(In),this._element.classList.remove(Nn),j.trigger(this._element,"hidden.bs.toast")}),this._element,this._config.animation)))}dispose(){this._clearTimeout(),this._element.classList.contains(Nn)&&this._element.classList.remove(Nn),super.dispose()}_getConfig(t){return t={...jn,...U.getDataAttributes(this._element),..."object"==typeof t&&t?t:{}},a(Dn,t,this.constructor.DefaultType),t}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout((()=>{this.hide()}),this._config.delay)))}_onInteraction(t,e){switch(t.type){case"mouseover":case"mouseout":this._hasMouseInteraction=e;break;case"focusin":case"focusout":this._hasKeyboardInteraction=e}if(e)return void this._clearTimeout();const i=t.relatedTarget;this._element===i||this._element.contains(i)||this._maybeScheduleHide()}_setListeners(){j.on(this._element,"mouseover.bs.toast",(t=>this._onInteraction(t,!0))),j.on(this._element,"mouseout.bs.toast",(t=>this._onInteraction(t,!1))),j.on(this._element,"focusin.bs.toast",(t=>this._onInteraction(t,!0))),j.on(this._element,"focusout.bs.toast",(t=>this._onInteraction(t,!1)))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(t){return this.each((function(){const e=Mn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}return R(Mn),g(Mn),{Alert:W,Button:z,Carousel:st,Collapse:pt,Dropdown:hi,Modal:Hi,Offcanvas:Fi,Popover:gn,ScrollSpy:An,Tab:xn,Toast:Mn,Tooltip:un}}));
diff --git a/themes/30coffe/assets/js/contact-form-script.js b/themes/30coffe/assets/js/contact-form-script.js
new file mode 100644
index 00000000..fa78e80b
--- /dev/null
+++ b/themes/30coffe/assets/js/contact-form-script.js
@@ -0,0 +1,8 @@
+(function($){"use strict";$("#contactForm").validator().on("submit",function(event){if(event.isDefaultPrevented()){formError();submitMSG(false,"Did you fill in the form properly?");}
+else{event.preventDefault();submitForm();}});function submitForm(){var name=$("#name").val();var email=$("#email").val();var msg_subject=$("#msg_subject").val();var phone_number=$("#phone_number").val();var message=$("#message").val();var gridCheck=$("#gridCheck").val();$.ajax({type:"POST",url:"assets/php/form-process.php",data:"name="+name+"&email="+email+"&msg_subject="+msg_subject+"&phone_number="+phone_number+"&message="+message+"&gridCheck="+gridCheck,success:function(text){if(text=="success"){formSuccess();}
+else{formError();submitMSG(false,text);}}});}
+function formSuccess(){$("#contactForm")[0].reset();submitMSG(true,"Message Submitted!")}
+function formError(){$("#contactForm").removeClass().addClass('shake animated').one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend',function(){$(this).removeClass();});}
+function submitMSG(valid,msg){if(valid){var msgClasses="h4 tada animated text-success";}
+else{var msgClasses="h4 text-danger";}
+$("#msgSubmit").removeClass().addClass(msgClasses).text(msg);}}(jQuery));
\ No newline at end of file
diff --git a/themes/30coffe/assets/js/custom.js b/themes/30coffe/assets/js/custom.js
new file mode 100644
index 00000000..c2d55427
--- /dev/null
+++ b/themes/30coffe/assets/js/custom.js
@@ -0,0 +1,11 @@
+jQuery(function($){'use strict';$(window).on('scroll',function(){if($(this).scrollTop()>50){$('.main-nav').addClass('menu-shrink');}else{$('.main-nav').removeClass('menu-shrink');}});jQuery('.mean-menu').meanmenu({meanScreenWidth:"991"});$('.banner-slider').owlCarousel({items:1,loop:true,margin:0,nav:true,dots:false,smartSpeed:1000,autoplay:true,autoplayTimeout:4000,autoplayHoverPause:true,navText:[" "," "],});$('.service-slider').owlCarousel({center:true,loop:true,margin:15,nav:true,dots:false,smartSpeed:1000,autoplay:true,autoplayTimeout:4000,autoplayHoverPause:true,navText:[" "," "],responsive:{0:{items:1,},600:{items:2,},1000:{items:3,}}});$(document).ready(function(){$('.minus').on('click',function(){var $input=$(this).parent().find('input');var count=parseInt($input.val())-1;count=count<1?1:count;$input.val(count);$input.change();return false;});$('.plus').on('click',function(){var $input=$(this).parent().find('input');$input.val(parseInt($input.val())+1);$input.change();return false;});});$('.popup-youtube').magnificPopup({disableOn:320,type:'iframe',mainClass:'mfp-fade',removalDelay:160,preloader:false,fixedContentPos:false});try{var mixer=mixitup('#Container',{controls:{toggleDefault:'none'}});}catch(err){}
+$('.slider-for').slick({slidesToShow:1,slidesToScroll:1,arrows:true,fade:true,asNavFor:'.slider-nav',autoplay:true,centerPadding:'0px',prevArrow:" ",nextArrow:" "});$('.slider-nav').slick({slidesToShow:3,slidesToScroll:1,asNavFor:'.slider-for',dots:false,centerMode:true,focusOnSelect:true,arrows:false,centerPadding:'0px',});$(".newsletter-form").validator().on("submit",function(event){if(event.isDefaultPrevented()){formErrorSub();submitMSGSub(false,"Please enter your email correctly.");}else{event.preventDefault();}});function callbackFunction(resp){if(resp.result==="success"){formSuccessSub();}
+else{formErrorSub();}}
+function formSuccessSub(){$(".newsletter-form")[0].reset();submitMSGSub(true,"Thank you for subscribing!");setTimeout(function(){$("#validator-newsletter").addClass('hide');},4000)}
+function formErrorSub(){$(".newsletter-form").addClass("animated shake");setTimeout(function(){$(".newsletter-form").removeClass("animated shake");},1000)}
+function submitMSGSub(valid,msg){if(valid){var msgClasses="validation-success";}else{var msgClasses="validation-danger";}
+$("#validator-newsletter").removeClass().addClass(msgClasses).text(msg);}
+$(".newsletter-form").ajaxChimp({url:"https://envytheme.us20.list-manage.com/subscribe/post?u=60e1ffe2e8a68ce1204cd39a5&id=42d6d188d9",callback:callbackFunction});$(".modal a").not(".dropdown-toggle").on('click',function(){$(".modal").modal("hide");});$('.accordion > li:eq(0) a').addClass('active').next().slideDown();$('.accordion a').on('click',function(j){var dropDown=$(this).closest('li').find('p');$(this).closest('.accordion').find('p').not(dropDown).slideUp();if($(this).hasClass('active')){$(this).removeClass('active');}else{$(this).closest('.accordion').find('a.active').removeClass('active');$(this).addClass('active');}
+dropDown.stop(false,true).slideToggle();j.preventDefault();});let getDaysId=document.getElementById('days');if(getDaysId!==null){const second=1000;const minute=second*60;const hour=minute*60;const day=hour*24;let countDown=new Date('November 30, 2022 00:00:00').getTime();setInterval(function(){let now=new Date().getTime();let distance=countDown-now;document.getElementById('days').innerText=Math.floor(distance/(day)),document.getElementById('hours').innerText=Math.floor((distance%(day))/(hour)),document.getElementById('minutes').innerText=Math.floor((distance%(hour))/(minute)),document.getElementById('seconds').innerText=Math.floor((distance%(minute))/second);},second);};jQuery(window).on('load',function(){jQuery(".loader").fadeOut(500);});$('body').append('
');$(window).scroll(function(){if($(this).scrollTop()!=0){$('#toTop').fadeIn();}else{$('#toTop').fadeOut();}});$('#toTop').on('click',function(){$("html, body").animate({scrollTop:0},900);return false;});$('body').append("
");}(jQuery));function setTheme(themeName){localStorage.setItem('restant_theme',themeName);document.documentElement.className=themeName;}
+function toggleTheme(){if(localStorage.getItem('restant_theme')==='theme-dark'){setTheme('theme-light');}else{setTheme('theme-dark');}}
+(function(){if(localStorage.getItem('restant_theme')==='theme-dark'){setTheme('theme-dark');document.getElementById('slider').checked=false;}else{setTheme('theme-light');document.getElementById('slider').checked=true;}})();
\ No newline at end of file
diff --git a/themes/30coffe/assets/js/form-validator.min.js b/themes/30coffe/assets/js/form-validator.min.js
new file mode 100644
index 00000000..ffb43154
--- /dev/null
+++ b/themes/30coffe/assets/js/form-validator.min.js
@@ -0,0 +1,9 @@
+/*!
+ * Validator v0.8.1 for Bootstrap 3, by @1000hz
+ * Copyright 2015 Cina Saffary
+ * Licensed under http://opensource.org/licenses/MIT
+ *
+ * https://github.com/1000hz/bootstrap-validator
+ */
+
++function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b),f=c.data("bs.validator");(f||"destroy"!=b)&&(f||c.data("bs.validator",f=new d(this,e)),"string"==typeof b&&f[b]())})}var c=':input:not([type="submit"], button):enabled:visible',d=function(b,c){this.$element=a(b),this.options=c,c.errors=a.extend({},d.DEFAULTS.errors,c.errors);for(var e in c.custom)if(!c.errors[e])throw new Error("Missing default error message for custom validator: "+e);a.extend(d.VALIDATORS,c.custom),this.$element.attr("novalidate",!0),this.toggleSubmit(),this.$element.on("input.bs.validator change.bs.validator focusout.bs.validator",a.proxy(this.validateInput,this)),this.$element.on("submit.bs.validator",a.proxy(this.onSubmit,this)),this.$element.find("[data-match]").each(function(){var b=a(this),c=b.data("match");a(c).on("input.bs.validator",function(){b.val()&&b.trigger("input.bs.validator")})})};d.DEFAULTS={delay:500,html:!1,disable:!0,custom:{},errors:{match:"Does not match",minlength:"Not long enough"},feedback:{success:"glyphicon-ok",error:"glyphicon-warning-sign"}},d.VALIDATORS={"native":function(a){var b=a[0];return b.checkValidity?b.checkValidity():!0},match:function(b){var c=b.data("match");return!b.val()||b.val()===a(c).val()},minlength:function(a){var b=a.data("minlength");return!a.val()||a.val().length>=b}},d.prototype.validateInput=function(b){var c=a(b.target),d=c.data("bs.validator.errors");if(c.is('[type="radio"]')&&(c=this.$element.find('input[name="'+c.attr("name")+'"]')),this.$element.trigger(b=a.Event("validate.bs.validator",{relatedTarget:c[0]})),!b.isDefaultPrevented()){var e=this;this.runValidators(c).done(function(f){c.data("bs.validator.errors",f),f.length?e.showErrors(c):e.clearErrors(c),d&&f.toString()===d.toString()||(b=f.length?a.Event("invalid.bs.validator",{relatedTarget:c[0],detail:f}):a.Event("valid.bs.validator",{relatedTarget:c[0],detail:d}),e.$element.trigger(b)),e.toggleSubmit(),e.$element.trigger(a.Event("validated.bs.validator",{relatedTarget:c[0]}))})}},d.prototype.runValidators=function(b){function c(a){return b.data(a+"-error")||b.data("error")||"native"==a&&b[0].validationMessage||g.errors[a]}var e=[],f=a.Deferred(),g=this.options;return b.data("bs.validator.deferred")&&b.data("bs.validator.deferred").reject(),b.data("bs.validator.deferred",f),a.each(d.VALIDATORS,a.proxy(function(a,d){if((b.data(a)||"native"==a)&&!d.call(this,b)){var f=c(a);!~e.indexOf(f)&&e.push(f)}},this)),!e.length&&b.val()&&b.data("remote")?this.defer(b,function(){var d={};d[b.attr("name")]=b.val(),a.get(b.data("remote"),d).fail(function(a,b,d){e.push(c("remote")||d)}).always(function(){f.resolve(e)})}):f.resolve(e),f.promise()},d.prototype.validate=function(){var a=this.options.delay;return this.options.delay=0,this.$element.find(c).trigger("input.bs.validator"),this.options.delay=a,this},d.prototype.showErrors=function(b){var c=this.options.html?"html":"text";this.defer(b,function(){var d=b.closest(".form-group"),e=d.find(".help-block.with-errors"),f=d.find(".form-control-feedback"),g=b.data("bs.validator.errors");g.length&&(g=a("").addClass("list-unstyled").append(a.map(g,function(b){return a(" ")[c](b)})),void 0===e.data("bs.validator.originalContent")&&e.data("bs.validator.originalContent",e.html()),e.empty().append(g),d.addClass("has-error"),f.length&&f.removeClass(this.options.feedback.success)&&f.addClass(this.options.feedback.error)&&d.removeClass("has-success"))})},d.prototype.clearErrors=function(a){var b=a.closest(".form-group"),c=b.find(".help-block.with-errors"),d=b.find(".form-control-feedback");c.html(c.data("bs.validator.originalContent")),b.removeClass("has-error"),d.length&&d.removeClass(this.options.feedback.error)&&d.addClass(this.options.feedback.success)&&b.addClass("has-success")},d.prototype.hasErrors=function(){function b(){return!!(a(this).data("bs.validator.errors")||[]).length}return!!this.$element.find(c).filter(b).length},d.prototype.isIncomplete=function(){function b(){return"checkbox"===this.type?!this.checked:"radio"===this.type?!a('[name="'+this.name+'"]:checked').length:""===a.trim(this.value)}return!!this.$element.find(c).filter("[required]").filter(b).length},d.prototype.onSubmit=function(a){this.validate(),(this.isIncomplete()||this.hasErrors())&&a.preventDefault()},d.prototype.toggleSubmit=function(){if(this.options.disable){var b=a('button[type="submit"], input[type="submit"]').filter('[form="'+this.$element.attr("id")+'"]').add(this.$element.find('input[type="submit"], button[type="submit"]'));b.toggleClass("disabled",this.isIncomplete()||this.hasErrors()).css({"pointer-events":"all",cursor:"pointer"})}},d.prototype.defer=function(b,c){return c=a.proxy(c,this),this.options.delay?(window.clearTimeout(b.data("bs.validator.timeout")),void b.data("bs.validator.timeout",window.setTimeout(c,this.options.delay))):c()},d.prototype.destroy=function(){return this.$element.removeAttr("novalidate").removeData("bs.validator").off(".bs.validator"),this.$element.find(c).off(".bs.validator").removeData(["bs.validator.errors","bs.validator.deferred"]).each(function(){var b=a(this),c=b.data("bs.validator.timeout");window.clearTimeout(c)&&b.removeData("bs.validator.timeout")}),this.$element.find(".help-block.with-errors").each(function(){var b=a(this),c=b.data("bs.validator.originalContent");b.removeData("bs.validator.originalContent").html(c)}),this.$element.find('input[type="submit"], button[type="submit"]').removeClass("disabled"),this.$element.find(".has-error").removeClass("has-error"),this};var e=a.fn.validator;a.fn.validator=b,a.fn.validator.Constructor=d,a.fn.validator.noConflict=function(){return a.fn.validator=e,this},a(window).on("load",function(){a('form[data-toggle="validator"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery);
\ No newline at end of file
diff --git a/themes/30coffe/assets/js/jquery.ajaxchimp.min.js b/themes/30coffe/assets/js/jquery.ajaxchimp.min.js
new file mode 100644
index 00000000..83b637d3
--- /dev/null
+++ b/themes/30coffe/assets/js/jquery.ajaxchimp.min.js
@@ -0,0 +1 @@
+(function($){"use strict";$.ajaxChimp={responses:{"We have sent you a confirmation email":0,"Please enter a value":1,"An email address must contain a single @":2,"The domain portion of the email address is invalid (the portion after the @: )":3,"The username portion of the email address is invalid (the portion before the @: )":4,"This email address looks fake or invalid. Please enter a real email address":5},translations:{en:null},init:function(selector,options){$(selector).ajaxChimp(options)}};$.fn.ajaxChimp=function(options){$(this).each(function(i,elem){var form=$(elem);var email=form.find("input[type=email]");var label=form.find("label[for="+email.attr("id")+"]");var settings=$.extend({url:form.attr("action"),language:"en"},options);var url=settings.url.replace("/post?","/post-json?").concat("&c=?");form.attr("novalidate","true");email.attr("name","EMAIL");form.submit(function(){var msg;function successCallback(resp){if(resp.result==="success"){msg="We have sent you a confirmation email";label.removeClass("error").addClass("valid");email.removeClass("error").addClass("valid")}else{email.removeClass("valid").addClass("error");label.removeClass("valid").addClass("error");var index=-1;try{var parts=resp.msg.split(" - ",2);if(parts[1]===undefined){msg=resp.msg}else{var i=parseInt(parts[0],10);if(i.toString()===parts[0]){index=parts[0];msg=parts[1]}else{index=-1;msg=resp.msg}}}catch(e){index=-1;msg=resp.msg}}if(settings.language!=="en"&&$.ajaxChimp.responses[msg]!==undefined&&$.ajaxChimp.translations&&$.ajaxChimp.translations[settings.language]&&$.ajaxChimp.translations[settings.language][$.ajaxChimp.responses[msg]]){msg=$.ajaxChimp.translations[settings.language][$.ajaxChimp.responses[msg]]}label.html(msg);label.show(2e3);if(settings.callback){settings.callback(resp)}}var data={};var dataArray=form.serializeArray();$.each(dataArray,function(index,item){data[item.name]=item.value});$.ajax({url:url,data:data,success:successCallback,dataType:"jsonp",error:function(resp,text){console.log("mailchimp ajax submit error: "+text)}});var submitMsg="Submitting...";if(settings.language!=="en"&&$.ajaxChimp.translations&&$.ajaxChimp.translations[settings.language]&&$.ajaxChimp.translations[settings.language]["submit"]){submitMsg=$.ajaxChimp.translations[settings.language]["submit"]}label.html(submitMsg).show(2e3);return false})});return this}})(jQuery);
\ No newline at end of file
diff --git a/themes/30coffe/assets/js/jquery.magnific-popup.min.js b/themes/30coffe/assets/js/jquery.magnific-popup.min.js
new file mode 100644
index 00000000..6ee3a3bd
--- /dev/null
+++ b/themes/30coffe/assets/js/jquery.magnific-popup.min.js
@@ -0,0 +1,4 @@
+/*! Magnific Popup - v1.1.0 - 2016-02-20
+* http://dimsemenov.com/plugins/magnific-popup/
+* Copyright (c) 2016 Dmitry Semenov; */
+!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):a("object"==typeof exports?require("jquery"):window.jQuery||window.Zepto)}(function(a){var b,c,d,e,f,g,h="Close",i="BeforeClose",j="AfterClose",k="BeforeAppend",l="MarkupParse",m="Open",n="Change",o="mfp",p="."+o,q="mfp-ready",r="mfp-removing",s="mfp-prevent-close",t=function(){},u=!!window.jQuery,v=a(window),w=function(a,c){b.ev.on(o+a+p,c)},x=function(b,c,d,e){var f=document.createElement("div");return f.className="mfp-"+b,d&&(f.innerHTML=d),e?c&&c.appendChild(f):(f=a(f),c&&f.appendTo(c)),f},y=function(c,d){b.ev.triggerHandler(o+c,d),b.st.callbacks&&(c=c.charAt(0).toLowerCase()+c.slice(1),b.st.callbacks[c]&&b.st.callbacks[c].apply(b,a.isArray(d)?d:[d]))},z=function(c){return c===g&&b.currTemplate.closeBtn||(b.currTemplate.closeBtn=a(b.st.closeMarkup.replace("%title%",b.st.tClose)),g=c),b.currTemplate.closeBtn},A=function(){a.magnificPopup.instance||(b=new t,b.init(),a.magnificPopup.instance=b)},B=function(){var a=document.createElement("p").style,b=["ms","O","Moz","Webkit"];if(void 0!==a.transition)return!0;for(;b.length;)if(b.pop()+"Transition"in a)return!0;return!1};t.prototype={constructor:t,init:function(){var c=navigator.appVersion;b.isLowIE=b.isIE8=document.all&&!document.addEventListener,b.isAndroid=/android/gi.test(c),b.isIOS=/iphone|ipad|ipod/gi.test(c),b.supportsTransition=B(),b.probablyMobile=b.isAndroid||b.isIOS||/(Opera Mini)|Kindle|webOS|BlackBerry|(Opera Mobi)|(Windows Phone)|IEMobile/i.test(navigator.userAgent),d=a(document),b.popupsCache={}},open:function(c){var e;if(c.isObj===!1){b.items=c.items.toArray(),b.index=0;var g,h=c.items;for(e=0;e(a||v.height())},_setFocus:function(){(b.st.focus?b.content.find(b.st.focus).eq(0):b.wrap).focus()},_onFocusIn:function(c){return c.target===b.wrap[0]||a.contains(b.wrap[0],c.target)?void 0:(b._setFocus(),!1)},_parseMarkup:function(b,c,d){var e;d.data&&(c=a.extend(d.data,c)),y(l,[b,c,d]),a.each(c,function(c,d){if(void 0===d||d===!1)return!0;if(e=c.split("_"),e.length>1){var f=b.find(p+"-"+e[0]);if(f.length>0){var g=e[1];"replaceWith"===g?f[0]!==d[0]&&f.replaceWith(d):"img"===g?f.is("img")?f.attr("src",d):f.replaceWith(a(" ").attr("src",d).attr("class",f.attr("class"))):f.attr(e[1],d)}}else b.find(p+"-"+c).html(d)})},_getScrollbarSize:function(){if(void 0===b.scrollbarSize){var a=document.createElement("div");a.style.cssText="width: 99px; height: 99px; overflow: scroll; position: absolute; top: -9999px;",document.body.appendChild(a),b.scrollbarSize=a.offsetWidth-a.clientWidth,document.body.removeChild(a)}return b.scrollbarSize}},a.magnificPopup={instance:null,proto:t.prototype,modules:[],open:function(b,c){return A(),b=b?a.extend(!0,{},b):{},b.isObj=!0,b.index=c||0,this.instance.open(b)},close:function(){return a.magnificPopup.instance&&a.magnificPopup.instance.close()},registerModule:function(b,c){c.options&&(a.magnificPopup.defaults[b]=c.options),a.extend(this.proto,c.proto),this.modules.push(b)},defaults:{disableOn:0,key:null,midClick:!1,mainClass:"",preloader:!0,focus:"",closeOnContentClick:!1,closeOnBgClick:!0,closeBtnInside:!0,showCloseBtn:!0,enableEscapeKey:!0,modal:!1,alignTop:!1,removalDelay:0,prependTo:null,fixedContentPos:"auto",fixedBgPos:"auto",overflowY:"auto",closeMarkup:'× ',tClose:"Close (Esc)",tLoading:"Loading...",autoFocusLast:!0}},a.fn.magnificPopup=function(c){A();var d=a(this);if("string"==typeof c)if("open"===c){var e,f=u?d.data("magnificPopup"):d[0].magnificPopup,g=parseInt(arguments[1],10)||0;f.items?e=f.items[g]:(e=d,f.delegate&&(e=e.find(f.delegate)),e=e.eq(g)),b._openClick({mfpEl:e},d,f)}else b.isOpen&&b[c].apply(b,Array.prototype.slice.call(arguments,1));else c=a.extend(!0,{},c),u?d.data("magnificPopup",c):d[0].magnificPopup=c,b.addGroup(d,c);return d};var C,D,E,F="inline",G=function(){E&&(D.after(E.addClass(C)).detach(),E=null)};a.magnificPopup.registerModule(F,{options:{hiddenClass:"hide",markup:"",tNotFound:"Content not found"},proto:{initInline:function(){b.types.push(F),w(h+"."+F,function(){G()})},getInline:function(c,d){if(G(),c.src){var e=b.st.inline,f=a(c.src);if(f.length){var g=f[0].parentNode;g&&g.tagName&&(D||(C=e.hiddenClass,D=x(C),C="mfp-"+C),E=f.after(D).detach().removeClass(C)),b.updateStatus("ready")}else b.updateStatus("error",e.tNotFound),f=a("");return c.inlineElement=f,f}return b.updateStatus("ready"),b._parseMarkup(d,{},c),d}}});var H,I="ajax",J=function(){H&&a(document.body).removeClass(H)},K=function(){J(),b.req&&b.req.abort()};a.magnificPopup.registerModule(I,{options:{settings:null,cursor:"mfp-ajax-cur",tError:'
The content could not be loaded.'},proto:{initAjax:function(){b.types.push(I),H=b.st.ajax.cursor,w(h+"."+I,K),w("BeforeChange."+I,K)},getAjax:function(c){H&&a(document.body).addClass(H),b.updateStatus("loading");var d=a.extend({url:c.src,success:function(d,e,f){var g={data:d,xhr:f};y("ParseAjax",g),b.appendContent(a(g.data),I),c.finished=!0,J(),b._setFocus(),setTimeout(function(){b.wrap.addClass(q)},16),b.updateStatus("ready"),y("AjaxContentAdded")},error:function(){J(),c.finished=c.loadError=!0,b.updateStatus("error",b.st.ajax.tError.replace("%url%",c.src))}},b.st.ajax.settings);return b.req=a.ajax(d),""}}});var L,M=function(c){if(c.data&&void 0!==c.data.title)return c.data.title;var d=b.st.image.titleSrc;if(d){if(a.isFunction(d))return d.call(b,c);if(c.el)return c.el.attr(d)||""}return""};a.magnificPopup.registerModule("image",{options:{markup:'
',cursor:"mfp-zoom-out-cur",titleSrc:"title",verticalFit:!0,tError:'
The image could not be loaded.'},proto:{initImage:function(){var c=b.st.image,d=".image";b.types.push("image"),w(m+d,function(){"image"===b.currItem.type&&c.cursor&&a(document.body).addClass(c.cursor)}),w(h+d,function(){c.cursor&&a(document.body).removeClass(c.cursor),v.off("resize"+p)}),w("Resize"+d,b.resizeImage),b.isLowIE&&w("AfterChange",b.resizeImage)},resizeImage:function(){var a=b.currItem;if(a&&a.img&&b.st.image.verticalFit){var c=0;b.isLowIE&&(c=parseInt(a.img.css("padding-top"),10)+parseInt(a.img.css("padding-bottom"),10)),a.img.css("max-height",b.wH-c)}},_onImageHasSize:function(a){a.img&&(a.hasSize=!0,L&&clearInterval(L),a.isCheckingImgSize=!1,y("ImageHasSize",a),a.imgHidden&&(b.content&&b.content.removeClass("mfp-loading"),a.imgHidden=!1))},findImageSize:function(a){var c=0,d=a.img[0],e=function(f){L&&clearInterval(L),L=setInterval(function(){return d.naturalWidth>0?void b._onImageHasSize(a):(c>200&&clearInterval(L),c++,void(3===c?e(10):40===c?e(50):100===c&&e(500)))},f)};e(1)},getImage:function(c,d){var e=0,f=function(){c&&(c.img[0].complete?(c.img.off(".mfploader"),c===b.currItem&&(b._onImageHasSize(c),b.updateStatus("ready")),c.hasSize=!0,c.loaded=!0,y("ImageLoadComplete")):(e++,200>e?setTimeout(f,100):g()))},g=function(){c&&(c.img.off(".mfploader"),c===b.currItem&&(b._onImageHasSize(c),b.updateStatus("error",h.tError.replace("%url%",c.src))),c.hasSize=!0,c.loaded=!0,c.loadError=!0)},h=b.st.image,i=d.find(".mfp-img");if(i.length){var j=document.createElement("img");j.className="mfp-img",c.el&&c.el.find("img").length&&(j.alt=c.el.find("img").attr("alt")),c.img=a(j).on("load.mfploader",f).on("error.mfploader",g),j.src=c.src,i.is("img")&&(c.img=c.img.clone()),j=c.img[0],j.naturalWidth>0?c.hasSize=!0:j.width||(c.hasSize=!1)}return b._parseMarkup(d,{title:M(c),img_replaceWith:c.img},c),b.resizeImage(),c.hasSize?(L&&clearInterval(L),c.loadError?(d.addClass("mfp-loading"),b.updateStatus("error",h.tError.replace("%url%",c.src))):(d.removeClass("mfp-loading"),b.updateStatus("ready")),d):(b.updateStatus("loading"),c.loading=!0,c.hasSize||(c.imgHidden=!0,d.addClass("mfp-loading"),b.findImageSize(c)),d)}}});var N,O=function(){return void 0===N&&(N=void 0!==document.createElement("p").style.MozTransform),N};a.magnificPopup.registerModule("zoom",{options:{enabled:!1,easing:"ease-in-out",duration:300,opener:function(a){return a.is("img")?a:a.find("img")}},proto:{initZoom:function(){var a,c=b.st.zoom,d=".zoom";if(c.enabled&&b.supportsTransition){var e,f,g=c.duration,j=function(a){var b=a.clone().removeAttr("style").removeAttr("class").addClass("mfp-animated-image"),d="all "+c.duration/1e3+"s "+c.easing,e={position:"fixed",zIndex:9999,left:0,top:0,"-webkit-backface-visibility":"hidden"},f="transition";return e["-webkit-"+f]=e["-moz-"+f]=e["-o-"+f]=e[f]=d,b.css(e),b},k=function(){b.content.css("visibility","visible")};w("BuildControls"+d,function(){if(b._allowZoom()){if(clearTimeout(e),b.content.css("visibility","hidden"),a=b._getItemToZoom(),!a)return void k();f=j(a),f.css(b._getOffset()),b.wrap.append(f),e=setTimeout(function(){f.css(b._getOffset(!0)),e=setTimeout(function(){k(),setTimeout(function(){f.remove(),a=f=null,y("ZoomAnimationEnded")},16)},g)},16)}}),w(i+d,function(){if(b._allowZoom()){if(clearTimeout(e),b.st.removalDelay=g,!a){if(a=b._getItemToZoom(),!a)return;f=j(a)}f.css(b._getOffset(!0)),b.wrap.append(f),b.content.css("visibility","hidden"),setTimeout(function(){f.css(b._getOffset())},16)}}),w(h+d,function(){b._allowZoom()&&(k(),f&&f.remove(),a=null)})}},_allowZoom:function(){return"image"===b.currItem.type},_getItemToZoom:function(){return b.currItem.hasSize?b.currItem.img:!1},_getOffset:function(c){var d;d=c?b.currItem.img:b.st.zoom.opener(b.currItem.el||b.currItem);var e=d.offset(),f=parseInt(d.css("padding-top"),10),g=parseInt(d.css("padding-bottom"),10);e.top-=a(window).scrollTop()-f;var h={width:d.width(),height:(u?d.innerHeight():d[0].offsetHeight)-g-f};return O()?h["-moz-transform"]=h.transform="translate("+e.left+"px,"+e.top+"px)":(h.left=e.left,h.top=e.top),h}}});var P="iframe",Q="//about:blank",R=function(a){if(b.currTemplate[P]){var c=b.currTemplate[P].find("iframe");c.length&&(a||(c[0].src=Q),b.isIE8&&c.css("display",a?"block":"none"))}};a.magnificPopup.registerModule(P,{options:{markup:'
',srcAction:"iframe_src",patterns:{youtube:{index:"youtube.com",id:"v=",src:"//www.youtube.com/embed/%id%?autoplay=1"},vimeo:{index:"vimeo.com/",id:"/",src:"//player.vimeo.com/video/%id%?autoplay=1"},gmaps:{index:"//maps.google.",src:"%id%&output=embed"}}},proto:{initIframe:function(){b.types.push(P),w("BeforeChange",function(a,b,c){b!==c&&(b===P?R():c===P&&R(!0))}),w(h+"."+P,function(){R()})},getIframe:function(c,d){var e=c.src,f=b.st.iframe;a.each(f.patterns,function(){return e.indexOf(this.index)>-1?(this.id&&(e="string"==typeof this.id?e.substr(e.lastIndexOf(this.id)+this.id.length,e.length):this.id.call(this,e)),e=this.src.replace("%id%",e),!1):void 0});var g={};return f.srcAction&&(g[f.srcAction]=e),b._parseMarkup(d,g,c),b.updateStatus("ready"),d}}});var S=function(a){var c=b.items.length;return a>c-1?a-c:0>a?c+a:a},T=function(a,b,c){return a.replace(/%curr%/gi,b+1).replace(/%total%/gi,c)};a.magnificPopup.registerModule("gallery",{options:{enabled:!1,arrowMarkup:'
',preload:[0,2],navigateByImgClick:!0,arrows:!0,tPrev:"Previous (Left arrow key)",tNext:"Next (Right arrow key)",tCounter:"%curr% of %total%"},proto:{initGallery:function(){var c=b.st.gallery,e=".mfp-gallery";return b.direction=!0,c&&c.enabled?(f+=" mfp-gallery",w(m+e,function(){c.navigateByImgClick&&b.wrap.on("click"+e,".mfp-img",function(){return b.items.length>1?(b.next(),!1):void 0}),d.on("keydown"+e,function(a){37===a.keyCode?b.prev():39===a.keyCode&&b.next()})}),w("UpdateStatus"+e,function(a,c){c.text&&(c.text=T(c.text,b.currItem.index,b.items.length))}),w(l+e,function(a,d,e,f){var g=b.items.length;e.counter=g>1?T(c.tCounter,f.index,g):""}),w("BuildControls"+e,function(){if(b.items.length>1&&c.arrows&&!b.arrowLeft){var d=c.arrowMarkup,e=b.arrowLeft=a(d.replace(/%title%/gi,c.tPrev).replace(/%dir%/gi,"left")).addClass(s),f=b.arrowRight=a(d.replace(/%title%/gi,c.tNext).replace(/%dir%/gi,"right")).addClass(s);e.click(function(){b.prev()}),f.click(function(){b.next()}),b.container.append(e.add(f))}}),w(n+e,function(){b._preloadTimeout&&clearTimeout(b._preloadTimeout),b._preloadTimeout=setTimeout(function(){b.preloadNearbyImages(),b._preloadTimeout=null},16)}),void w(h+e,function(){d.off(e),b.wrap.off("click"+e),b.arrowRight=b.arrowLeft=null})):!1},next:function(){b.direction=!0,b.index=S(b.index+1),b.updateItemHTML()},prev:function(){b.direction=!1,b.index=S(b.index-1),b.updateItemHTML()},goTo:function(a){b.direction=a>=b.index,b.index=a,b.updateItemHTML()},preloadNearbyImages:function(){var a,c=b.st.gallery.preload,d=Math.min(c[0],b.items.length),e=Math.min(c[1],b.items.length);for(a=1;a<=(b.direction?e:d);a++)b._preloadItem(b.index+a);for(a=1;a<=(b.direction?d:e);a++)b._preloadItem(b.index-a)},_preloadItem:function(c){if(c=S(c),!b.items[c].preloaded){var d=b.items[c];d.parsed||(d=b.parseEl(c)),y("LazyLoad",d),"image"===d.type&&(d.img=a('
').on("load.mfploader",function(){d.hasSize=!0}).on("error.mfploader",function(){d.hasSize=!0,d.loadError=!0,y("LazyLoadError",d)}).attr("src",d.src)),d.preloaded=!0}}}});var U="retina";a.magnificPopup.registerModule(U,{options:{replaceSrc:function(a){return a.src.replace(/\.\w+$/,function(a){return"@2x"+a})},ratio:1},proto:{initRetina:function(){if(window.devicePixelRatio>1){var a=b.st.retina,c=a.ratio;c=isNaN(c)?c():c,c>1&&(w("ImageHasSize."+U,function(a,b){b.img.css({"max-width":b.img[0].naturalWidth/c,width:"100%"})}),w("ElementParse."+U,function(b,d){d.src=a.replaceSrc(d,c)}))}}}}),A()});
\ No newline at end of file
diff --git a/themes/30coffe/assets/js/jquery.meanmenu.js b/themes/30coffe/assets/js/jquery.meanmenu.js
new file mode 100644
index 00000000..0dc6dd26
--- /dev/null
+++ b/themes/30coffe/assets/js/jquery.meanmenu.js
@@ -0,0 +1 @@
+!function($){"use strict";$.fn.meanmenu=function(e){var n={meanMenuTarget:jQuery(this),meanMenuContainer:".mobile-nav",meanMenuClose:"X",meanMenuCloseSize:"18px",meanMenuOpen:"
",meanRevealPosition:"right",meanRevealPositionDistance:"0",meanRevealColour:"",meanScreenWidth:"480",meanNavPush:"",meanShowChildren:!0,meanExpandableChildren:!0,meanExpand:"+",meanContract:"-",meanRemoveAttrs:!1,onePage:!1,meanDisplay:"block",removeElements:""};e=$.extend(n,e);var a=window.innerWidth||document.documentElement.clientWidth;return this.each(function(){var n=e.meanMenuTarget,t=e.meanMenuContainer,r=e.meanMenuClose,i=e.meanMenuCloseSize,s=e.meanMenuOpen,u=e.meanRevealPosition,m=e.meanRevealPositionDistance,l=e.meanRevealColour,o=e.meanScreenWidth,c=e.meanNavPush,v=".meanmenu-reveal",h=e.meanShowChildren,d=e.meanExpandableChildren,y=e.meanExpand,j=e.meanContract,Q=e.meanRemoveAttrs,f=e.onePage,g=e.meanDisplay,p=e.removeElements,C=!1;(navigator.userAgent.match(/iPhone/i)||navigator.userAgent.match(/iPod/i)||navigator.userAgent.match(/iPad/i)||navigator.userAgent.match(/Android/i)||navigator.userAgent.match(/Blackberry/i)||navigator.userAgent.match(/Windows Phone/i))&&(C=!0),(navigator.userAgent.match(/MSIE 8/i)||navigator.userAgent.match(/MSIE 7/i))&&jQuery("html").css("overflow-y","scroll");var w="",x=function(){if("center"===u){var e=window.innerWidth||document.documentElement.clientWidth,n=e/2-22+"px";w="left:"+n+";right:auto;",C?jQuery(".meanmenu-reveal").animate({left:n}):jQuery(".meanmenu-reveal").css("left",n)}},A=!1,E=!1;"right"===u&&(w="right:"+m+";left:auto;"),"left"===u&&(w="left:"+m+";right:auto;"),x();var M="",P=function(){M.html(jQuery(M).is(".meanmenu-reveal.meanclose")?r:s)},W=function(){jQuery(".mean-bar,.mean-push").remove(),jQuery(t).removeClass("mean-container"),jQuery(n).css("display",g),A=!1,E=!1,jQuery(p).removeClass("mean-remove")},b=function(){var e="background:"+l+";color:"+l+";"+w;if(o>=a){jQuery(p).addClass("mean-remove"),E=!0,jQuery(t).addClass("mean-container"),jQuery(".mean-container").prepend('
');var r=jQuery(n).html();jQuery(".mean-nav").html(r),Q&&jQuery("nav.mean-nav ul, nav.mean-nav ul *").each(function(){jQuery(this).is(".mean-remove")?jQuery(this).attr("class","mean-remove"):jQuery(this).removeAttr("class"),jQuery(this).removeAttr("id")}),jQuery(n).before('
'),jQuery(".mean-push").css("margin-top",c),jQuery(n).hide(),jQuery(".meanmenu-reveal").show(),jQuery(v).html(s),M=jQuery(v),jQuery(".mean-nav ul").hide(),h?d?(jQuery(".mean-nav ul ul").each(function(){jQuery(this).children().length&&jQuery(this,"li:first").parent().append('
'+y+" ")}),jQuery(".mean-expand").on("click",function(e){e.preventDefault(),jQuery(this).hasClass("mean-clicked")?(jQuery(this).text(y),jQuery(this).prev("ul").slideUp(300,function(){})):(jQuery(this).text(j),jQuery(this).prev("ul").slideDown(300,function(){})),jQuery(this).toggleClass("mean-clicked")})):jQuery(".mean-nav ul ul").show():jQuery(".mean-nav ul ul").hide(),jQuery(".mean-nav ul li").last().addClass("mean-last"),M.removeClass("meanclose"),jQuery(M).click(function(e){e.preventDefault(),A===!1?(M.css("text-align","center"),M.css("text-indent","0"),M.css("font-size",i),jQuery(".mean-nav ul:first").slideDown(),A=!0):(jQuery(".mean-nav ul:first").slideUp(),A=!1),M.toggleClass("meanclose"),P(),jQuery(p).addClass("mean-remove")}),f&&jQuery(".mean-nav ul > li > a:first-child").on("click",function(){jQuery(".mean-nav ul:first").slideUp(),A=!1,jQuery(M).toggleClass("meanclose").html(s)})}else W()};C||jQuery(window).resize(function(){a=window.innerWidth||document.documentElement.clientWidth,a>o,W(),o>=a?(b(),x()):W()}),jQuery(window).resize(function(){a=window.innerWidth||document.documentElement.clientWidth,C?(x(),o>=a?E===!1&&b():W()):(W(),o>=a&&(b(),x()))}),b()})}}(jQuery);
\ No newline at end of file
diff --git a/themes/30coffe/assets/js/jquery.min.js b/themes/30coffe/assets/js/jquery.min.js
new file mode 100644
index 00000000..200b54e4
--- /dev/null
+++ b/themes/30coffe/assets/js/jquery.min.js
@@ -0,0 +1,2 @@
+/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */
+!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0
+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML=" ",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML=" ";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML=" ",y.option=!!ce.lastChild;var ge={thead:[1,""],col:[2,""],tr:[2,""],td:[3,""],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0-1}}(t.Element.prototype),Object.keys||(Object.keys=function(){var t=Object.prototype.hasOwnProperty,e=!1,n=[],a=-1;return e=!{toString:null}.propertyIsEnumerable("toString"),n=["toString","toLocaleString","valueOf","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","constructor"],a=n.length,function(i){var o=[],r="",s=-1;if("object"!=typeof i&&("function"!=typeof i||null===i))throw new TypeError("Object.keys called on non-object");for(r in i)t.call(i,r)&&o.push(r);if(e)for(s=0;s>>0,0===i)return-1;if(e=0,arguments.length>1&&(e=Number(arguments[1]),e!==e?e=0:0!==e&&e!==1/0&&e!==-(1/0)&&(e=(e>0||-1)*Math.floor(Math.abs(e)))),e>=i)return-1;for(n=e>=0?e:Math.max(i-Math.abs(e),0);n0)||s);g++)r.id?d=r.id:(d="MixItUp"+n.randomHex(),r.id=d),e.instances[d]instanceof e.Mixer?(l=e.instances[d],(!i||i&&i.debug&&i.debug.showWarnings!==!1)&&console.warn(e.messages.warningFactoryPreexistingInstance())):(l=new e.Mixer,l.attach(r,u,d,i),e.instances[d]=l),c=new e.Facade(l),i&&i.debug&&i.debug.enable?h.push(l):h.push(c);return f=s?new e.Collection(h):h[0]},e.use=function(t){e.Base.prototype.callActions.call(e,"beforeUse",arguments),"function"==typeof t&&"mixitup-extension"===t.TYPE?"undefined"==typeof e.extensions[t.NAME]&&(t(e),e.extensions[t.NAME]=t):t.fn&&t.fn.jquery&&(e.libraries.$=t),e.Base.prototype.callActions.call(e,"afterUse",arguments)},e.instances={},e.extensions={},e.libraries={},n={hasClass:function(t,e){return!!t.className.match(new RegExp("(\\s|^)"+e+"(\\s|$)"))},addClass:function(t,e){this.hasClass(t,e)||(t.className+=t.className?" "+e:e)},removeClass:function(t,e){if(this.hasClass(t,e)){var n=new RegExp("(\\s|^)"+e+"(\\s|$)");t.className=t.className.replace(n," ").trim()}},extend:function(t,e,n,a){var i=[],o="",r=-1;n=n||!1,a=a||!1;try{if(Array.isArray(e))for(r=0;ru&&(u=f,l=c)}throw u>1&&(s=e.messages.errorConfigInvalidPropertySuggestion({probableMatch:l})),r=e.messages.errorConfigInvalidProperty({erroneous:o,suggestion:s}),new TypeError(r)}throw t},template:function(t){for(var e=/\${([\w]*)}/g,n={},a=null;a=e.exec(t);)n[a[1]]=new RegExp("\\${"+a[1]+"}","g");return function(e){var a="",i=t;e=e||{};for(a in n)i=i.replace(n[a],"undefined"!=typeof e[a]?e[a]:"");return i}},on:function(e,n,a,i){e&&(e.addEventListener?e.addEventListener(n,a,i):e.attachEvent&&(e["e"+n+a]=a,e[n+a]=function(){e["e"+n+a](t.event)},e.attachEvent("on"+n,e[n+a])))},off:function(t,e,n){t&&(t.removeEventListener?t.removeEventListener(e,n,!1):t.detachEvent&&(t.detachEvent("on"+e,t[e+n]),t[e+n]=null))},getCustomEvent:function(e,n,a){var i=null;return a=a||t.document,"function"==typeof t.CustomEvent?i=new t.CustomEvent(e,{detail:n,bubbles:!0,cancelable:!0}):"function"==typeof a.createEvent?(i=a.createEvent("CustomEvent"),i.initCustomEvent(e,!0,!0,n)):(i=a.createEventObject(),i.type=e,i.returnValue=!1,i.cancelBubble=!1,i.detail=n),i},getOriginalEvent:function(t){return t.touches&&t.touches.length?t.touches[0]:t.changedTouches&&t.changedTouches.length?t.changedTouches[0]:t},index:function(t,e){for(var n=0;null!==(t=t.previousElementSibling);)e&&!t.matches(e)||++n;return n},camelCase:function(t){return t.toLowerCase().replace(/([_-][a-z])/g,function(t){return t.toUpperCase().replace(/[_-]/,"")})},pascalCase:function(t){return(t=this.camelCase(t)).charAt(0).toUpperCase()+t.slice(1)},dashCase:function(t){return t.replace(/([A-Z])/g,"-$1").replace(/^-/,"").toLowerCase()},isElement:function(e,n){return n=n||t.document,!!(t.HTMLElement&&e instanceof t.HTMLElement)||(!!(n.defaultView&&n.defaultView.HTMLElement&&e instanceof n.defaultView.HTMLElement)||null!==e&&1===e.nodeType&&"string"==typeof e.nodeName)},createElement:function(e,n){var a=null,i=null;for(n=n||t.document,a=n.createDocumentFragment(),i=n.createElement("div"),i.innerHTML=e.trim();i.firstChild;)a.appendChild(i.firstChild);return a},removeWhitespace:function(t){for(var e;t&&"#text"===t.nodeName;)e=t,t=t.previousSibling,e.parentElement&&e.parentElement.removeChild(e)},isEqualArray:function(t,e){var n=t.length;if(n!==e.length)return!1;for(;n--;)if(t[n]!==e[n])return!1;return!0},deepEquals:function(t,e){var n;if("object"==typeof t&&t&&"object"==typeof e&&e){if(Object.keys(t).length!==Object.keys(e).length)return!1;for(n in t)if(!e.hasOwnProperty(n)||!this.deepEquals(t[n],e[n]))return!1}else if(t!==e)return!1;return!0},arrayShuffle:function(t){for(var e=t.slice(),n=e.length,a=n,i=-1,o=[];a--;)i=~~(Math.random()*n),o=e[a],e[a]=e[i],e[i]=o;return e},arrayFromList:function(t){var e,n;try{return Array.prototype.slice.call(t)}catch(a){for(e=[],n=0;n "+n),o&&e.removeAttribute("id")),i},clean:function(t){var e=[],n=-1;for(n=0;ni)return!0}return!0},Deferred:function(){this.promise=null,this.resolve=null,this.reject=null,this.id=n.randomHex()},isEmptyObject:function(t){var e="";if("function"==typeof Object.keys)return 0===Object.keys(t).length;for(e in t)if(t.hasOwnProperty(e))return!1;return!0},getClassname:function(t,e,n){var a="";return a+=t.block,a.length&&(a+=t.delineatorElement),a+=t["element"+this.pascalCase(e)],n?(a.length&&(a+=t.delineatorModifier),a+=n):a},getProperty:function(t,e){var n=e.split("."),a=null,i="",o=0;if(!e)return t;for(a=function(t){return t?t[i]:null};o-1,e.callFilters("afterIsBound",n,arguments)},addBinding:function(t){var e=this;this.callActions("beforeAddBinding",arguments),e.isBound()||e.bound.push(t),this.callActions("afterAddBinding",arguments)},removeBinding:function(t){var n=this,a=-1;this.callActions("beforeRemoveBinding",arguments),(a=n.bound.indexOf(t))>-1&&n.bound.splice(a,1),n.bound.length<1&&(n.unbindClick(),a=e.controls.indexOf(n),e.controls.splice(a,1),"active"===n.status&&n.renderStatus(n.el,"inactive")),this.callActions("afterRemoveBinding",arguments)},bindClick:function(){var t=this;this.callActions("beforeBindClick",arguments),t.handler=function(e){t.handleClick(e)},n.on(t.el,"click",t.handler),this.callActions("afterBindClick",arguments)},unbindClick:function(){var t=this;this.callActions("beforeUnbindClick",arguments),n.off(t.el,"click",t.handler),t.handler=null,this.callActions("afterUnbindClick",arguments)},handleClick:function(t){var a=this,i=null,o=null,r=!1,s=void 0,l={},c=null,u=[],f=-1;if(this.callActions("beforeHandleClick",arguments),this.pending=0,o=a.bound[0],i=a.selector?n.closestParent(t.target,o.config.selectors.control+a.selector,!0,o.dom.document):a.el,!i)return void a.callActions("afterHandleClick",arguments);switch(a.type){case"filter":l.filter=a.filter||i.getAttribute("data-filter");break;case"sort":l.sort=a.sort||i.getAttribute("data-sort");break;case"multimix":l.filter=a.filter||i.getAttribute("data-filter"),l.sort=a.sort||i.getAttribute("data-sort");break;case"toggle":l.filter=a.filter||i.getAttribute("data-toggle"),r="live"===a.status?n.hasClass(i,a.classNames.active):"active"===a.status}for(f=0;f0||("live"===a.status?a.updateLive(t,n):(i.sort=a.sort,i.filter=a.filter,a.callFilters("actionsUpdate",i,arguments),a.parseStatusChange(a.el,t,i,n)),a.callActions("afterUpdate",arguments))},updateLive:function(t,n){var a=this,i=null,o=null,r=null,s=-1;if(a.callActions("beforeUpdateLive",arguments),a.el){for(i=a.el.querySelectorAll(a.selector),s=0;r=i[s];s++){switch(o=new e.CommandMultimix,a.type){case"filter":o.filter=r.getAttribute("data-filter");break;case"sort":o.sort=r.getAttribute("data-sort");break;case"multimix":o.filter=r.getAttribute("data-filter"),o.sort=r.getAttribute("data-sort");break;case"toggle":o.filter=r.getAttribute("data-toggle")}o=a.callFilters("actionsUpdateLive",o,arguments),a.parseStatusChange(r,t,o,n)}a.callActions("afterUpdateLive",arguments)}},parseStatusChange:function(t,e,n,a){var i=this,o="",r="",s=-1;switch(i.callActions("beforeParseStatusChange",arguments),i.type){case"filter":e.filter===n.filter?i.renderStatus(t,"active"):i.renderStatus(t,"inactive");break;case"multimix":e.sort===n.sort&&e.filter===n.filter?i.renderStatus(t,"active"):i.renderStatus(t,"inactive");break;case"sort":e.sort.match(/:asc/g)&&(o=e.sort.replace(/:asc/g,"")),e.sort===n.sort||o===n.sort?i.renderStatus(t,"active"):i.renderStatus(t,"inactive");break;case"toggle":for(a.length<1&&i.renderStatus(t,"inactive"),e.filter===n.filter&&i.renderStatus(t,"active"),s=0;s-1)throw new Error(e.messages.errorInsertPreexistingElement());c.style.display="none",s.appendChild(c),s.appendChild(i.dom.document.createTextNode(" ")),n.isElement(c,i.dom.document)&&c.matches(i.config.selectors.target)&&(l=new e.Target,l.init(c,i),l.isInDom=!0,i.targets.splice(r,0,l),r++)}i.dom.parent.insertBefore(s,o)}a.startOrder=i.origOrder=i.targets,i.callActions("afterInsertTargets",arguments)},getNextSibling:function(t,e,n){var a=this,i=null;return t=Math.max(t,0),e&&"before"===n?i=e:e&&"after"===n?i=e.nextElementSibling||null:a.targets.length>0&&"undefined"!=typeof t?i=t0&&(a.config.layout.siblingAfter?i=a.config.layout.siblingAfter:a.config.layout.siblingBefore?i=a.config.layout.siblingBefore.nextElementSibling:a.dom.parent.children[0]),a.callFilters("elementGetNextSibling",i,arguments)},filterOperation:function(t){var e=this,n=!1,a=-1,i="",o=null,r=-1;for(e.callActions("beforeFilterOperation",arguments),i=t.newFilter.action,r=0;o=t.newOrder[r];r++)n=t.newFilter.collection?t.newFilter.collection.indexOf(o.dom.el)>-1:""!==t.newFilter.selector&&o.dom.el.matches(t.newFilter.selector),e.evaluateHideShow(n,o,i,t);if(t.toRemove.length)for(r=0;o=t.show[r];r++)t.toRemove.indexOf(o)>-1&&(t.show.splice(r,1),(a=t.toShow.indexOf(o))>-1&&t.toShow.splice(a,1),t.toHide.push(o),t.hide.push(o),r--);t.matching=t.show.slice(),0===t.show.length&&""!==t.newFilter.selector&&0!==e.targets.length&&(t.hasFailed=!0),e.callActions("afterFilterOperation",arguments)},evaluateHideShow:function(t,e,n,a){var i=this,o=!1,r=Array.prototype.slice.call(arguments,1);o=i.callFilters("testResultEvaluateHideShow",t,r),i.callActions("beforeEvaluateHideShow",arguments),o===!0&&"show"===n||o===!1&&"hide"===n?(a.show.push(e),!e.isShown&&a.toShow.push(e)):(a.hide.push(e),e.isShown&&a.toHide.push(e)),i.callActions("afterEvaluateHideShow",arguments)},sortOperation:function(t){var a=this,i=[],o=null,r=null,s=-1;if(a.callActions("beforeSortOperation",arguments),t.startOrder=a.targets,t.newSort.collection){for(i=[],s=0;r=t.newSort.collection[s];s++){if(a.dom.targets.indexOf(r)<0)throw new Error(e.messages.errorSortNonExistentElement());o=new e.Target,o.init(r,a),o.isInDom=!0,i.push(o)}t.newOrder=i}else"random"===t.newSort.order?t.newOrder=n.arrayShuffle(t.startOrder):""===t.newSort.attribute?(t.newOrder=a.origOrder.slice(),"desc"===t.newSort.order&&t.newOrder.reverse()):(t.newOrder=t.startOrder.slice(),t.newOrder.sort(function(e,n){return a.compare(e,n,t.newSort)}));n.isEqualArray(t.newOrder,t.startOrder)&&(t.willSort=!1),a.callActions("afterSortOperation",arguments)},compare:function(t,e,n){var a=this,i=n.order,o=a.getAttributeValue(t,n.attribute),r=a.getAttributeValue(e,n.attribute);return isNaN(1*o)||isNaN(1*r)?(o=o.toLowerCase(),r=r.toLowerCase()):(o=1*o,r=1*r),or?"asc"===i?1:-1:o===r&&n.next?a.compare(t,e,n.next):0},getAttributeValue:function(t,n){var a=this,i="";return i=t.dom.el.getAttribute("data-"+n),null===i&&a.config.debug.showWarnings&&console.warn(e.messages.warningInconsistentSortingAttributes({attribute:"data-"+n})),a.callFilters("valueGetAttributeValue",i||0,arguments)},printSort:function(e,a){var i=this,o=e?a.newOrder:a.startOrder,r=e?a.startOrder:a.newOrder,s=o.length?o[o.length-1].dom.el.nextElementSibling:null,l=t.document.createDocumentFragment(),c=null,u=null,f=null,h=-1;for(i.callActions("beforePrintSort",arguments),h=0;u=o[h];h++)f=u.dom.el,"absolute"!==f.style.position&&(n.removeWhitespace(f.previousSibling),f.parentElement.removeChild(f));for(c=s?s.previousSibling:i.dom.parent.lastChild,c&&"#text"===c.nodeName&&n.removeWhitespace(c),h=0;u=r[h];h++)f=u.dom.el,n.isElement(l.lastChild)&&l.appendChild(t.document.createTextNode(" ")),l.appendChild(f);i.dom.parent.firstChild&&i.dom.parent.firstChild!==s&&l.insertBefore(t.document.createTextNode(" "),l.childNodes[0]),s?(l.appendChild(t.document.createTextNode(" ")),i.dom.parent.insertBefore(l,s)):i.dom.parent.appendChild(l),i.callActions("afterPrintSort",arguments)},parseSortString:function(t,a){var i=this,o=t.split(" "),r=a,s=[],l=-1;for(l=0;l-1&&(c=n.substring(l),u=s.exec(c),f=u[1]),t){case"fade":a.opacity=f?parseFloat(f):0;break;case"stagger":r.staggerDuration=f?parseFloat(f):100;break;default:if(o&&r.config.animation.reverseOut&&"scale"!==t?a[t].value=(f?parseFloat(f):e.transformDefaults[t].value)*-1:a[t].value=f?parseFloat(f):e.transformDefaults[t].value,f){for(m=0;d=h[m];m++)if(f.indexOf(d)>-1){a[t].unit=d;break}}else a[t].unit=e.transformDefaults[t].unit;i.push(t+"("+a[t].value+a[t].unit+")")}r.callActions("afterParseEffect",arguments)},buildState:function(t){var n=this,a=new e.State,i=null,o=-1;for(n.callActions("beforeBuildState",arguments),o=0;i=n.targets[o];o++)(!t.toRemove.length||t.toRemove.indexOf(i)<0)&&a.targets.push(i.dom.el);for(o=0;i=t.matching[o];o++)a.matching.push(i.dom.el);for(o=0;i=t.show[o];o++)a.show.push(i.dom.el);for(o=0;i=t.hide[o];o++)(!t.toRemove.length||t.toRemove.indexOf(i)<0)&&a.hide.push(i.dom.el);return a.id=n.id,a.container=n.dom.container,a.activeFilter=t.newFilter,a.activeSort=t.newSort,a.activeDataset=t.newDataset,a.activeContainerClassName=t.newContainerClassName,a.hasFailed=t.hasFailed,a.totalTargets=n.targets.length,a.totalShow=t.show.length,a.totalHide=t.hide.length,a.totalMatching=t.matching.length,a.triggerElement=t.triggerElement,n.callFilters("stateBuildState",a,arguments)},goMix:function(a,i){var o=this,r=null;return o.callActions("beforeGoMix",arguments),o.config.animation.duration&&o.config.animation.effects&&n.isVisible(o.dom.container)||(a=!1),i.toShow.length||i.toHide.length||i.willSort||i.willChangeLayout||(a=!1),i.startState.show.length||i.show.length||(a=!1),e.events.fire("mixStart",o.dom.container,{state:i.startState,futureState:i.newState,instance:o},o.dom.document),"function"==typeof o.config.callbacks.onMixStart&&o.config.callbacks.onMixStart.call(o.dom.container,i.startState,i.newState,o),n.removeClass(o.dom.container,n.getClassname(o.config.classNames,"container",o.config.classNames.modifierFailed)),r=o.userDeferred?o.userDeferred:o.userDeferred=n.defer(e.libraries),o.isBusy=!0,a&&e.features.has.transitions?(t.pageYOffset!==i.docState.scrollTop&&t.scrollTo(i.docState.scrollLeft,i.docState.scrollTop),o.config.animation.applyPerspective&&(o.dom.parent.style[e.features.perspectiveProp]=o.config.animation.perspectiveDistance,o.dom.parent.style[e.features.perspectiveOriginProp]=o.config.animation.perspectiveOrigin),o.config.animation.animateResizeContainer&&i.startHeight!==i.newHeight&&i.viewportDeltaY!==i.startHeight-i.newHeight&&(o.dom.parent.style.height=i.startHeight+"px"),o.config.animation.animateResizeContainer&&i.startWidth!==i.newWidth&&i.viewportDeltaX!==i.startWidth-i.newWidth&&(o.dom.parent.style.width=i.startWidth+"px"),i.startHeight===i.newHeight&&(o.dom.parent.style.height=i.startHeight+"px"),i.startWidth===i.newWidth&&(o.dom.parent.style.width=i.startWidth+"px"),i.startHeight===i.newHeight&&i.startWidth===i.newWidth&&(o.dom.parent.style.overflow="hidden"),requestAnimationFrame(function(){o.moveTargets(i)}),o.callFilters("promiseGoMix",r.promise,arguments)):(o.config.debug.fauxAsync?setTimeout(function(){o.cleanUp(i)},o.config.animation.duration):o.cleanUp(i),o.callFilters("promiseGoMix",r.promise,arguments))},getStartMixData:function(n){var a=this,i=t.getComputedStyle(a.dom.parent),o=a.dom.parent.getBoundingClientRect(),r=null,s={},l=-1,c=i[e.features.boxSizingProp];for(a.incPadding="border-box"===c,a.callActions("beforeGetStartMixData",arguments),l=0;r=n.show[l];l++)s=r.getPosData(),n.showPosData[l]={startPosData:s};for(l=0;r=n.toHide[l];l++)s=r.getPosData(),n.toHidePosData[l]={startPosData:s};n.startX=o.left,n.startY=o.top,n.startHeight=a.incPadding?o.height:o.height-parseFloat(i.paddingTop)-parseFloat(i.paddingBottom)-parseFloat(i.borderTop)-parseFloat(i.borderBottom),n.startWidth=a.incPadding?o.width:o.width-parseFloat(i.paddingLeft)-parseFloat(i.paddingRight)-parseFloat(i.borderLeft)-parseFloat(i.borderRight),a.callActions("afterGetStartMixData",arguments)},setInter:function(t){var e=this,a=null,i=-1;for(e.callActions("beforeSetInter",arguments),e.config.animation.clampHeight&&(e.dom.parent.style.height=t.startHeight+"px",e.dom.parent.style.overflow="hidden"),e.config.animation.clampWidth&&(e.dom.parent.style.width=t.startWidth+"px",e.dom.parent.style.overflow="hidden"),i=0;a=t.toShow[i];i++)a.show();t.willChangeLayout&&(n.removeClass(e.dom.container,t.startContainerClassName),n.addClass(e.dom.container,t.newContainerClassName)),e.callActions("afterSetInter",arguments)},getInterMixData:function(t){var e=this,n=null,a=-1;for(e.callActions("beforeGetInterMixData",arguments),a=0;n=t.show[a];a++)t.showPosData[a].interPosData=n.getPosData();for(a=0;n=t.toHide[a];a++)t.toHidePosData[a].interPosData=n.getPosData();e.callActions("afterGetInterMixData",arguments)},setFinal:function(t){var e=this,n=null,a=-1;for(e.callActions("beforeSetFinal",arguments),t.willSort&&e.printSort(!1,t),a=0;n=t.toHide[a];a++)n.hide();e.callActions("afterSetFinal",arguments)},getFinalMixData:function(e){var a=this,i=null,o=null,r=null,s=-1;for(a.callActions("beforeGetFinalMixData",arguments),s=0;r=e.show[s];s++)e.showPosData[s].finalPosData=r.getPosData();for(s=0;r=e.toHide[s];s++)e.toHidePosData[s].finalPosData=r.getPosData();for((a.config.animation.clampHeight||a.config.animation.clampWidth)&&(a.dom.parent.style.height=a.dom.parent.style.width=a.dom.parent.style.overflow=""),a.incPadding||(i=t.getComputedStyle(a.dom.parent)),o=a.dom.parent.getBoundingClientRect(),e.newX=o.left,e.newY=o.top,e.newHeight=a.incPadding?o.height:o.height-parseFloat(i.paddingTop)-parseFloat(i.paddingBottom)-parseFloat(i.borderTop)-parseFloat(i.borderBottom),e.newWidth=a.incPadding?o.width:o.width-parseFloat(i.paddingLeft)-parseFloat(i.paddingRight)-parseFloat(i.borderLeft)-parseFloat(i.borderRight),e.viewportDeltaX=e.docState.viewportWidth-this.dom.document.documentElement.clientWidth,e.viewportDeltaY=e.docState.viewportHeight-this.dom.document.documentElement.clientHeight,e.willSort&&a.printSort(!0,e),s=0;r=e.toShow[s];s++)r.hide();for(s=0;r=e.toHide[s];s++)r.show();e.willChangeLayout&&(n.removeClass(a.dom.container,e.newContainerClassName),n.addClass(a.dom.container,a.config.layout.containerClassName)),a.callActions("afterGetFinalMixData",arguments)},getTweenData:function(t){var n=this,a=null,i=null,o=Object.getOwnPropertyNames(n.effectsIn),r="",s=null,l=-1,c=-1,u=-1,f=-1;for(n.callActions("beforeGetTweenData",arguments),u=0;a=t.show[u];u++)for(i=t.showPosData[u],i.posIn=new e.StyleData,i.posOut=new e.StyleData,i.tweenData=new e.StyleData,a.isShown?(i.posIn.x=i.startPosData.x-i.interPosData.x,i.posIn.y=i.startPosData.y-i.interPosData.y):i.posIn.x=i.posIn.y=0,i.posOut.x=i.finalPosData.x-i.interPosData.x,i.posOut.y=i.finalPosData.y-i.interPosData.y,i.posIn.opacity=a.isShown?1:n.effectsIn.opacity,i.posOut.opacity=1,i.tweenData.opacity=i.posOut.opacity-i.posIn.opacity,a.isShown||n.config.animation.nudge||(i.posIn.x=i.posOut.x,i.posIn.y=i.posOut.y),i.tweenData.x=i.posOut.x-i.posIn.x,i.tweenData.y=i.posOut.y-i.posIn.y,n.config.animation.animateResizeTargets&&(i.posIn.width=i.startPosData.width,i.posIn.height=i.startPosData.height,l=(i.startPosData.width||i.finalPosData.width)-i.interPosData.width,i.posIn.marginRight=i.startPosData.marginRight-l,c=(i.startPosData.height||i.finalPosData.height)-i.interPosData.height,i.posIn.marginBottom=i.startPosData.marginBottom-c,i.posOut.width=i.finalPosData.width,i.posOut.height=i.finalPosData.height,l=(i.finalPosData.width||i.startPosData.width)-i.interPosData.width,i.posOut.marginRight=i.finalPosData.marginRight-l,c=(i.finalPosData.height||i.startPosData.height)-i.interPosData.height,i.posOut.marginBottom=i.finalPosData.marginBottom-c,i.tweenData.width=i.posOut.width-i.posIn.width,i.tweenData.height=i.posOut.height-i.posIn.height,i.tweenData.marginRight=i.posOut.marginRight-i.posIn.marginRight,i.tweenData.marginBottom=i.posOut.marginBottom-i.posIn.marginBottom),f=0;r=o[f];f++)s=n.effectsIn[r],s instanceof e.TransformData&&s.value&&(i.posIn[r].value=s.value,i.posOut[r].value=0,i.tweenData[r].value=i.posOut[r].value-i.posIn[r].value,i.posIn[r].unit=i.posOut[r].unit=i.tweenData[r].unit=s.unit);for(u=0;a=t.toHide[u];u++)for(i=t.toHidePosData[u],i.posIn=new e.StyleData,i.posOut=new e.StyleData,i.tweenData=new e.StyleData,i.posIn.x=a.isShown?i.startPosData.x-i.interPosData.x:0,i.posIn.y=a.isShown?i.startPosData.y-i.interPosData.y:0,i.posOut.x=n.config.animation.nudge?0:i.posIn.x,i.posOut.y=n.config.animation.nudge?0:i.posIn.y,i.tweenData.x=i.posOut.x-i.posIn.x,i.tweenData.y=i.posOut.y-i.posIn.y,n.config.animation.animateResizeTargets&&(i.posIn.width=i.startPosData.width,i.posIn.height=i.startPosData.height,l=i.startPosData.width-i.interPosData.width,i.posIn.marginRight=i.startPosData.marginRight-l,c=i.startPosData.height-i.interPosData.height,i.posIn.marginBottom=i.startPosData.marginBottom-c),i.posIn.opacity=1,i.posOut.opacity=n.effectsOut.opacity,i.tweenData.opacity=i.posOut.opacity-i.posIn.opacity,f=0;r=o[f];f++)s=n.effectsOut[r],s instanceof e.TransformData&&s.value&&(i.posIn[r].value=0,i.posOut[r].value=s.value,i.tweenData[r].value=i.posOut[r].value-i.posIn[r].value,i.posIn[r].unit=i.posOut[r].unit=i.tweenData[r].unit=s.unit);n.callActions("afterGetTweenData",arguments)},moveTargets:function(t){var a=this,i=null,o=null,r=null,s="",l=!1,c=-1,u=-1,f=a.checkProgress.bind(a);for(a.callActions("beforeMoveTargets",arguments),u=0;i=t.show[u];u++)o=new e.IMoveData,r=t.showPosData[u],s=i.isShown?"none":"show",l=a.willTransition(s,t.hasEffect,r.posIn,r.posOut),l&&c++,i.show(),o.posIn=r.posIn,o.posOut=r.posOut,o.statusChange=s,o.staggerIndex=c,o.operation=t,o.callback=l?f:null,i.move(o);for(u=0;i=t.toHide[u];u++)r=t.toHidePosData[u],o=new e.IMoveData,s="hide",l=a.willTransition(s,r.posIn,r.posOut),o.posIn=r.posIn,o.posOut=r.posOut,o.statusChange=s,o.staggerIndex=u,o.operation=t,o.callback=l?f:null,i.move(o);a.config.animation.animateResizeContainer&&(a.dom.parent.style[e.features.transitionProp]="height "+a.config.animation.duration+"ms ease, width "+a.config.animation.duration+"ms ease ",requestAnimationFrame(function(){t.startHeight!==t.newHeight&&t.viewportDeltaY!==t.startHeight-t.newHeight&&(a.dom.parent.style.height=t.newHeight+"px"),t.startWidth!==t.newWidth&&t.viewportDeltaX!==t.startWidth-t.newWidth&&(a.dom.parent.style.width=t.newWidth+"px")})),t.willChangeLayout&&(n.removeClass(a.dom.container,a.config.layout.ContainerClassName),n.addClass(a.dom.container,t.newContainerClassName)),a.callActions("afterMoveTargets",arguments)},hasEffect:function(){var t=this,e=["scale","translateX","translateY","translateZ","rotateX","rotateY","rotateZ"],n="",a=null,i=!1,o=-1,r=-1;if(1!==t.effectsIn.opacity)return t.callFilters("resultHasEffect",!0,arguments);for(r=0;n=e[r];r++)if(a=t.effectsIn[n],o="undefined"!==a.value?a.value:a,0!==o){i=!0;break}return t.callFilters("resultHasEffect",i,arguments)},willTransition:function(t,e,a,i){var o=this,r=!1;return r=!!n.isVisible(o.dom.container)&&(!!("none"!==t&&e||a.x!==i.x||a.y!==i.y)||!!o.config.animation.animateResizeTargets&&(a.width!==i.width||a.height!==i.height||a.marginRight!==i.marginRight||a.marginTop!==i.marginTop)),o.callFilters("resultWillTransition",r,arguments)},checkProgress:function(t){var e=this;e.targetsDone++,e.targetsBound===e.targetsDone&&e.cleanUp(t)},cleanUp:function(t){var a=this,i=null,o=null,r=null,s=null,l=-1;for(a.callActions("beforeCleanUp",arguments),a.targetsMoved=a.targetsImmovable=a.targetsBound=a.targetsDone=0,l=0;i=t.show[l];l++)i.cleanUp(),i.show();for(l=0;i=t.toHide[l];l++)i.cleanUp(),i.hide();if(t.willSort&&a.printSort(!1,t),a.dom.parent.style[e.features.transitionProp]=a.dom.parent.style.height=a.dom.parent.style.width=a.dom.parent.style.overflow=a.dom.parent.style[e.features.perspectiveProp]=a.dom.parent.style[e.features.perspectiveOriginProp]="",t.willChangeLayout&&(n.removeClass(a.dom.container,t.startContainerClassName),n.addClass(a.dom.container,t.newContainerClassName)),t.toRemove.length){for(l=0;i=a.targets[l];l++)t.toRemove.indexOf(i)>-1&&((o=i.dom.el.previousSibling)&&"#text"===o.nodeName&&(r=i.dom.el.nextSibling)&&"#text"===r.nodeName&&n.removeWhitespace(o),t.willSort||a.dom.parent.removeChild(i.dom.el),a.targets.splice(l,1),i.isInDom=!1,l--);a.origOrder=a.targets}t.willSort&&(a.targets=t.newOrder),a.state=t.newState,a.lastOperation=t,a.dom.targets=a.state.targets,e.events.fire("mixEnd",a.dom.container,{state:a.state,instance:a},a.dom.document),"function"==typeof a.config.callbacks.onMixEnd&&a.config.callbacks.onMixEnd.call(a.dom.container,a.state,a),t.hasFailed&&(e.events.fire("mixFail",a.dom.container,{state:a.state,instance:a},a.dom.document),"function"==typeof a.config.callbacks.onMixFail&&a.config.callbacks.onMixFail.call(a.dom.container,a.state,a),n.addClass(a.dom.container,n.getClassname(a.config.classNames,"container",a.config.classNames.modifierFailed))),"function"==typeof a.userCallback&&a.userCallback.call(a.dom.container,a.state,a),"function"==typeof a.userDeferred.resolve&&a.userDeferred.resolve(a.state),a.userCallback=null,a.userDeferred=null,a.lastClicked=null,a.isToggling=!1,a.isBusy=!1,a.queue.length&&(a.callActions("beforeReadQueueCleanUp",arguments),s=a.queue.shift(),a.userDeferred=s.deferred,a.isToggling=s.isToggling,a.lastClicked=s.triggerElement,s.instruction.command instanceof e.CommandMultimix?a.multimix.apply(a,s.args):a.dataset.apply(a,s.args)),a.callActions("afterCleanUp",arguments)},parseMultimixArgs:function(t){var a=this,i=new e.UserInstruction,o=null,r=-1;for(i.animate=a.config.animation.enable,i.command=new e.CommandMultimix,r=0;r-1?i.command.position=o:"string"==typeof o?i.command.collection=n.arrayFromList(n.createElement(o).childNodes):"object"==typeof o&&n.isElement(o,a.dom.document)?i.command.collection.length?i.command.sibling=o:i.command.collection=[o]:"object"==typeof o&&o.length?i.command.collection.length?i.command.sibling=o[0]:i.command.collection=o:"object"==typeof o&&o.childNodes&&o.childNodes.length?i.command.collection.length?i.command.sibling=o.childNodes[0]:i.command.collection=n.arrayFromList(o.childNodes):"object"==typeof o?n.extend(i.command,o):"boolean"==typeof o?i.animate=o:"function"==typeof o&&(i.callback=o));if(i.command.index&&i.command.sibling)throw new Error(e.messages.errorInsertInvalidArguments());return!i.command.collection.length&&a.config.debug.showWarnings&&console.warn(e.messages.warningInsertNoElements()),i=a.callFilters("instructionParseInsertArgs",i,arguments),n.freeze(i),i},parseRemoveArgs:function(t){var a=this,i=new e.UserInstruction,o=null,r=null,s=-1;for(i.animate=a.config.animation.enable,i.command=new e.CommandRemove,s=0;s-1&&i.command.targets.push(o);return!i.command.targets.length&&a.config.debug.showWarnings&&console.warn(e.messages.warningRemoveNoElements()),n.freeze(i),i},parseDatasetArgs:function(t){var a=this,i=new e.UserInstruction,o=null,r=-1;for(i.animate=a.config.animation.enable,i.command=new e.CommandDataset,r=0;r-1&&t.toggleArray.splice(a,1),i=t.getToggleSelector(),t.multimix({filter:i},e.animate,e.callback)},sort:function(){var t=this,e=t.parseSortArgs(arguments);return t.multimix({sort:e.command},e.animate,e.callback)},changeLayout:function(){var t=this,e=t.parseChangeLayoutArgs(arguments);return t.multimix({changeLayout:e.command},e.animate,e.callback)},dataset:function(){var t=this,n=t.parseDatasetArgs(arguments),a=null,i=null,o=!1;return t.callActions("beforeDataset",arguments),t.isBusy?(i=new e.QueueItem,i.args=arguments,i.instruction=n,t.queueMix(i)):(n.callback&&(t.userCallback=n.callback),o=n.animate^t.config.animation.enable?n.animate:t.config.animation.enable,a=t.getDataOperation(n.command.dataset),t.goMix(o,a))},multimix:function(){var t=this,n=null,a=!1,i=null,o=t.parseMultimixArgs(arguments);return t.callActions("beforeMultimix",arguments),t.isBusy?(i=new e.QueueItem,i.args=arguments,i.instruction=o,i.triggerElement=t.lastClicked,i.isToggling=t.isToggling,t.queueMix(i)):(n=t.getOperation(o.command),t.config.controls.enable&&(o.command.filter&&!t.isToggling&&(t.toggleArray.length=0,t.buildToggleArray(n.command)),t.queue.length<1&&t.updateControls(n.command)),o.callback&&(t.userCallback=o.callback),a=o.animate^t.config.animation.enable?o.animate:t.config.animation.enable,t.callFilters("operationMultimix",n,arguments),t.goMix(a,n))},getOperation:function(t){var a=this,i=t.sort,o=t.filter,r=t.changeLayout,s=t.remove,l=t.insert,c=new e.Operation;return c=a.callFilters("operationUnmappedGetOperation",c,arguments),c.id=n.randomHex(),c.command=t,c.startState=a.state,c.triggerElement=a.lastClicked,a.isBusy?(a.config.debug.showWarnings&&console.warn(e.messages.warningGetOperationInstanceBusy()),null):(l&&a.insertTargets(l,c),s&&(c.toRemove=s.targets),c.startSort=c.newSort=c.startState.activeSort,c.startOrder=c.newOrder=a.targets,i&&(c.startSort=c.startState.activeSort,c.newSort=i,c.willSort=a.willSort(i,c.startState.activeSort),c.willSort&&a.sortOperation(c)),c.startFilter=c.startState.activeFilter,o?c.newFilter=o:c.newFilter=n.extend(new e.CommandFilter,c.startFilter),"all"===c.newFilter.selector?c.newFilter.selector=a.config.selectors.target:"none"===c.newFilter.selector&&(c.newFilter.selector=""),a.filterOperation(c),c.startContainerClassName=c.startState.activeContainerClassName,r?(c.newContainerClassName=r.containerClassName,c.newContainerClassName!==c.startContainerClassName&&(c.willChangeLayout=!0)):c.newContainerClassName=c.startContainerClassName,a.config.animation.enable&&(a.getStartMixData(c),a.setInter(c),c.docState=n.getDocumentState(a.dom.document),a.getInterMixData(c),a.setFinal(c),a.getFinalMixData(c),a.parseEffects(),c.hasEffect=a.hasEffect(),a.getTweenData(c)),c.willSort&&(a.targets=c.newOrder),c.newState=a.buildState(c),a.callFilters("operationMappedGetOperation",c,arguments))},tween:function(t,e){var n=null,a=null,i=-1,o=-1;for(e=Math.min(e,1),e=Math.max(e,0),o=0;n=t.show[o];o++)a=t.showPosData[o],n.applyTween(a,e);for(o=0;n=t.hide[o];o++)n.isShown&&n.hide(),(i=t.toHide.indexOf(n))>-1&&(a=t.toHidePosData[i],n.isShown||n.show(),n.applyTween(a,e))},insert:function(){var t=this,e=t.parseInsertArgs(arguments);return t.multimix({insert:e.command},e.animate,e.callback)},insertBefore:function(){var t=this,e=t.parseInsertArgs(arguments);return t.insert(e.command.collection,"before",e.command.sibling,e.animate,e.callback)},insertAfter:function(){var t=this,e=t.parseInsertArgs(arguments);return t.insert(e.command.collection,"after",e.command.sibling,e.animate,e.callback)},prepend:function(){var t=this,e=t.parseInsertArgs(arguments);return t.insert(0,e.command.collection,e.animate,e.callback)},append:function(){var t=this,e=t.parseInsertArgs(arguments);return t.insert(t.state.totalTargets,e.command.collection,e.animate,e.callback)},remove:function(){var t=this,e=t.parseRemoveArgs(arguments);return t.multimix({remove:e.command},e.animate,e.callback)},getConfig:function(t){var e=this,a=null;return a=t?n.getProperty(e.config,t):e.config,e.callFilters("valueGetConfig",a,arguments)},configure:function(t){var e=this;e.callActions("beforeConfigure",arguments),n.extend(e.config,t,!0,!0),e.callActions("afterConfigure",arguments)},getState:function(){var t=this,a=null;return a=new e.State,n.extend(a,t.state),n.freeze(a),t.callFilters("stateGetState",a,arguments)},forceRefresh:function(){var t=this;t.indexTargets()},forceRender:function(){var t=this,e=null,n=null,a="";for(a in t.cache)e=t.cache[a],n=e.render(e.data),n!==e.dom.el&&(e.isInDom&&(e.unbindEvents(),t.dom.parent.replaceChild(n,e.dom.el)),e.isShown||(n.style.display="none"),e.dom.el=n,e.isInDom&&e.bindEvents());t.state=t.buildState(t.lastOperation)},destroy:function(t){var n=this,a=null,i=null,o=0;for(n.callActions("beforeDestroy",arguments),o=0;a=n.controls[o];o++)a.removeBinding(n);for(o=0;i=n.targets[o];o++)t&&i.show(),i.unbindEvents();n.dom.container.id.match(/^MixItUp/)&&n.dom.container.removeAttribute("id"),delete e.instances[n.id],n.callActions("afterDestroy",arguments)}}),e.IMoveData=function(){e.Base.call(this),this.callActions("beforeConstruct"),this.posIn=null,this.posOut=null,this.operation=null,this.callback=null,this.statusChange="",this.duration=-1,this.staggerIndex=-1,this.callActions("afterConstruct"),n.seal(this)},e.BaseStatic.call(e.IMoveData),e.IMoveData.prototype=Object.create(e.Base.prototype),e.IMoveData.prototype.constructor=e.IMoveData,e.TargetDom=function(){e.Base.call(this),this.callActions("beforeConstruct"),this.el=null,this.callActions("afterConstruct"),n.seal(this)},e.BaseStatic.call(e.TargetDom),e.TargetDom.prototype=Object.create(e.Base.prototype),e.TargetDom.prototype.constructor=e.TargetDom,e.Target=function(){e.Base.call(this),this.callActions("beforeConstruct"),this.id="",this.sortString="",this.mixer=null,this.callback=null,this.isShown=!1,this.isBound=!1,this.isExcluded=!1,this.isInDom=!1,this.handler=null,this.operation=null,this.data=null,this.dom=new e.TargetDom,this.callActions("afterConstruct"),n.seal(this)},e.BaseStatic.call(e.Target),e.Target.prototype=Object.create(e.Base.prototype),n.extend(e.Target.prototype,{constructor:e.Target,init:function(t,n,a){var i=this,o="";if(i.callActions("beforeInit",arguments),i.mixer=n,t||(t=i.render(a)),i.cacheDom(t),i.bindEvents(),"none"!==i.dom.el.style.display&&(i.isShown=!0),a&&n.config.data.uidKey){if("undefined"==typeof(o=a[n.config.data.uidKey])||o.toString().length<1)throw new TypeError(e.messages.errorDatasetInvalidUidKey({uidKey:n.config.data.uidKey}));i.id=o,i.data=a,n.cache[o]=i}i.callActions("afterInit",arguments)},render:function(t){var a=this,i=null,o=null,r=null,s="";if(a.callActions("beforeRender",arguments),i=a.callFilters("renderRender",a.mixer.config.render.target,arguments),"function"!=typeof i)throw new TypeError(e.messages.errorDatasetRendererNotSet());return s=i(t),s&&"object"==typeof s&&n.isElement(s)?o=s:"string"==typeof s&&(r=document.createElement("div"),r.innerHTML=s,o=r.firstElementChild),a.callFilters("elRender",o,arguments)},cacheDom:function(t){var e=this;e.callActions("beforeCacheDom",arguments),e.dom.el=t,e.callActions("afterCacheDom",arguments)},getSortString:function(t){var e=this,n=e.dom.el.getAttribute("data-"+t)||"";e.callActions("beforeGetSortString",arguments),n=isNaN(1*n)?n.toLowerCase():1*n,e.sortString=n,e.callActions("afterGetSortString",arguments)},show:function(){var t=this;t.callActions("beforeShow",arguments),t.isShown||(t.dom.el.style.display="",t.isShown=!0),t.callActions("afterShow",arguments)},hide:function(){var t=this;t.callActions("beforeHide",arguments),t.isShown&&(t.dom.el.style.display="none",t.isShown=!1),t.callActions("afterHide",arguments)},move:function(t){var e=this;e.callActions("beforeMove",arguments),e.isExcluded||e.mixer.targetsMoved++,e.applyStylesIn(t),requestAnimationFrame(function(){e.applyStylesOut(t)}),e.callActions("afterMove",arguments)},applyTween:function(t,n){var a=this,i="",o=null,r=t.posIn,s=[],l=new e.StyleData,c=-1;for(a.callActions("beforeApplyTween",arguments),l.x=r.x,l.y=r.y,0===n?a.hide():a.isShown||a.show(),c=0;i=e.features.TWEENABLE[c];c++)if(o=t.tweenData[i],"x"===i){if(!o)continue;l.x=r.x+o*n}else if("y"===i){if(!o)continue;l.y=r.y+o*n}else if(o instanceof e.TransformData){if(!o.value)continue;l[i].value=r[i].value+o.value*n,l[i].unit=o.unit,s.push(i+"("+l[i].value+o.unit+")")}else{if(!o)continue;l[i]=r[i]+o*n,a.dom.el.style[i]=l[i]}(l.x||l.y)&&s.unshift("translate("+l.x+"px, "+l.y+"px)"),s.length&&(a.dom.el.style[e.features.transformProp]=s.join(" ")),a.callActions("afterApplyTween",arguments)},applyStylesIn:function(t){var n=this,a=t.posIn,i=1!==n.mixer.effectsIn.opacity,o=[];n.callActions("beforeApplyStylesIn",arguments),o.push("translate("+a.x+"px, "+a.y+"px)"),n.mixer.config.animation.animateResizeTargets&&("show"!==t.statusChange&&(n.dom.el.style.width=a.width+"px",n.dom.el.style.height=a.height+"px"),n.dom.el.style.marginRight=a.marginRight+"px",n.dom.el.style.marginBottom=a.marginBottom+"px"),i&&(n.dom.el.style.opacity=a.opacity),"show"===t.statusChange&&(o=o.concat(n.mixer.transformIn)),n.dom.el.style[e.features.transformProp]=o.join(" "),n.callActions("afterApplyStylesIn",arguments)},applyStylesOut:function(t){var n=this,a=[],i=[],o=n.mixer.config.animation.animateResizeTargets,r="undefined"!=typeof n.mixer.effectsIn.opacity;if(n.callActions("beforeApplyStylesOut",arguments),a.push(n.writeTransitionRule(e.features.transformRule,t.staggerIndex)),"none"!==t.statusChange&&a.push(n.writeTransitionRule("opacity",t.staggerIndex,t.duration)),o&&(a.push(n.writeTransitionRule("width",t.staggerIndex,t.duration)),a.push(n.writeTransitionRule("height",t.staggerIndex,t.duration)),a.push(n.writeTransitionRule("margin",t.staggerIndex,t.duration))),!t.callback)return n.mixer.targetsImmovable++,void(n.mixer.targetsMoved===n.mixer.targetsImmovable&&n.mixer.cleanUp(t.operation));switch(n.operation=t.operation,n.callback=t.callback,!n.isExcluded&&n.mixer.targetsBound++,n.isBound=!0,n.applyTransition(a),o&&t.posOut.width>0&&t.posOut.height>0&&(n.dom.el.style.width=t.posOut.width+"px",n.dom.el.style.height=t.posOut.height+"px",n.dom.el.style.marginRight=t.posOut.marginRight+"px",n.dom.el.style.marginBottom=t.posOut.marginBottom+"px"),n.mixer.config.animation.nudge||"hide"!==t.statusChange||i.push("translate("+t.posOut.x+"px, "+t.posOut.y+"px)"),t.statusChange){case"hide":r&&(n.dom.el.style.opacity=n.mixer.effectsOut.opacity),i=i.concat(n.mixer.transformOut);break;case"show":r&&(n.dom.el.style.opacity=1)}(n.mixer.config.animation.nudge||!n.mixer.config.animation.nudge&&"hide"!==t.statusChange)&&i.push("translate("+t.posOut.x+"px, "+t.posOut.y+"px)"),n.dom.el.style[e.features.transformProp]=i.join(" "),n.callActions("afterApplyStylesOut",arguments)},writeTransitionRule:function(t,e,n){var a=this,i=a.getDelay(e),o="";return o=t+" "+(n>0?n:a.mixer.config.animation.duration)+"ms "+i+"ms "+("opacity"===t?"linear":a.mixer.config.animation.easing),a.callFilters("ruleWriteTransitionRule",o,arguments)},getDelay:function(t){var e=this,n=-1;return"function"==typeof e.mixer.config.animation.staggerSequence&&(t=e.mixer.config.animation.staggerSequence.call(e,t,e.state)),n=e.mixer.staggerDuration?t*e.mixer.staggerDuration:0,e.callFilters("delayGetDelay",n,arguments)},applyTransition:function(t){var n=this,a=t.join(", ");n.callActions("beforeApplyTransition",arguments),n.dom.el.style[e.features.transitionProp]=a,n.callActions("afterApplyTransition",arguments)},handleTransitionEnd:function(t){var e=this,n=t.propertyName,a=e.mixer.config.animation.animateResizeTargets;e.callActions("beforeHandleTransitionEnd",arguments),e.isBound&&t.target.matches(e.mixer.config.selectors.target)&&(n.indexOf("transform")>-1||n.indexOf("opacity")>-1||a&&n.indexOf("height")>-1||a&&n.indexOf("width")>-1||a&&n.indexOf("margin")>-1)&&(e.callback.call(e,e.operation),e.isBound=!1,e.callback=null,e.operation=null),e.callActions("afterHandleTransitionEnd",arguments)},eventBus:function(t){var e=this;switch(e.callActions("beforeEventBus",arguments),t.type){case"webkitTransitionEnd":case"transitionend":e.handleTransitionEnd(t)}e.callActions("afterEventBus",arguments)},unbindEvents:function(){var t=this;t.callActions("beforeUnbindEvents",arguments),n.off(t.dom.el,"webkitTransitionEnd",t.handler),n.off(t.dom.el,"transitionend",t.handler),t.callActions("afterUnbindEvents",arguments)},bindEvents:function(){var t=this,a="";t.callActions("beforeBindEvents",arguments),a="webkit"===e.features.transitionPrefix?"webkitTransitionEnd":"transitionend",t.handler=function(e){return t.eventBus(e)},n.on(t.dom.el,a,t.handler),t.callActions("afterBindEvents",arguments)},getPosData:function(n){var a=this,i={},o=null,r=new e.StyleData;return a.callActions("beforeGetPosData",arguments),r.x=a.dom.el.offsetLeft,r.y=a.dom.el.offsetTop,(a.mixer.config.animation.animateResizeTargets||n)&&(o=a.dom.el.getBoundingClientRect(),r.top=o.top,r.right=o.right,r.bottom=o.bottom,r.left=o.left,r.width=o.width,r.height=o.height),a.mixer.config.animation.animateResizeTargets&&(i=t.getComputedStyle(a.dom.el),r.marginBottom=parseFloat(i.marginBottom),r.marginRight=parseFloat(i.marginRight)),a.callFilters("posDataGetPosData",r,arguments)},cleanUp:function(){var t=this;t.callActions("beforeCleanUp",arguments),t.dom.el.style[e.features.transformProp]="",t.dom.el.style[e.features.transitionProp]="",t.dom.el.style.opacity="",t.mixer.config.animation.animateResizeTargets&&(t.dom.el.style.width="",t.dom.el.style.height="",t.dom.el.style.marginRight="",t.dom.el.style.marginBottom=""),t.callActions("afterCleanUp",arguments)}}),e.Collection=function(t){var e=null,a=-1;for(this.callActions("beforeConstruct"),a=0;e=t[a];a++)this[a]=e;this.length=t.length,this.callActions("afterConstruct"),n.freeze(this)},e.BaseStatic.call(e.Collection),e.Collection.prototype=Object.create(e.Base.prototype),n.extend(e.Collection.prototype,{constructor:e.Collection,mixitup:function(t){var a=this,i=null,o=Array.prototype.slice.call(arguments),r=[],s=-1;for(this.callActions("beforeMixitup"),o.shift(),s=0;i=a[s];s++)r.push(i[t].apply(i,o));return a.callFilters("promiseMixitup",n.all(r,e.libraries),arguments)}}),e.Operation=function(){e.Base.call(this),this.callActions("beforeConstruct"),this.id="",this.args=[],this.command=null,this.showPosData=[],this.toHidePosData=[],this.startState=null,this.newState=null,this.docState=null,this.willSort=!1,this.willChangeLayout=!1,this.hasEffect=!1,this.hasFailed=!1,this.triggerElement=null,this.show=[],this.hide=[],this.matching=[],this.toShow=[],this.toHide=[],this.toMove=[],this.toRemove=[],this.startOrder=[],this.newOrder=[],this.startSort=null,this.newSort=null,this.startFilter=null,this.newFilter=null,this.startDataset=null,this.newDataset=null,this.viewportDeltaX=0,this.viewportDeltaY=0,this.startX=0,this.startY=0,this.startHeight=0,this.startWidth=0,this.newX=0,this.newY=0,this.newHeight=0,this.newWidth=0,this.startContainerClassName="",this.startDisplay="",this.newContainerClassName="",this.newDisplay="",this.callActions("afterConstruct"),n.seal(this)},e.BaseStatic.call(e.Operation),e.Operation.prototype=Object.create(e.Base.prototype),e.Operation.prototype.constructor=e.Operation,e.State=function(){e.Base.call(this),this.callActions("beforeConstruct"),this.id="",this.activeFilter=null,this.activeSort=null,this.activeContainerClassName="",this.container=null,this.targets=[],this.hide=[],this.show=[],this.matching=[],this.totalTargets=-1,this.totalShow=-1,this.totalHide=-1,this.totalMatching=-1,this.hasFailed=!1,this.triggerElement=null,this.activeDataset=null,this.callActions("afterConstruct"),n.seal(this)},e.BaseStatic.call(e.State),e.State.prototype=Object.create(e.Base.prototype),e.State.prototype.constructor=e.State,e.UserInstruction=function(){e.Base.call(this),this.callActions("beforeConstruct"),this.command={},this.animate=!1,this.callback=null,this.callActions("afterConstruct"),n.seal(this)},e.BaseStatic.call(e.UserInstruction),e.UserInstruction.prototype=Object.create(e.Base.prototype),e.UserInstruction.prototype.constructor=e.UserInstruction,e.Messages=function(){e.Base.call(this),this.callActions("beforeConstruct"),this.ERROR_FACTORY_INVALID_CONTAINER="[MixItUp] An invalid selector or element reference was passed to the mixitup factory function",this.ERROR_FACTORY_CONTAINER_NOT_FOUND="[MixItUp] The provided selector yielded no container element",this.ERROR_CONFIG_INVALID_ANIMATION_EFFECTS="[MixItUp] Invalid value for `animation.effects`",this.ERROR_CONFIG_INVALID_CONTROLS_SCOPE="[MixItUp] Invalid value for `controls.scope`",this.ERROR_CONFIG_INVALID_PROPERTY='[MixitUp] Invalid configuration object property "${erroneous}"${suggestion}',this.ERROR_CONFIG_INVALID_PROPERTY_SUGGESTION='. Did you mean "${probableMatch}"?',this.ERROR_CONFIG_DATA_UID_KEY_NOT_SET="[MixItUp] To use the dataset API, a UID key must be specified using `data.uidKey`",this.ERROR_DATASET_INVALID_UID_KEY='[MixItUp] The specified UID key "${uidKey}" is not present on one or more dataset items',this.ERROR_DATASET_DUPLICATE_UID='[MixItUp] The UID "${uid}" was found on two or more dataset items. UIDs must be unique.',this.ERROR_INSERT_INVALID_ARGUMENTS="[MixItUp] Please provider either an index or a sibling and position to insert, not both",this.ERROR_INSERT_PREEXISTING_ELEMENT="[MixItUp] An element to be inserted already exists in the container",this.ERROR_FILTER_INVALID_ARGUMENTS="[MixItUp] Please provide either a selector or collection `.filter()`, not both",this.ERROR_DATASET_NOT_SET="[MixItUp] To use the dataset API with pre-rendered targets, a starting dataset must be set using `load.dataset`",this.ERROR_DATASET_PRERENDERED_MISMATCH="[MixItUp] `load.dataset` does not match pre-rendered targets",this.ERROR_DATASET_RENDERER_NOT_SET="[MixItUp] To insert an element via the dataset API, a target renderer function must be provided to `render.target`",this.ERROR_SORT_NON_EXISTENT_ELEMENT="[MixItUp] An element to be sorted does not already exist in the container",this.WARNING_FACTORY_PREEXISTING_INSTANCE="[MixItUp] WARNING: This element already has an active MixItUp instance. The provided configuration object will be ignored. If you wish to perform additional methods on this instance, please create a reference.",this.WARNING_INSERT_NO_ELEMENTS="[MixItUp] WARNING: No valid elements were passed to `.insert()`",this.WARNING_REMOVE_NO_ELEMENTS="[MixItUp] WARNING: No valid elements were passed to `.remove()`",this.WARNING_MULTIMIX_INSTANCE_QUEUE_FULL="[MixItUp] WARNING: An operation was requested but the MixItUp instance was busy. The operation was rejected because the queue is full or queuing is disabled.",this.WARNING_GET_OPERATION_INSTANCE_BUSY="[MixItUp] WARNING: Operations can be be created while the MixItUp instance is busy.",this.WARNING_NO_PROMISE_IMPLEMENTATION="[MixItUp] WARNING: No Promise implementations could be found. If you wish to use promises with MixItUp please install an ES6 Promise polyfill.",this.WARNING_INCONSISTENT_SORTING_ATTRIBUTES='[MixItUp] WARNING: The requested sorting data attribute "${attribute}" was not present on one or more target elements which may product unexpected sort output',this.callActions("afterConstruct"),this.compileTemplates(),n.seal(this)},e.BaseStatic.call(e.Messages),e.Messages.prototype=Object.create(e.Base.prototype),e.Messages.prototype.constructor=e.Messages,e.Messages.prototype.compileTemplates=function(){var t="",e="";for(t in this)"string"==typeof(e=this[t])&&(this[n.camelCase(t)]=n.template(e))},e.messages=new e.Messages,e.Facade=function(t){e.Base.call(this),this.callActions("beforeConstruct",arguments),this.configure=t.configure.bind(t),this.show=t.show.bind(t),this.hide=t.hide.bind(t),this.filter=t.filter.bind(t),this.toggleOn=t.toggleOn.bind(t),this.toggleOff=t.toggleOff.bind(t),this.sort=t.sort.bind(t),this.changeLayout=t.changeLayout.bind(t),this.multimix=t.multimix.bind(t),this.dataset=t.dataset.bind(t),this.tween=t.tween.bind(t),this.insert=t.insert.bind(t),this.insertBefore=t.insertBefore.bind(t),this.insertAfter=t.insertAfter.bind(t),this.prepend=t.prepend.bind(t),this.append=t.append.bind(t),this.remove=t.remove.bind(t),this.destroy=t.destroy.bind(t),this.forceRefresh=t.forceRefresh.bind(t),this.forceRender=t.forceRender.bind(t),this.isMixing=t.isMixing.bind(t),this.getOperation=t.getOperation.bind(t),this.getConfig=t.getConfig.bind(t),this.getState=t.getState.bind(t),this.callActions("afterConstruct",arguments),n.freeze(this),n.seal(this)},e.BaseStatic.call(e.Facade),e.Facade.prototype=Object.create(e.Base.prototype),e.Facade.prototype.constructor=e.Facade,"object"==typeof exports&&"object"==typeof module?module.exports=e:"function"==typeof define&&define.amd?define(function(){return e}):"undefined"!=typeof t.mixitup&&"function"==typeof t.mixitup||(t.mixitup=e),e.BaseStatic.call(e.constructor),e.NAME="mixitup",e.CORE_VERSION="3.3.1"}(window);
\ No newline at end of file
diff --git a/themes/30coffe/assets/js/owl.carousel.min.js b/themes/30coffe/assets/js/owl.carousel.min.js
new file mode 100644
index 00000000..fbbffc53
--- /dev/null
+++ b/themes/30coffe/assets/js/owl.carousel.min.js
@@ -0,0 +1,7 @@
+/**
+ * Owl Carousel v2.3.4
+ * Copyright 2013-2018 David Deutsch
+ * Licensed under: SEE LICENSE IN https://github.com/OwlCarousel2/OwlCarousel2/blob/master/LICENSE
+ */
+!function(a,b,c,d){function e(b,c){this.settings=null,this.options=a.extend({},e.Defaults,c),this.$element=a(b),this._handlers={},this._plugins={},this._supress={},this._current=null,this._speed=null,this._coordinates=[],this._breakpoint=null,this._width=null,this._items=[],this._clones=[],this._mergers=[],this._widths=[],this._invalidated={},this._pipe=[],this._drag={time:null,target:null,pointer:null,stage:{start:null,current:null},direction:null},this._states={current:{},tags:{initializing:["busy"],animating:["busy"],dragging:["interacting"]}},a.each(["onResize","onThrottledResize"],a.proxy(function(b,c){this._handlers[c]=a.proxy(this[c],this)},this)),a.each(e.Plugins,a.proxy(function(a,b){this._plugins[a.charAt(0).toLowerCase()+a.slice(1)]=new b(this)},this)),a.each(e.Workers,a.proxy(function(b,c){this._pipe.push({filter:c.filter,run:a.proxy(c.run,this)})},this)),this.setup(),this.initialize()}e.Defaults={items:3,loop:!1,center:!1,rewind:!1,checkVisibility:!0,mouseDrag:!0,touchDrag:!0,pullDrag:!0,freeDrag:!1,margin:0,stagePadding:0,merge:!1,mergeFit:!0,autoWidth:!1,startPosition:0,rtl:!1,smartSpeed:250,fluidSpeed:!1,dragEndSpeed:!1,responsive:{},responsiveRefreshRate:200,responsiveBaseElement:b,fallbackEasing:"swing",slideTransition:"",info:!1,nestedItemSelector:!1,itemElement:"div",stageElement:"div",refreshClass:"owl-refresh",loadedClass:"owl-loaded",loadingClass:"owl-loading",rtlClass:"owl-rtl",responsiveClass:"owl-responsive",dragClass:"owl-drag",itemClass:"owl-item",stageClass:"owl-stage",stageOuterClass:"owl-stage-outer",grabClass:"owl-grab"},e.Width={Default:"default",Inner:"inner",Outer:"outer"},e.Type={Event:"event",State:"state"},e.Plugins={},e.Workers=[{filter:["width","settings"],run:function(){this._width=this.$element.width()}},{filter:["width","items","settings"],run:function(a){a.current=this._items&&this._items[this.relative(this._current)]}},{filter:["items","settings"],run:function(){this.$stage.children(".cloned").remove()}},{filter:["width","items","settings"],run:function(a){var b=this.settings.margin||"",c=!this.settings.autoWidth,d=this.settings.rtl,e={width:"auto","margin-left":d?b:"","margin-right":d?"":b};!c&&this.$stage.children().css(e),a.css=e}},{filter:["width","items","settings"],run:function(a){var b=(this.width()/this.settings.items).toFixed(3)-this.settings.margin,c=null,d=this._items.length,e=!this.settings.autoWidth,f=[];for(a.items={merge:!1,width:b};d--;)c=this._mergers[d],c=this.settings.mergeFit&&Math.min(c,this.settings.items)||c,a.items.merge=c>1||a.items.merge,f[d]=e?b*c:this._items[d].width();this._widths=f}},{filter:["items","settings"],run:function(){var b=[],c=this._items,d=this.settings,e=Math.max(2*d.items,4),f=2*Math.ceil(c.length/2),g=d.loop&&c.length?d.rewind?e:Math.max(e,f):0,h="",i="";for(g/=2;g>0;)b.push(this.normalize(b.length/2,!0)),h+=c[b[b.length-1]][0].outerHTML,b.push(this.normalize(c.length-1-(b.length-1)/2,!0)),i=c[b[b.length-1]][0].outerHTML+i,g-=1;this._clones=b,a(h).addClass("cloned").appendTo(this.$stage),a(i).addClass("cloned").prependTo(this.$stage)}},{filter:["width","items","settings"],run:function(){for(var a=this.settings.rtl?1:-1,b=this._clones.length+this._items.length,c=-1,d=0,e=0,f=[];++c",h)||this.op(b,"<",g)&&this.op(b,">",h))&&i.push(c);this.$stage.children(".active").removeClass("active"),this.$stage.children(":eq("+i.join("), :eq(")+")").addClass("active"),this.$stage.children(".center").removeClass("center"),this.settings.center&&this.$stage.children().eq(this.current()).addClass("center")}}],e.prototype.initializeStage=function(){this.$stage=this.$element.find("."+this.settings.stageClass),this.$stage.length||(this.$element.addClass(this.options.loadingClass),this.$stage=a("<"+this.settings.stageElement+">",{class:this.settings.stageClass}).wrap(a("
",{class:this.settings.stageOuterClass})),this.$element.append(this.$stage.parent()))},e.prototype.initializeItems=function(){var b=this.$element.find(".owl-item");if(b.length)return this._items=b.get().map(function(b){return a(b)}),this._mergers=this._items.map(function(){return 1}),void this.refresh();this.replace(this.$element.children().not(this.$stage.parent())),this.isVisible()?this.refresh():this.invalidate("width"),this.$element.removeClass(this.options.loadingClass).addClass(this.options.loadedClass)},e.prototype.initialize=function(){if(this.enter("initializing"),this.trigger("initialize"),this.$element.toggleClass(this.settings.rtlClass,this.settings.rtl),this.settings.autoWidth&&!this.is("pre-loading")){var a,b,c;a=this.$element.find("img"),b=this.settings.nestedItemSelector?"."+this.settings.nestedItemSelector:d,c=this.$element.children(b).width(),a.length&&c<=0&&this.preloadAutoWidthImages(a)}this.initializeStage(),this.initializeItems(),this.registerEventHandlers(),this.leave("initializing"),this.trigger("initialized")},e.prototype.isVisible=function(){return!this.settings.checkVisibility||this.$element.is(":visible")},e.prototype.setup=function(){var b=this.viewport(),c=this.options.responsive,d=-1,e=null;c?(a.each(c,function(a){a<=b&&a>d&&(d=Number(a))}),e=a.extend({},this.options,c[d]),"function"==typeof e.stagePadding&&(e.stagePadding=e.stagePadding()),delete e.responsive,e.responsiveClass&&this.$element.attr("class",this.$element.attr("class").replace(new RegExp("("+this.options.responsiveClass+"-)\\S+\\s","g"),"$1"+d))):e=a.extend({},this.options),this.trigger("change",{property:{name:"settings",value:e}}),this._breakpoint=d,this.settings=e,this.invalidate("settings"),this.trigger("changed",{property:{name:"settings",value:this.settings}})},e.prototype.optionsLogic=function(){this.settings.autoWidth&&(this.settings.stagePadding=!1,this.settings.merge=!1)},e.prototype.prepare=function(b){var c=this.trigger("prepare",{content:b});return c.data||(c.data=a("<"+this.settings.itemElement+"/>").addClass(this.options.itemClass).append(b)),this.trigger("prepared",{content:c.data}),c.data},e.prototype.update=function(){for(var b=0,c=this._pipe.length,d=a.proxy(function(a){return this[a]},this._invalidated),e={};b0)&&this._pipe[b].run(e),b++;this._invalidated={},!this.is("valid")&&this.enter("valid")},e.prototype.width=function(a){switch(a=a||e.Width.Default){case e.Width.Inner:case e.Width.Outer:return this._width;default:return this._width-2*this.settings.stagePadding+this.settings.margin}},e.prototype.refresh=function(){this.enter("refreshing"),this.trigger("refresh"),this.setup(),this.optionsLogic(),this.$element.addClass(this.options.refreshClass),this.update(),this.$element.removeClass(this.options.refreshClass),this.leave("refreshing"),this.trigger("refreshed")},e.prototype.onThrottledResize=function(){b.clearTimeout(this.resizeTimer),this.resizeTimer=b.setTimeout(this._handlers.onResize,this.settings.responsiveRefreshRate)},e.prototype.onResize=function(){return!!this._items.length&&(this._width!==this.$element.width()&&(!!this.isVisible()&&(this.enter("resizing"),this.trigger("resize").isDefaultPrevented()?(this.leave("resizing"),!1):(this.invalidate("width"),this.refresh(),this.leave("resizing"),void this.trigger("resized")))))},e.prototype.registerEventHandlers=function(){a.support.transition&&this.$stage.on(a.support.transition.end+".owl.core",a.proxy(this.onTransitionEnd,this)),!1!==this.settings.responsive&&this.on(b,"resize",this._handlers.onThrottledResize),this.settings.mouseDrag&&(this.$element.addClass(this.options.dragClass),this.$stage.on("mousedown.owl.core",a.proxy(this.onDragStart,this)),this.$stage.on("dragstart.owl.core selectstart.owl.core",function(){return!1})),this.settings.touchDrag&&(this.$stage.on("touchstart.owl.core",a.proxy(this.onDragStart,this)),this.$stage.on("touchcancel.owl.core",a.proxy(this.onDragEnd,this)))},e.prototype.onDragStart=function(b){var d=null;3!==b.which&&(a.support.transform?(d=this.$stage.css("transform").replace(/.*\(|\)| /g,"").split(","),d={x:d[16===d.length?12:4],y:d[16===d.length?13:5]}):(d=this.$stage.position(),d={x:this.settings.rtl?d.left+this.$stage.width()-this.width()+this.settings.margin:d.left,y:d.top}),this.is("animating")&&(a.support.transform?this.animate(d.x):this.$stage.stop(),this.invalidate("position")),this.$element.toggleClass(this.options.grabClass,"mousedown"===b.type),this.speed(0),this._drag.time=(new Date).getTime(),this._drag.target=a(b.target),this._drag.stage.start=d,this._drag.stage.current=d,this._drag.pointer=this.pointer(b),a(c).on("mouseup.owl.core touchend.owl.core",a.proxy(this.onDragEnd,this)),a(c).one("mousemove.owl.core touchmove.owl.core",a.proxy(function(b){var d=this.difference(this._drag.pointer,this.pointer(b));a(c).on("mousemove.owl.core touchmove.owl.core",a.proxy(this.onDragMove,this)),Math.abs(d.x)0^this.settings.rtl?"left":"right";a(c).off(".owl.core"),this.$element.removeClass(this.options.grabClass),(0!==d.x&&this.is("dragging")||!this.is("valid"))&&(this.speed(this.settings.dragEndSpeed||this.settings.smartSpeed),this.current(this.closest(e.x,0!==d.x?f:this._drag.direction)),this.invalidate("position"),this.update(),this._drag.direction=f,(Math.abs(d.x)>3||(new Date).getTime()-this._drag.time>300)&&this._drag.target.one("click.owl.core",function(){return!1})),this.is("dragging")&&(this.leave("dragging"),this.trigger("dragged"))},e.prototype.closest=function(b,c){var e=-1,f=30,g=this.width(),h=this.coordinates();return this.settings.freeDrag||a.each(h,a.proxy(function(a,i){return"left"===c&&b>i-f&&bi-g-f&&b",h[a+1]!==d?h[a+1]:i-g)&&(e="left"===c?a+1:a),-1===e},this)),this.settings.loop||(this.op(b,">",h[this.minimum()])?e=b=this.minimum():this.op(b,"<",h[this.maximum()])&&(e=b=this.maximum())),e},e.prototype.animate=function(b){var c=this.speed()>0;this.is("animating")&&this.onTransitionEnd(),c&&(this.enter("animating"),this.trigger("translate")),a.support.transform3d&&a.support.transition?this.$stage.css({transform:"translate3d("+b+"px,0px,0px)",transition:this.speed()/1e3+"s"+(this.settings.slideTransition?" "+this.settings.slideTransition:"")}):c?this.$stage.animate({left:b+"px"},this.speed(),this.settings.fallbackEasing,a.proxy(this.onTransitionEnd,this)):this.$stage.css({left:b+"px"})},e.prototype.is=function(a){return this._states.current[a]&&this._states.current[a]>0},e.prototype.current=function(a){if(a===d)return this._current;if(0===this._items.length)return d;if(a=this.normalize(a),this._current!==a){var b=this.trigger("change",{property:{name:"position",value:a}});b.data!==d&&(a=this.normalize(b.data)),this._current=a,this.invalidate("position"),this.trigger("changed",{property:{name:"position",value:this._current}})}return this._current},e.prototype.invalidate=function(b){return"string"===a.type(b)&&(this._invalidated[b]=!0,this.is("valid")&&this.leave("valid")),a.map(this._invalidated,function(a,b){return b})},e.prototype.reset=function(a){(a=this.normalize(a))!==d&&(this._speed=0,this._current=a,this.suppress(["translate","translated"]),this.animate(this.coordinates(a)),this.release(["translate","translated"]))},e.prototype.normalize=function(a,b){var c=this._items.length,e=b?0:this._clones.length;return!this.isNumeric(a)||c<1?a=d:(a<0||a>=c+e)&&(a=((a-e/2)%c+c)%c+e/2),a},e.prototype.relative=function(a){return a-=this._clones.length/2,this.normalize(a,!0)},e.prototype.maximum=function(a){var b,c,d,e=this.settings,f=this._coordinates.length;if(e.loop)f=this._clones.length/2+this._items.length-1;else if(e.autoWidth||e.merge){if(b=this._items.length)for(c=this._items[--b].width(),d=this.$element.width();b--&&!((c+=this._items[b].width()+this.settings.margin)>d););f=b+1}else f=e.center?this._items.length-1:this._items.length-e.items;return a&&(f-=this._clones.length/2),Math.max(f,0)},e.prototype.minimum=function(a){return a?0:this._clones.length/2},e.prototype.items=function(a){return a===d?this._items.slice():(a=this.normalize(a,!0),this._items[a])},e.prototype.mergers=function(a){return a===d?this._mergers.slice():(a=this.normalize(a,!0),this._mergers[a])},e.prototype.clones=function(b){var c=this._clones.length/2,e=c+this._items.length,f=function(a){return a%2==0?e+a/2:c-(a+1)/2};return b===d?a.map(this._clones,function(a,b){return f(b)}):a.map(this._clones,function(a,c){return a===b?f(c):null})},e.prototype.speed=function(a){return a!==d&&(this._speed=a),this._speed},e.prototype.coordinates=function(b){var c,e=1,f=b-1;return b===d?a.map(this._coordinates,a.proxy(function(a,b){return this.coordinates(b)},this)):(this.settings.center?(this.settings.rtl&&(e=-1,f=b+1),c=this._coordinates[b],c+=(this.width()-c+(this._coordinates[f]||0))/2*e):c=this._coordinates[f]||0,c=Math.ceil(c))},e.prototype.duration=function(a,b,c){return 0===c?0:Math.min(Math.max(Math.abs(b-a),1),6)*Math.abs(c||this.settings.smartSpeed)},e.prototype.to=function(a,b){var c=this.current(),d=null,e=a-this.relative(c),f=(e>0)-(e<0),g=this._items.length,h=this.minimum(),i=this.maximum();this.settings.loop?(!this.settings.rewind&&Math.abs(e)>g/2&&(e+=-1*f*g),a=c+e,(d=((a-h)%g+g)%g+h)!==a&&d-e<=i&&d-e>0&&(c=d-e,a=d,this.reset(c))):this.settings.rewind?(i+=1,a=(a%i+i)%i):a=Math.max(h,Math.min(i,a)),this.speed(this.duration(c,a,b)),this.current(a),this.isVisible()&&this.update()},e.prototype.next=function(a){a=a||!1,this.to(this.relative(this.current())+1,a)},e.prototype.prev=function(a){a=a||!1,this.to(this.relative(this.current())-1,a)},e.prototype.onTransitionEnd=function(a){if(a!==d&&(a.stopPropagation(),(a.target||a.srcElement||a.originalTarget)!==this.$stage.get(0)))return!1;this.leave("animating"),this.trigger("translated")},e.prototype.viewport=function(){var d;return this.options.responsiveBaseElement!==b?d=a(this.options.responsiveBaseElement).width():b.innerWidth?d=b.innerWidth:c.documentElement&&c.documentElement.clientWidth?d=c.documentElement.clientWidth:console.warn("Can not detect viewport width."),d},e.prototype.replace=function(b){this.$stage.empty(),this._items=[],b&&(b=b instanceof jQuery?b:a(b)),this.settings.nestedItemSelector&&(b=b.find("."+this.settings.nestedItemSelector)),b.filter(function(){return 1===this.nodeType}).each(a.proxy(function(a,b){b=this.prepare(b),this.$stage.append(b),this._items.push(b),this._mergers.push(1*b.find("[data-merge]").addBack("[data-merge]").attr("data-merge")||1)},this)),this.reset(this.isNumeric(this.settings.startPosition)?this.settings.startPosition:0),this.invalidate("items")},e.prototype.add=function(b,c){var e=this.relative(this._current);c=c===d?this._items.length:this.normalize(c,!0),b=b instanceof jQuery?b:a(b),this.trigger("add",{content:b,position:c}),b=this.prepare(b),0===this._items.length||c===this._items.length?(0===this._items.length&&this.$stage.append(b),0!==this._items.length&&this._items[c-1].after(b),this._items.push(b),this._mergers.push(1*b.find("[data-merge]").addBack("[data-merge]").attr("data-merge")||1)):(this._items[c].before(b),this._items.splice(c,0,b),this._mergers.splice(c,0,1*b.find("[data-merge]").addBack("[data-merge]").attr("data-merge")||1)),this._items[e]&&this.reset(this._items[e].index()),this.invalidate("items"),this.trigger("added",{content:b,position:c})},e.prototype.remove=function(a){(a=this.normalize(a,!0))!==d&&(this.trigger("remove",{content:this._items[a],position:a}),this._items[a].remove(),this._items.splice(a,1),this._mergers.splice(a,1),this.invalidate("items"),this.trigger("removed",{content:null,position:a}))},e.prototype.preloadAutoWidthImages=function(b){b.each(a.proxy(function(b,c){this.enter("pre-loading"),c=a(c),a(new Image).one("load",a.proxy(function(a){c.attr("src",a.target.src),c.css("opacity",1),this.leave("pre-loading"),!this.is("pre-loading")&&!this.is("initializing")&&this.refresh()},this)).attr("src",c.attr("src")||c.attr("data-src")||c.attr("data-src-retina"))},this))},e.prototype.destroy=function(){this.$element.off(".owl.core"),this.$stage.off(".owl.core"),a(c).off(".owl.core"),!1!==this.settings.responsive&&(b.clearTimeout(this.resizeTimer),this.off(b,"resize",this._handlers.onThrottledResize));for(var d in this._plugins)this._plugins[d].destroy();this.$stage.children(".cloned").remove(),this.$stage.unwrap(),this.$stage.children().contents().unwrap(),this.$stage.children().unwrap(),this.$stage.remove(),this.$element.removeClass(this.options.refreshClass).removeClass(this.options.loadingClass).removeClass(this.options.loadedClass).removeClass(this.options.rtlClass).removeClass(this.options.dragClass).removeClass(this.options.grabClass).attr("class",this.$element.attr("class").replace(new RegExp(this.options.responsiveClass+"-\\S+\\s","g"),"")).removeData("owl.carousel")},e.prototype.op=function(a,b,c){var d=this.settings.rtl;switch(b){case"<":return d?a>c:a":return d?ac;case">=":return d?a<=c:a>=c;case"<=":return d?a>=c:a<=c}},e.prototype.on=function(a,b,c,d){a.addEventListener?a.addEventListener(b,c,d):a.attachEvent&&a.attachEvent("on"+b,c)},e.prototype.off=function(a,b,c,d){a.removeEventListener?a.removeEventListener(b,c,d):a.detachEvent&&a.detachEvent("on"+b,c)},e.prototype.trigger=function(b,c,d,f,g){var h={item:{count:this._items.length,index:this.current()}},i=a.camelCase(a.grep(["on",b,d],function(a){return a}).join("-").toLowerCase()),j=a.Event([b,"owl",d||"carousel"].join(".").toLowerCase(),a.extend({relatedTarget:this},h,c));return this._supress[b]||(a.each(this._plugins,function(a,b){b.onTrigger&&b.onTrigger(j)}),this.register({type:e.Type.Event,name:b}),this.$element.trigger(j),this.settings&&"function"==typeof this.settings[i]&&this.settings[i].call(this,j)),j},e.prototype.enter=function(b){a.each([b].concat(this._states.tags[b]||[]),a.proxy(function(a,b){this._states.current[b]===d&&(this._states.current[b]=0),this._states.current[b]++},this))},e.prototype.leave=function(b){a.each([b].concat(this._states.tags[b]||[]),a.proxy(function(a,b){this._states.current[b]--},this))},e.prototype.register=function(b){if(b.type===e.Type.Event){if(a.event.special[b.name]||(a.event.special[b.name]={}),!a.event.special[b.name].owl){var c=a.event.special[b.name]._default;a.event.special[b.name]._default=function(a){return!c||!c.apply||a.namespace&&-1!==a.namespace.indexOf("owl")?a.namespace&&a.namespace.indexOf("owl")>-1:c.apply(this,arguments)},a.event.special[b.name].owl=!0}}else b.type===e.Type.State&&(this._states.tags[b.name]?this._states.tags[b.name]=this._states.tags[b.name].concat(b.tags):this._states.tags[b.name]=b.tags,this._states.tags[b.name]=a.grep(this._states.tags[b.name],a.proxy(function(c,d){return a.inArray(c,this._states.tags[b.name])===d},this)))},e.prototype.suppress=function(b){a.each(b,a.proxy(function(a,b){this._supress[b]=!0},this))},e.prototype.release=function(b){a.each(b,a.proxy(function(a,b){delete this._supress[b]},this))},e.prototype.pointer=function(a){var c={x:null,y:null};return a=a.originalEvent||a||b.event,a=a.touches&&a.touches.length?a.touches[0]:a.changedTouches&&a.changedTouches.length?a.changedTouches[0]:a,a.pageX?(c.x=a.pageX,c.y=a.pageY):(c.x=a.clientX,c.y=a.clientY),c},e.prototype.isNumeric=function(a){return!isNaN(parseFloat(a))},e.prototype.difference=function(a,b){return{x:a.x-b.x,y:a.y-b.y}},a.fn.owlCarousel=function(b){var c=Array.prototype.slice.call(arguments,1);return this.each(function(){var d=a(this),f=d.data("owl.carousel");f||(f=new e(this,"object"==typeof b&&b),d.data("owl.carousel",f),a.each(["next","prev","to","destroy","refresh","replace","add","remove"],function(b,c){f.register({type:e.Type.Event,name:c}),f.$element.on(c+".owl.carousel.core",a.proxy(function(a){a.namespace&&a.relatedTarget!==this&&(this.suppress([c]),f[c].apply(this,[].slice.call(arguments,1)),this.release([c]))},f))})),"string"==typeof b&&"_"!==b.charAt(0)&&f[b].apply(f,c)})},a.fn.owlCarousel.Constructor=e}(window.Zepto||window.jQuery,window,document),function(a,b,c,d){var e=function(b){this._core=b,this._interval=null,this._visible=null,this._handlers={"initialized.owl.carousel":a.proxy(function(a){a.namespace&&this._core.settings.autoRefresh&&this.watch()},this)},this._core.options=a.extend({},e.Defaults,this._core.options),this._core.$element.on(this._handlers)};e.Defaults={autoRefresh:!0,autoRefreshInterval:500},e.prototype.watch=function(){this._interval||(this._visible=this._core.isVisible(),this._interval=b.setInterval(a.proxy(this.refresh,this),this._core.settings.autoRefreshInterval))},e.prototype.refresh=function(){this._core.isVisible()!==this._visible&&(this._visible=!this._visible,this._core.$element.toggleClass("owl-hidden",!this._visible),this._visible&&this._core.invalidate("width")&&this._core.refresh())},e.prototype.destroy=function(){var a,c;b.clearInterval(this._interval);for(a in this._handlers)this._core.$element.off(a,this._handlers[a]);for(c in Object.getOwnPropertyNames(this))"function"!=typeof this[c]&&(this[c]=null)},a.fn.owlCarousel.Constructor.Plugins.AutoRefresh=e}(window.Zepto||window.jQuery,window,document),function(a,b,c,d){var e=function(b){this._core=b,this._loaded=[],this._handlers={"initialized.owl.carousel change.owl.carousel resized.owl.carousel":a.proxy(function(b){if(b.namespace&&this._core.settings&&this._core.settings.lazyLoad&&(b.property&&"position"==b.property.name||"initialized"==b.type)){var c=this._core.settings,e=c.center&&Math.ceil(c.items/2)||c.items,f=c.center&&-1*e||0,g=(b.property&&b.property.value!==d?b.property.value:this._core.current())+f,h=this._core.clones().length,i=a.proxy(function(a,b){this.load(b)},this);for(c.lazyLoadEager>0&&(e+=c.lazyLoadEager,c.loop&&(g-=c.lazyLoadEager,e++));f++-1||(e.each(a.proxy(function(c,d){var e,f=a(d),g=b.devicePixelRatio>1&&f.attr("data-src-retina")||f.attr("data-src")||f.attr("data-srcset");this._core.trigger("load",{element:f,url:g},"lazy"),f.is("img")?f.one("load.owl.lazy",a.proxy(function(){f.css("opacity",1),this._core.trigger("loaded",{element:f,url:g},"lazy")},this)).attr("src",g):f.is("source")?f.one("load.owl.lazy",a.proxy(function(){this._core.trigger("loaded",{element:f,url:g},"lazy")},this)).attr("srcset",g):(e=new Image,e.onload=a.proxy(function(){f.css({"background-image":'url("'+g+'")',opacity:"1"}),this._core.trigger("loaded",{element:f,url:g},"lazy")},this),e.src=g)},this)),this._loaded.push(d.get(0)))},e.prototype.destroy=function(){var a,b;for(a in this.handlers)this._core.$element.off(a,this.handlers[a]);for(b in Object.getOwnPropertyNames(this))"function"!=typeof this[b]&&(this[b]=null)},a.fn.owlCarousel.Constructor.Plugins.Lazy=e}(window.Zepto||window.jQuery,window,document),function(a,b,c,d){var e=function(c){this._core=c,this._previousHeight=null,this._handlers={"initialized.owl.carousel refreshed.owl.carousel":a.proxy(function(a){a.namespace&&this._core.settings.autoHeight&&this.update()},this),"changed.owl.carousel":a.proxy(function(a){a.namespace&&this._core.settings.autoHeight&&"position"===a.property.name&&this.update()},this),"loaded.owl.lazy":a.proxy(function(a){a.namespace&&this._core.settings.autoHeight&&a.element.closest("."+this._core.settings.itemClass).index()===this._core.current()&&this.update()},this)},this._core.options=a.extend({},e.Defaults,this._core.options),this._core.$element.on(this._handlers),this._intervalId=null;var d=this;a(b).on("load",function(){d._core.settings.autoHeight&&d.update()}),a(b).resize(function(){d._core.settings.autoHeight&&(null!=d._intervalId&&clearTimeout(d._intervalId),d._intervalId=setTimeout(function(){d.update()},250))})};e.Defaults={autoHeight:!1,autoHeightClass:"owl-height"},e.prototype.update=function(){var b=this._core._current,c=b+this._core.settings.items,d=this._core.settings.lazyLoad,e=this._core.$stage.children().toArray().slice(b,c),f=[],g=0;a.each(e,function(b,c){f.push(a(c).height())}),g=Math.max.apply(null,f),g<=1&&d&&this._previousHeight&&(g=this._previousHeight),this._previousHeight=g,this._core.$stage.parent().height(g).addClass(this._core.settings.autoHeightClass)},e.prototype.destroy=function(){var a,b;for(a in this._handlers)this._core.$element.off(a,this._handlers[a]);for(b in Object.getOwnPropertyNames(this))"function"!=typeof this[b]&&(this[b]=null)},a.fn.owlCarousel.Constructor.Plugins.AutoHeight=e}(window.Zepto||window.jQuery,window,document),function(a,b,c,d){var e=function(b){this._core=b,this._videos={},this._playing=null,this._handlers={"initialized.owl.carousel":a.proxy(function(a){a.namespace&&this._core.register({type:"state",name:"playing",tags:["interacting"]})},this),"resize.owl.carousel":a.proxy(function(a){a.namespace&&this._core.settings.video&&this.isInFullScreen()&&a.preventDefault()},this),"refreshed.owl.carousel":a.proxy(function(a){a.namespace&&this._core.is("resizing")&&this._core.$stage.find(".cloned .owl-video-frame").remove()},this),"changed.owl.carousel":a.proxy(function(a){a.namespace&&"position"===a.property.name&&this._playing&&this.stop()},this),"prepared.owl.carousel":a.proxy(function(b){if(b.namespace){var c=a(b.content).find(".owl-video");c.length&&(c.css("display","none"),this.fetch(c,a(b.content)))}},this)},this._core.options=a.extend({},e.Defaults,this._core.options),this._core.$element.on(this._handlers),this._core.$element.on("click.owl.video",".owl-video-play-icon",a.proxy(function(a){this.play(a)},this))};e.Defaults={video:!1,videoHeight:!1,videoWidth:!1},e.prototype.fetch=function(a,b){var c=function(){return a.attr("data-vimeo-id")?"vimeo":a.attr("data-vzaar-id")?"vzaar":"youtube"}(),d=a.attr("data-vimeo-id")||a.attr("data-youtube-id")||a.attr("data-vzaar-id"),e=a.attr("data-width")||this._core.settings.videoWidth,f=a.attr("data-height")||this._core.settings.videoHeight,g=a.attr("href");if(!g)throw new Error("Missing video URL.");if(d=g.match(/(http:|https:|)\/\/(player.|www.|app.)?(vimeo\.com|youtu(be\.com|\.be|be\.googleapis\.com|be\-nocookie\.com)|vzaar\.com)\/(video\/|videos\/|embed\/|channels\/.+\/|groups\/.+\/|watch\?v=|v\/)?([A-Za-z0-9._%-]*)(\&\S+)?/),d[3].indexOf("youtu")>-1)c="youtube";else if(d[3].indexOf("vimeo")>-1)c="vimeo";else{if(!(d[3].indexOf("vzaar")>-1))throw new Error("Video URL not supported.");c="vzaar"}d=d[6],this._videos[g]={type:c,id:d,width:e,height:f},b.attr("data-video",g),this.thumbnail(a,this._videos[g])},e.prototype.thumbnail=function(b,c){var d,e,f,g=c.width&&c.height?"width:"+c.width+"px;height:"+c.height+"px;":"",h=b.find("img"),i="src",j="",k=this._core.settings,l=function(c){e='
',d=k.lazyLoad?a("
",{class:"owl-video-tn "+j,srcType:c}):a("
",{class:"owl-video-tn",style:"opacity:1;background-image:url("+c+")"}),b.after(d),b.after(e)};if(b.wrap(a("
",{class:"owl-video-wrapper",style:g})),this._core.settings.lazyLoad&&(i="data-src",j="owl-lazy"),h.length)return l(h.attr(i)),h.remove(),!1;"youtube"===c.type?(f="//img.youtube.com/vi/"+c.id+"/hqdefault.jpg",l(f)):"vimeo"===c.type?a.ajax({type:"GET",url:"//vimeo.com/api/v2/video/"+c.id+".json",jsonp:"callback",dataType:"jsonp",success:function(a){f=a[0].thumbnail_large,l(f)}}):"vzaar"===c.type&&a.ajax({type:"GET",url:"//vzaar.com/api/videos/"+c.id+".json",jsonp:"callback",dataType:"jsonp",success:function(a){f=a.framegrab_url,l(f)}})},e.prototype.stop=function(){this._core.trigger("stop",null,"video"),this._playing.find(".owl-video-frame").remove(),this._playing.removeClass("owl-video-playing"),this._playing=null,this._core.leave("playing"),this._core.trigger("stopped",null,"video")},e.prototype.play=function(b){var c,d=a(b.target),e=d.closest("."+this._core.settings.itemClass),f=this._videos[e.attr("data-video")],g=f.width||"100%",h=f.height||this._core.$stage.height();this._playing||(this._core.enter("playing"),this._core.trigger("play",null,"video"),e=this._core.items(this._core.relative(e.index())),this._core.reset(e.index()),c=a(''),c.attr("height",h),c.attr("width",g),"youtube"===f.type?c.attr("src","//www.youtube.com/embed/"+f.id+"?autoplay=1&rel=0&v="+f.id):"vimeo"===f.type?c.attr("src","//player.vimeo.com/video/"+f.id+"?autoplay=1"):"vzaar"===f.type&&c.attr("src","//view.vzaar.com/"+f.id+"/player?autoplay=true"),a(c).wrap('
').insertAfter(e.find(".owl-video")),this._playing=e.addClass("owl-video-playing"))},e.prototype.isInFullScreen=function(){var b=c.fullscreenElement||c.mozFullScreenElement||c.webkitFullscreenElement;return b&&a(b).parent().hasClass("owl-video-frame")},e.prototype.destroy=function(){var a,b;this._core.$element.off("click.owl.video");for(a in this._handlers)this._core.$element.off(a,this._handlers[a]);for(b in Object.getOwnPropertyNames(this))"function"!=typeof this[b]&&(this[b]=null)},a.fn.owlCarousel.Constructor.Plugins.Video=e}(window.Zepto||window.jQuery,window,document),function(a,b,c,d){var e=function(b){this.core=b,this.core.options=a.extend({},e.Defaults,this.core.options),this.swapping=!0,this.previous=d,this.next=d,this.handlers={"change.owl.carousel":a.proxy(function(a){a.namespace&&"position"==a.property.name&&(this.previous=this.core.current(),this.next=a.property.value)},this),"drag.owl.carousel dragged.owl.carousel translated.owl.carousel":a.proxy(function(a){a.namespace&&(this.swapping="translated"==a.type)},this),"translate.owl.carousel":a.proxy(function(a){a.namespace&&this.swapping&&(this.core.options.animateOut||this.core.options.animateIn)&&this.swap()},this)},this.core.$element.on(this.handlers)};e.Defaults={animateOut:!1,
+animateIn:!1},e.prototype.swap=function(){if(1===this.core.settings.items&&a.support.animation&&a.support.transition){this.core.speed(0);var b,c=a.proxy(this.clear,this),d=this.core.$stage.children().eq(this.previous),e=this.core.$stage.children().eq(this.next),f=this.core.settings.animateIn,g=this.core.settings.animateOut;this.core.current()!==this.previous&&(g&&(b=this.core.coordinates(this.previous)-this.core.coordinates(this.next),d.one(a.support.animation.end,c).css({left:b+"px"}).addClass("animated owl-animated-out").addClass(g)),f&&e.one(a.support.animation.end,c).addClass("animated owl-animated-in").addClass(f))}},e.prototype.clear=function(b){a(b.target).css({left:""}).removeClass("animated owl-animated-out owl-animated-in").removeClass(this.core.settings.animateIn).removeClass(this.core.settings.animateOut),this.core.onTransitionEnd()},e.prototype.destroy=function(){var a,b;for(a in this.handlers)this.core.$element.off(a,this.handlers[a]);for(b in Object.getOwnPropertyNames(this))"function"!=typeof this[b]&&(this[b]=null)},a.fn.owlCarousel.Constructor.Plugins.Animate=e}(window.Zepto||window.jQuery,window,document),function(a,b,c,d){var e=function(b){this._core=b,this._call=null,this._time=0,this._timeout=0,this._paused=!0,this._handlers={"changed.owl.carousel":a.proxy(function(a){a.namespace&&"settings"===a.property.name?this._core.settings.autoplay?this.play():this.stop():a.namespace&&"position"===a.property.name&&this._paused&&(this._time=0)},this),"initialized.owl.carousel":a.proxy(function(a){a.namespace&&this._core.settings.autoplay&&this.play()},this),"play.owl.autoplay":a.proxy(function(a,b,c){a.namespace&&this.play(b,c)},this),"stop.owl.autoplay":a.proxy(function(a){a.namespace&&this.stop()},this),"mouseover.owl.autoplay":a.proxy(function(){this._core.settings.autoplayHoverPause&&this._core.is("rotating")&&this.pause()},this),"mouseleave.owl.autoplay":a.proxy(function(){this._core.settings.autoplayHoverPause&&this._core.is("rotating")&&this.play()},this),"touchstart.owl.core":a.proxy(function(){this._core.settings.autoplayHoverPause&&this._core.is("rotating")&&this.pause()},this),"touchend.owl.core":a.proxy(function(){this._core.settings.autoplayHoverPause&&this.play()},this)},this._core.$element.on(this._handlers),this._core.options=a.extend({},e.Defaults,this._core.options)};e.Defaults={autoplay:!1,autoplayTimeout:5e3,autoplayHoverPause:!1,autoplaySpeed:!1},e.prototype._next=function(d){this._call=b.setTimeout(a.proxy(this._next,this,d),this._timeout*(Math.round(this.read()/this._timeout)+1)-this.read()),this._core.is("interacting")||c.hidden||this._core.next(d||this._core.settings.autoplaySpeed)},e.prototype.read=function(){return(new Date).getTime()-this._time},e.prototype.play=function(c,d){var e;this._core.is("rotating")||this._core.enter("rotating"),c=c||this._core.settings.autoplayTimeout,e=Math.min(this._time%(this._timeout||c),c),this._paused?(this._time=this.read(),this._paused=!1):b.clearTimeout(this._call),this._time+=this.read()%c-e,this._timeout=c,this._call=b.setTimeout(a.proxy(this._next,this,d),c-e)},e.prototype.stop=function(){this._core.is("rotating")&&(this._time=0,this._paused=!0,b.clearTimeout(this._call),this._core.leave("rotating"))},e.prototype.pause=function(){this._core.is("rotating")&&!this._paused&&(this._time=this.read(),this._paused=!0,b.clearTimeout(this._call))},e.prototype.destroy=function(){var a,b;this.stop();for(a in this._handlers)this._core.$element.off(a,this._handlers[a]);for(b in Object.getOwnPropertyNames(this))"function"!=typeof this[b]&&(this[b]=null)},a.fn.owlCarousel.Constructor.Plugins.autoplay=e}(window.Zepto||window.jQuery,window,document),function(a,b,c,d){"use strict";var e=function(b){this._core=b,this._initialized=!1,this._pages=[],this._controls={},this._templates=[],this.$element=this._core.$element,this._overrides={next:this._core.next,prev:this._core.prev,to:this._core.to},this._handlers={"prepared.owl.carousel":a.proxy(function(b){b.namespace&&this._core.settings.dotsData&&this._templates.push(''+a(b.content).find("[data-dot]").addBack("[data-dot]").attr("data-dot")+"
")},this),"added.owl.carousel":a.proxy(function(a){a.namespace&&this._core.settings.dotsData&&this._templates.splice(a.position,0,this._templates.pop())},this),"remove.owl.carousel":a.proxy(function(a){a.namespace&&this._core.settings.dotsData&&this._templates.splice(a.position,1)},this),"changed.owl.carousel":a.proxy(function(a){a.namespace&&"position"==a.property.name&&this.draw()},this),"initialized.owl.carousel":a.proxy(function(a){a.namespace&&!this._initialized&&(this._core.trigger("initialize",null,"navigation"),this.initialize(),this.update(),this.draw(),this._initialized=!0,this._core.trigger("initialized",null,"navigation"))},this),"refreshed.owl.carousel":a.proxy(function(a){a.namespace&&this._initialized&&(this._core.trigger("refresh",null,"navigation"),this.update(),this.draw(),this._core.trigger("refreshed",null,"navigation"))},this)},this._core.options=a.extend({},e.Defaults,this._core.options),this.$element.on(this._handlers)};e.Defaults={nav:!1,navText:['‹ ','› '],navSpeed:!1,navElement:'button type="button" role="presentation"',navContainer:!1,navContainerClass:"owl-nav",navClass:["owl-prev","owl-next"],slideBy:1,dotClass:"owl-dot",dotsClass:"owl-dots",dots:!0,dotsEach:!1,dotsData:!1,dotsSpeed:!1,dotsContainer:!1},e.prototype.initialize=function(){var b,c=this._core.settings;this._controls.$relative=(c.navContainer?a(c.navContainer):a("").addClass(c.navContainerClass).appendTo(this.$element)).addClass("disabled"),this._controls.$previous=a("<"+c.navElement+">").addClass(c.navClass[0]).html(c.navText[0]).prependTo(this._controls.$relative).on("click",a.proxy(function(a){this.prev(c.navSpeed)},this)),this._controls.$next=a("<"+c.navElement+">").addClass(c.navClass[1]).html(c.navText[1]).appendTo(this._controls.$relative).on("click",a.proxy(function(a){this.next(c.navSpeed)},this)),c.dotsData||(this._templates=[a('
').addClass(c.dotClass).append(a("")).prop("outerHTML")]),this._controls.$absolute=(c.dotsContainer?a(c.dotsContainer):a("").addClass(c.dotsClass).appendTo(this.$element)).addClass("disabled"),this._controls.$absolute.on("click","button",a.proxy(function(b){var d=a(b.target).parent().is(this._controls.$absolute)?a(b.target).index():a(b.target).parent().index();b.preventDefault(),this.to(d,c.dotsSpeed)},this));for(b in this._overrides)this._core[b]=a.proxy(this[b],this)},e.prototype.destroy=function(){var a,b,c,d,e;e=this._core.settings;for(a in this._handlers)this.$element.off(a,this._handlers[a]);for(b in this._controls)"$relative"===b&&e.navContainer?this._controls[b].html(""):this._controls[b].remove();for(d in this.overides)this._core[d]=this._overrides[d];for(c in Object.getOwnPropertyNames(this))"function"!=typeof this[c]&&(this[c]=null)},e.prototype.update=function(){var a,b,c,d=this._core.clones().length/2,e=d+this._core.items().length,f=this._core.maximum(!0),g=this._core.settings,h=g.center||g.autoWidth||g.dotsData?1:g.dotsEach||g.items;if("page"!==g.slideBy&&(g.slideBy=Math.min(g.slideBy,g.items)),g.dots||"page"==g.slideBy)for(this._pages=[],a=d,b=0,c=0;a
=h||0===b){if(this._pages.push({start:Math.min(f,a-d),end:a-d+h-1}),Math.min(f,a-d)===f)break;b=0,++c}b+=this._core.mergers(this._core.relative(a))}},e.prototype.draw=function(){var b,c=this._core.settings,d=this._core.items().length<=c.items,e=this._core.relative(this._core.current()),f=c.loop||c.rewind;this._controls.$relative.toggleClass("disabled",!c.nav||d),c.nav&&(this._controls.$previous.toggleClass("disabled",!f&&e<=this._core.minimum(!0)),this._controls.$next.toggleClass("disabled",!f&&e>=this._core.maximum(!0))),this._controls.$absolute.toggleClass("disabled",!c.dots||d),c.dots&&(b=this._pages.length-this._controls.$absolute.children().length,c.dotsData&&0!==b?this._controls.$absolute.html(this._templates.join("")):b>0?this._controls.$absolute.append(new Array(b+1).join(this._templates[0])):b<0&&this._controls.$absolute.children().slice(b).remove(),this._controls.$absolute.find(".active").removeClass("active"),this._controls.$absolute.children().eq(a.inArray(this.current(),this._pages)).addClass("active"))},e.prototype.onTrigger=function(b){var c=this._core.settings;b.page={index:a.inArray(this.current(),this._pages),count:this._pages.length,size:c&&(c.center||c.autoWidth||c.dotsData?1:c.dotsEach||c.items)}},e.prototype.current=function(){var b=this._core.relative(this._core.current());return a.grep(this._pages,a.proxy(function(a,c){return a.start<=b&&a.end>=b},this)).pop()},e.prototype.getPosition=function(b){var c,d,e=this._core.settings;return"page"==e.slideBy?(c=a.inArray(this.current(),this._pages),d=this._pages.length,b?++c:--c,c=this._pages[(c%d+d)%d].start):(c=this._core.relative(this._core.current()),d=this._core.items().length,b?c+=e.slideBy:c-=e.slideBy),c},e.prototype.next=function(b){a.proxy(this._overrides.to,this._core)(this.getPosition(!0),b)},e.prototype.prev=function(b){a.proxy(this._overrides.to,this._core)(this.getPosition(!1),b)},e.prototype.to=function(b,c,d){var e;!d&&this._pages.length?(e=this._pages.length,a.proxy(this._overrides.to,this._core)(this._pages[(b%e+e)%e].start,c)):a.proxy(this._overrides.to,this._core)(b,c)},a.fn.owlCarousel.Constructor.Plugins.Navigation=e}(window.Zepto||window.jQuery,window,document),function(a,b,c,d){"use strict";var e=function(c){this._core=c,this._hashes={},this.$element=this._core.$element,this._handlers={"initialized.owl.carousel":a.proxy(function(c){c.namespace&&"URLHash"===this._core.settings.startPosition&&a(b).trigger("hashchange.owl.navigation")},this),"prepared.owl.carousel":a.proxy(function(b){if(b.namespace){var c=a(b.content).find("[data-hash]").addBack("[data-hash]").attr("data-hash");if(!c)return;this._hashes[c]=b.content}},this),"changed.owl.carousel":a.proxy(function(c){if(c.namespace&&"position"===c.property.name){var d=this._core.items(this._core.relative(this._core.current())),e=a.map(this._hashes,function(a,b){return a===d?b:null}).join();if(!e||b.location.hash.slice(1)===e)return;b.location.hash=e}},this)},this._core.options=a.extend({},e.Defaults,this._core.options),this.$element.on(this._handlers),a(b).on("hashchange.owl.navigation",a.proxy(function(a){var c=b.location.hash.substring(1),e=this._core.$stage.children(),f=this._hashes[c]&&e.index(this._hashes[c]);f!==d&&f!==this._core.current()&&this._core.to(this._core.relative(f),!1,!0)},this))};e.Defaults={URLhashListener:!1},e.prototype.destroy=function(){var c,d;a(b).off("hashchange.owl.navigation");for(c in this._handlers)this._core.$element.off(c,this._handlers[c]);for(d in Object.getOwnPropertyNames(this))"function"!=typeof this[d]&&(this[d]=null)},a.fn.owlCarousel.Constructor.Plugins.Hash=e}(window.Zepto||window.jQuery,window,document),function(a,b,c,d){function e(b,c){var e=!1,f=b.charAt(0).toUpperCase()+b.slice(1);return a.each((b+" "+h.join(f+" ")+f).split(" "),function(a,b){if(g[b]!==d)return e=!c||b,!1}),e}function f(a){return e(a,!0)}var g=a("").get(0).style,h="Webkit Moz O ms".split(" "),i={transition:{end:{WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd",transition:"transitionend"}},animation:{end:{WebkitAnimation:"webkitAnimationEnd",MozAnimation:"animationend",OAnimation:"oAnimationEnd",animation:"animationend"}}},j={csstransforms:function(){return!!e("transform")},csstransforms3d:function(){return!!e("perspective")},csstransitions:function(){return!!e("transition")},cssanimations:function(){return!!e("animation")}};j.csstransitions()&&(a.support.transition=new String(f("transition")),a.support.transition.end=i.transition.end[a.support.transition]),j.cssanimations()&&(a.support.animation=new String(f("animation")),a.support.animation.end=i.animation.end[a.support.animation]),j.csstransforms()&&(a.support.transform=new String(f("transform")),a.support.transform3d=j.csstransforms3d())}(window.Zepto||window.jQuery,window,document);
\ No newline at end of file
diff --git a/themes/30coffe/assets/js/slick.min.js b/themes/30coffe/assets/js/slick.min.js
new file mode 100644
index 00000000..42172c2f
--- /dev/null
+++ b/themes/30coffe/assets/js/slick.min.js
@@ -0,0 +1 @@
+!function(i){"use strict";"function"==typeof define&&define.amd?define(["jquery"],i):"undefined"!=typeof exports?module.exports=i(require("jquery")):i(jQuery)}(function(i){"use strict";var e=window.Slick||{};(e=function(){var e=0;return function(t,o){var s,n=this;n.defaults={accessibility:!0,adaptiveHeight:!1,appendArrows:i(t),appendDots:i(t),arrows:!0,asNavFor:null,prevArrow:'Previous ',nextArrow:'Next ',autoplay:!1,autoplaySpeed:3e3,centerMode:!1,centerPadding:"50px",cssEase:"ease",customPaging:function(e,t){return i(' ').text(t+1)},dots:!1,dotsClass:"slick-dots",draggable:!0,easing:"linear",edgeFriction:.35,fade:!1,focusOnSelect:!1,focusOnChange:!1,infinite:!0,initialSlide:0,lazyLoad:"ondemand",mobileFirst:!1,pauseOnHover:!0,pauseOnFocus:!0,pauseOnDotsHover:!1,respondTo:"window",responsive:null,rows:1,rtl:!1,slide:"",slidesPerRow:1,slidesToShow:1,slidesToScroll:1,speed:500,swipe:!0,swipeToSlide:!1,touchMove:!0,touchThreshold:5,useCSS:!0,useTransform:!0,variableWidth:!1,vertical:!1,verticalSwiping:!1,waitForAnimate:!0,zIndex:1e3},n.initials={animating:!1,dragging:!1,autoPlayTimer:null,currentDirection:0,currentLeft:null,currentSlide:0,direction:1,$dots:null,listWidth:null,listHeight:null,loadIndex:0,$nextArrow:null,$prevArrow:null,scrolling:!1,slideCount:null,slideWidth:null,$slideTrack:null,$slides:null,sliding:!1,slideOffset:0,swipeLeft:null,swiping:!1,$list:null,touchObject:{},transformsEnabled:!1,unslicked:!1},i.extend(n,n.initials),n.activeBreakpoint=null,n.animType=null,n.animProp=null,n.breakpoints=[],n.breakpointSettings=[],n.cssTransitions=!1,n.focussed=!1,n.interrupted=!1,n.hidden="hidden",n.paused=!0,n.positionProp=null,n.respondTo=null,n.rowCount=1,n.shouldClick=!0,n.$slider=i(t),n.$slidesCache=null,n.transformType=null,n.transitionType=null,n.visibilityChange="visibilitychange",n.windowWidth=0,n.windowTimer=null,s=i(t).data("slick")||{},n.options=i.extend({},n.defaults,o,s),n.currentSlide=n.options.initialSlide,n.originalSettings=n.options,void 0!==document.mozHidden?(n.hidden="mozHidden",n.visibilityChange="mozvisibilitychange"):void 0!==document.webkitHidden&&(n.hidden="webkitHidden",n.visibilityChange="webkitvisibilitychange"),n.autoPlay=i.proxy(n.autoPlay,n),n.autoPlayClear=i.proxy(n.autoPlayClear,n),n.autoPlayIterator=i.proxy(n.autoPlayIterator,n),n.changeSlide=i.proxy(n.changeSlide,n),n.clickHandler=i.proxy(n.clickHandler,n),n.selectHandler=i.proxy(n.selectHandler,n),n.setPosition=i.proxy(n.setPosition,n),n.swipeHandler=i.proxy(n.swipeHandler,n),n.dragHandler=i.proxy(n.dragHandler,n),n.keyHandler=i.proxy(n.keyHandler,n),n.instanceUid=e++,n.htmlExpr=/^(?:\s*(<[\w\W]+>)[^>]*)$/,n.registerBreakpoints(),n.init(!0)}}()).prototype.activateADA=function(){this.$slideTrack.find(".slick-active").attr({"aria-hidden":"false"}).find("a, input, button, select").attr({tabindex:"0"})},e.prototype.addSlide=e.prototype.slickAdd=function(e,t,o){var s=this;if("boolean"==typeof t)o=t,t=null;else if(t<0||t>=s.slideCount)return!1;s.unload(),"number"==typeof t?0===t&&0===s.$slides.length?i(e).appendTo(s.$slideTrack):o?i(e).insertBefore(s.$slides.eq(t)):i(e).insertAfter(s.$slides.eq(t)):!0===o?i(e).prependTo(s.$slideTrack):i(e).appendTo(s.$slideTrack),s.$slides=s.$slideTrack.children(this.options.slide),s.$slideTrack.children(this.options.slide).detach(),s.$slideTrack.append(s.$slides),s.$slides.each(function(e,t){i(t).attr("data-slick-index",e)}),s.$slidesCache=s.$slides,s.reinit()},e.prototype.animateHeight=function(){var i=this;if(1===i.options.slidesToShow&&!0===i.options.adaptiveHeight&&!1===i.options.vertical){var e=i.$slides.eq(i.currentSlide).outerHeight(!0);i.$list.animate({height:e},i.options.speed)}},e.prototype.animateSlide=function(e,t){var o={},s=this;s.animateHeight(),!0===s.options.rtl&&!1===s.options.vertical&&(e=-e),!1===s.transformsEnabled?!1===s.options.vertical?s.$slideTrack.animate({left:e},s.options.speed,s.options.easing,t):s.$slideTrack.animate({top:e},s.options.speed,s.options.easing,t):!1===s.cssTransitions?(!0===s.options.rtl&&(s.currentLeft=-s.currentLeft),i({animStart:s.currentLeft}).animate({animStart:e},{duration:s.options.speed,easing:s.options.easing,step:function(i){i=Math.ceil(i),!1===s.options.vertical?(o[s.animType]="translate("+i+"px, 0px)",s.$slideTrack.css(o)):(o[s.animType]="translate(0px,"+i+"px)",s.$slideTrack.css(o))},complete:function(){t&&t.call()}})):(s.applyTransition(),e=Math.ceil(e),!1===s.options.vertical?o[s.animType]="translate3d("+e+"px, 0px, 0px)":o[s.animType]="translate3d(0px,"+e+"px, 0px)",s.$slideTrack.css(o),t&&setTimeout(function(){s.disableTransition(),t.call()},s.options.speed))},e.prototype.getNavTarget=function(){var e=this,t=e.options.asNavFor;return t&&null!==t&&(t=i(t).not(e.$slider)),t},e.prototype.asNavFor=function(e){var t=this.getNavTarget();null!==t&&"object"==typeof t&&t.each(function(){var t=i(this).slick("getSlick");t.unslicked||t.slideHandler(e,!0)})},e.prototype.applyTransition=function(i){var e=this,t={};!1===e.options.fade?t[e.transitionType]=e.transformType+" "+e.options.speed+"ms "+e.options.cssEase:t[e.transitionType]="opacity "+e.options.speed+"ms "+e.options.cssEase,!1===e.options.fade?e.$slideTrack.css(t):e.$slides.eq(i).css(t)},e.prototype.autoPlay=function(){var i=this;i.autoPlayClear(),i.slideCount>i.options.slidesToShow&&(i.autoPlayTimer=setInterval(i.autoPlayIterator,i.options.autoplaySpeed))},e.prototype.autoPlayClear=function(){var i=this;i.autoPlayTimer&&clearInterval(i.autoPlayTimer)},e.prototype.autoPlayIterator=function(){var i=this,e=i.currentSlide+i.options.slidesToScroll;i.paused||i.interrupted||i.focussed||(!1===i.options.infinite&&(1===i.direction&&i.currentSlide+1===i.slideCount-1?i.direction=0:0===i.direction&&(e=i.currentSlide-i.options.slidesToScroll,i.currentSlide-1==0&&(i.direction=1))),i.slideHandler(e))},e.prototype.buildArrows=function(){var e=this;!0===e.options.arrows&&(e.$prevArrow=i(e.options.prevArrow).addClass("slick-arrow"),e.$nextArrow=i(e.options.nextArrow).addClass("slick-arrow"),e.slideCount>e.options.slidesToShow?(e.$prevArrow.removeClass("slick-hidden").removeAttr("aria-hidden tabindex"),e.$nextArrow.removeClass("slick-hidden").removeAttr("aria-hidden tabindex"),e.htmlExpr.test(e.options.prevArrow)&&e.$prevArrow.prependTo(e.options.appendArrows),e.htmlExpr.test(e.options.nextArrow)&&e.$nextArrow.appendTo(e.options.appendArrows),!0!==e.options.infinite&&e.$prevArrow.addClass("slick-disabled").attr("aria-disabled","true")):e.$prevArrow.add(e.$nextArrow).addClass("slick-hidden").attr({"aria-disabled":"true",tabindex:"-1"}))},e.prototype.buildDots=function(){var e,t,o=this;if(!0===o.options.dots){for(o.$slider.addClass("slick-dotted"),t=i("").addClass(o.options.dotsClass),e=0;e<=o.getDotCount();e+=1)t.append(i(" ").append(o.options.customPaging.call(this,o,e)));o.$dots=t.appendTo(o.options.appendDots),o.$dots.find("li").first().addClass("slick-active")}},e.prototype.buildOut=function(){var e=this;e.$slides=e.$slider.children(e.options.slide+":not(.slick-cloned)").addClass("slick-slide"),e.slideCount=e.$slides.length,e.$slides.each(function(e,t){i(t).attr("data-slick-index",e).data("originalStyling",i(t).attr("style")||"")}),e.$slider.addClass("slick-slider"),e.$slideTrack=0===e.slideCount?i('
').appendTo(e.$slider):e.$slides.wrapAll('
').parent(),e.$list=e.$slideTrack.wrap('
').parent(),e.$slideTrack.css("opacity",0),!0!==e.options.centerMode&&!0!==e.options.swipeToSlide||(e.options.slidesToScroll=1),i("img[data-lazy]",e.$slider).not("[src]").addClass("slick-loading"),e.setupInfinite(),e.buildArrows(),e.buildDots(),e.updateDots(),e.setSlideClasses("number"==typeof e.currentSlide?e.currentSlide:0),!0===e.options.draggable&&e.$list.addClass("draggable")},e.prototype.buildRows=function(){var i,e,t,o,s,n,r,l=this;if(o=document.createDocumentFragment(),n=l.$slider.children(),l.options.rows>1){for(r=l.options.slidesPerRow*l.options.rows,s=Math.ceil(n.length/r),i=0;ir.breakpoints[o]&&(s=r.breakpoints[o]));null!==s?null!==r.activeBreakpoint?(s!==r.activeBreakpoint||t)&&(r.activeBreakpoint=s,"unslick"===r.breakpointSettings[s]?r.unslick(s):(r.options=i.extend({},r.originalSettings,r.breakpointSettings[s]),!0===e&&(r.currentSlide=r.options.initialSlide),r.refresh(e)),l=s):(r.activeBreakpoint=s,"unslick"===r.breakpointSettings[s]?r.unslick(s):(r.options=i.extend({},r.originalSettings,r.breakpointSettings[s]),!0===e&&(r.currentSlide=r.options.initialSlide),r.refresh(e)),l=s):null!==r.activeBreakpoint&&(r.activeBreakpoint=null,r.options=r.originalSettings,!0===e&&(r.currentSlide=r.options.initialSlide),r.refresh(e),l=s),e||!1===l||r.$slider.trigger("breakpoint",[r,l])}},e.prototype.changeSlide=function(e,t){var o,s,n,r=this,l=i(e.currentTarget);switch(l.is("a")&&e.preventDefault(),l.is("li")||(l=l.closest("li")),n=r.slideCount%r.options.slidesToScroll!=0,o=n?0:(r.slideCount-r.currentSlide)%r.options.slidesToScroll,e.data.message){case"previous":s=0===o?r.options.slidesToScroll:r.options.slidesToShow-o,r.slideCount>r.options.slidesToShow&&r.slideHandler(r.currentSlide-s,!1,t);break;case"next":s=0===o?r.options.slidesToScroll:o,r.slideCount>r.options.slidesToShow&&r.slideHandler(r.currentSlide+s,!1,t);break;case"index":var d=0===e.data.index?0:e.data.index||l.index()*r.options.slidesToScroll;r.slideHandler(r.checkNavigable(d),!1,t),l.children().trigger("focus");break;default:return}},e.prototype.checkNavigable=function(i){var e,t;if(e=this.getNavigableIndexes(),t=0,i>e[e.length-1])i=e[e.length-1];else for(var o in e){if(ie.options.slidesToShow&&(e.$prevArrow&&e.$prevArrow.off("click.slick",e.changeSlide),e.$nextArrow&&e.$nextArrow.off("click.slick",e.changeSlide),!0===e.options.accessibility&&(e.$prevArrow&&e.$prevArrow.off("keydown.slick",e.keyHandler),e.$nextArrow&&e.$nextArrow.off("keydown.slick",e.keyHandler))),e.$list.off("touchstart.slick mousedown.slick",e.swipeHandler),e.$list.off("touchmove.slick mousemove.slick",e.swipeHandler),e.$list.off("touchend.slick mouseup.slick",e.swipeHandler),e.$list.off("touchcancel.slick mouseleave.slick",e.swipeHandler),e.$list.off("click.slick",e.clickHandler),i(document).off(e.visibilityChange,e.visibility),e.cleanUpSlideEvents(),!0===e.options.accessibility&&e.$list.off("keydown.slick",e.keyHandler),!0===e.options.focusOnSelect&&i(e.$slideTrack).children().off("click.slick",e.selectHandler),i(window).off("orientationchange.slick.slick-"+e.instanceUid,e.orientationChange),i(window).off("resize.slick.slick-"+e.instanceUid,e.resize),i("[draggable!=true]",e.$slideTrack).off("dragstart",e.preventDefault),i(window).off("load.slick.slick-"+e.instanceUid,e.setPosition)},e.prototype.cleanUpSlideEvents=function(){var e=this;e.$list.off("mouseenter.slick",i.proxy(e.interrupt,e,!0)),e.$list.off("mouseleave.slick",i.proxy(e.interrupt,e,!1))},e.prototype.cleanUpRows=function(){var i,e=this;e.options.rows>1&&((i=e.$slides.children().children()).removeAttr("style"),e.$slider.empty().append(i))},e.prototype.clickHandler=function(i){!1===this.shouldClick&&(i.stopImmediatePropagation(),i.stopPropagation(),i.preventDefault())},e.prototype.destroy=function(e){var t=this;t.autoPlayClear(),t.touchObject={},t.cleanUpEvents(),i(".slick-cloned",t.$slider).detach(),t.$dots&&t.$dots.remove(),t.$prevArrow&&t.$prevArrow.length&&(t.$prevArrow.removeClass("slick-disabled slick-arrow slick-hidden").removeAttr("aria-hidden aria-disabled tabindex").css("display",""),t.htmlExpr.test(t.options.prevArrow)&&t.$prevArrow.remove()),t.$nextArrow&&t.$nextArrow.length&&(t.$nextArrow.removeClass("slick-disabled slick-arrow slick-hidden").removeAttr("aria-hidden aria-disabled tabindex").css("display",""),t.htmlExpr.test(t.options.nextArrow)&&t.$nextArrow.remove()),t.$slides&&(t.$slides.removeClass("slick-slide slick-active slick-center slick-visible slick-current").removeAttr("aria-hidden").removeAttr("data-slick-index").each(function(){i(this).attr("style",i(this).data("originalStyling"))}),t.$slideTrack.children(this.options.slide).detach(),t.$slideTrack.detach(),t.$list.detach(),t.$slider.append(t.$slides)),t.cleanUpRows(),t.$slider.removeClass("slick-slider"),t.$slider.removeClass("slick-initialized"),t.$slider.removeClass("slick-dotted"),t.unslicked=!0,e||t.$slider.trigger("destroy",[t])},e.prototype.disableTransition=function(i){var e=this,t={};t[e.transitionType]="",!1===e.options.fade?e.$slideTrack.css(t):e.$slides.eq(i).css(t)},e.prototype.fadeSlide=function(i,e){var t=this;!1===t.cssTransitions?(t.$slides.eq(i).css({zIndex:t.options.zIndex}),t.$slides.eq(i).animate({opacity:1},t.options.speed,t.options.easing,e)):(t.applyTransition(i),t.$slides.eq(i).css({opacity:1,zIndex:t.options.zIndex}),e&&setTimeout(function(){t.disableTransition(i),e.call()},t.options.speed))},e.prototype.fadeSlideOut=function(i){var e=this;!1===e.cssTransitions?e.$slides.eq(i).animate({opacity:0,zIndex:e.options.zIndex-2},e.options.speed,e.options.easing):(e.applyTransition(i),e.$slides.eq(i).css({opacity:0,zIndex:e.options.zIndex-2}))},e.prototype.filterSlides=e.prototype.slickFilter=function(i){var e=this;null!==i&&(e.$slidesCache=e.$slides,e.unload(),e.$slideTrack.children(this.options.slide).detach(),e.$slidesCache.filter(i).appendTo(e.$slideTrack),e.reinit())},e.prototype.focusHandler=function(){var e=this;e.$slider.off("focus.slick blur.slick").on("focus.slick blur.slick","*",function(t){t.stopImmediatePropagation();var o=i(this);setTimeout(function(){e.options.pauseOnFocus&&(e.focussed=o.is(":focus"),e.autoPlay())},0)})},e.prototype.getCurrent=e.prototype.slickCurrentSlide=function(){return this.currentSlide},e.prototype.getDotCount=function(){var i=this,e=0,t=0,o=0;if(!0===i.options.infinite)if(i.slideCount<=i.options.slidesToShow)++o;else for(;en.options.slidesToShow&&(n.slideOffset=n.slideWidth*n.options.slidesToShow*-1,s=-1,!0===n.options.vertical&&!0===n.options.centerMode&&(2===n.options.slidesToShow?s=-1.5:1===n.options.slidesToShow&&(s=-2)),r=t*n.options.slidesToShow*s),n.slideCount%n.options.slidesToScroll!=0&&i+n.options.slidesToScroll>n.slideCount&&n.slideCount>n.options.slidesToShow&&(i>n.slideCount?(n.slideOffset=(n.options.slidesToShow-(i-n.slideCount))*n.slideWidth*-1,r=(n.options.slidesToShow-(i-n.slideCount))*t*-1):(n.slideOffset=n.slideCount%n.options.slidesToScroll*n.slideWidth*-1,r=n.slideCount%n.options.slidesToScroll*t*-1))):i+n.options.slidesToShow>n.slideCount&&(n.slideOffset=(i+n.options.slidesToShow-n.slideCount)*n.slideWidth,r=(i+n.options.slidesToShow-n.slideCount)*t),n.slideCount<=n.options.slidesToShow&&(n.slideOffset=0,r=0),!0===n.options.centerMode&&n.slideCount<=n.options.slidesToShow?n.slideOffset=n.slideWidth*Math.floor(n.options.slidesToShow)/2-n.slideWidth*n.slideCount/2:!0===n.options.centerMode&&!0===n.options.infinite?n.slideOffset+=n.slideWidth*Math.floor(n.options.slidesToShow/2)-n.slideWidth:!0===n.options.centerMode&&(n.slideOffset=0,n.slideOffset+=n.slideWidth*Math.floor(n.options.slidesToShow/2)),e=!1===n.options.vertical?i*n.slideWidth*-1+n.slideOffset:i*t*-1+r,!0===n.options.variableWidth&&(o=n.slideCount<=n.options.slidesToShow||!1===n.options.infinite?n.$slideTrack.children(".slick-slide").eq(i):n.$slideTrack.children(".slick-slide").eq(i+n.options.slidesToShow),e=!0===n.options.rtl?o[0]?-1*(n.$slideTrack.width()-o[0].offsetLeft-o.width()):0:o[0]?-1*o[0].offsetLeft:0,!0===n.options.centerMode&&(o=n.slideCount<=n.options.slidesToShow||!1===n.options.infinite?n.$slideTrack.children(".slick-slide").eq(i):n.$slideTrack.children(".slick-slide").eq(i+n.options.slidesToShow+1),e=!0===n.options.rtl?o[0]?-1*(n.$slideTrack.width()-o[0].offsetLeft-o.width()):0:o[0]?-1*o[0].offsetLeft:0,e+=(n.$list.width()-o.outerWidth())/2)),e},e.prototype.getOption=e.prototype.slickGetOption=function(i){return this.options[i]},e.prototype.getNavigableIndexes=function(){var i,e=this,t=0,o=0,s=[];for(!1===e.options.infinite?i=e.slideCount:(t=-1*e.options.slidesToScroll,o=-1*e.options.slidesToScroll,i=2*e.slideCount);t-1*o.swipeLeft)return e=n,!1}),Math.abs(i(e).attr("data-slick-index")-o.currentSlide)||1):o.options.slidesToScroll},e.prototype.goTo=e.prototype.slickGoTo=function(i,e){this.changeSlide({data:{message:"index",index:parseInt(i)}},e)},e.prototype.init=function(e){var t=this;i(t.$slider).hasClass("slick-initialized")||(i(t.$slider).addClass("slick-initialized"),t.buildRows(),t.buildOut(),t.setProps(),t.startLoad(),t.loadSlider(),t.initializeEvents(),t.updateArrows(),t.updateDots(),t.checkResponsive(!0),t.focusHandler()),e&&t.$slider.trigger("init",[t]),!0===t.options.accessibility&&t.initADA(),t.options.autoplay&&(t.paused=!1,t.autoPlay())},e.prototype.initADA=function(){var e=this,t=Math.ceil(e.slideCount/e.options.slidesToShow),o=e.getNavigableIndexes().filter(function(i){return i>=0&&ii.options.slidesToShow&&(i.$prevArrow.off("click.slick").on("click.slick",{message:"previous"},i.changeSlide),i.$nextArrow.off("click.slick").on("click.slick",{message:"next"},i.changeSlide),!0===i.options.accessibility&&(i.$prevArrow.on("keydown.slick",i.keyHandler),i.$nextArrow.on("keydown.slick",i.keyHandler)))},e.prototype.initDotEvents=function(){var e=this;!0===e.options.dots&&(i("li",e.$dots).on("click.slick",{message:"index"},e.changeSlide),!0===e.options.accessibility&&e.$dots.on("keydown.slick",e.keyHandler)),!0===e.options.dots&&!0===e.options.pauseOnDotsHover&&i("li",e.$dots).on("mouseenter.slick",i.proxy(e.interrupt,e,!0)).on("mouseleave.slick",i.proxy(e.interrupt,e,!1))},e.prototype.initSlideEvents=function(){var e=this;e.options.pauseOnHover&&(e.$list.on("mouseenter.slick",i.proxy(e.interrupt,e,!0)),e.$list.on("mouseleave.slick",i.proxy(e.interrupt,e,!1)))},e.prototype.initializeEvents=function(){var e=this;e.initArrowEvents(),e.initDotEvents(),e.initSlideEvents(),e.$list.on("touchstart.slick mousedown.slick",{action:"start"},e.swipeHandler),e.$list.on("touchmove.slick mousemove.slick",{action:"move"},e.swipeHandler),e.$list.on("touchend.slick mouseup.slick",{action:"end"},e.swipeHandler),e.$list.on("touchcancel.slick mouseleave.slick",{action:"end"},e.swipeHandler),e.$list.on("click.slick",e.clickHandler),i(document).on(e.visibilityChange,i.proxy(e.visibility,e)),!0===e.options.accessibility&&e.$list.on("keydown.slick",e.keyHandler),!0===e.options.focusOnSelect&&i(e.$slideTrack).children().on("click.slick",e.selectHandler),i(window).on("orientationchange.slick.slick-"+e.instanceUid,i.proxy(e.orientationChange,e)),i(window).on("resize.slick.slick-"+e.instanceUid,i.proxy(e.resize,e)),i("[draggable!=true]",e.$slideTrack).on("dragstart",e.preventDefault),i(window).on("load.slick.slick-"+e.instanceUid,e.setPosition),i(e.setPosition)},e.prototype.initUI=function(){var i=this;!0===i.options.arrows&&i.slideCount>i.options.slidesToShow&&(i.$prevArrow.show(),i.$nextArrow.show()),!0===i.options.dots&&i.slideCount>i.options.slidesToShow&&i.$dots.show()},e.prototype.keyHandler=function(i){var e=this;i.target.tagName.match("TEXTAREA|INPUT|SELECT")||(37===i.keyCode&&!0===e.options.accessibility?e.changeSlide({data:{message:!0===e.options.rtl?"next":"previous"}}):39===i.keyCode&&!0===e.options.accessibility&&e.changeSlide({data:{message:!0===e.options.rtl?"previous":"next"}}))},e.prototype.lazyLoad=function(){function e(e){i("img[data-lazy]",e).each(function(){var e=i(this),t=i(this).attr("data-lazy"),o=i(this).attr("data-srcset"),s=i(this).attr("data-sizes")||n.$slider.attr("data-sizes"),r=document.createElement("img");r.onload=function(){e.animate({opacity:0},100,function(){o&&(e.attr("srcset",o),s&&e.attr("sizes",s)),e.attr("src",t).animate({opacity:1},200,function(){e.removeAttr("data-lazy data-srcset data-sizes").removeClass("slick-loading")}),n.$slider.trigger("lazyLoaded",[n,e,t])})},r.onerror=function(){e.removeAttr("data-lazy").removeClass("slick-loading").addClass("slick-lazyload-error"),n.$slider.trigger("lazyLoadError",[n,e,t])},r.src=t})}var t,o,s,n=this;if(!0===n.options.centerMode?!0===n.options.infinite?s=(o=n.currentSlide+(n.options.slidesToShow/2+1))+n.options.slidesToShow+2:(o=Math.max(0,n.currentSlide-(n.options.slidesToShow/2+1)),s=n.options.slidesToShow/2+1+2+n.currentSlide):(o=n.options.infinite?n.options.slidesToShow+n.currentSlide:n.currentSlide,s=Math.ceil(o+n.options.slidesToShow),!0===n.options.fade&&(o>0&&o--,s<=n.slideCount&&s++)),t=n.$slider.find(".slick-slide").slice(o,s),"anticipated"===n.options.lazyLoad)for(var r=o-1,l=s,d=n.$slider.find(".slick-slide"),a=0;a=n.slideCount-n.options.slidesToShow?e(n.$slider.find(".slick-cloned").slice(0,n.options.slidesToShow)):0===n.currentSlide&&e(n.$slider.find(".slick-cloned").slice(-1*n.options.slidesToShow))},e.prototype.loadSlider=function(){var i=this;i.setPosition(),i.$slideTrack.css({opacity:1}),i.$slider.removeClass("slick-loading"),i.initUI(),"progressive"===i.options.lazyLoad&&i.progressiveLazyLoad()},e.prototype.next=e.prototype.slickNext=function(){this.changeSlide({data:{message:"next"}})},e.prototype.orientationChange=function(){var i=this;i.checkResponsive(),i.setPosition()},e.prototype.pause=e.prototype.slickPause=function(){var i=this;i.autoPlayClear(),i.paused=!0},e.prototype.play=e.prototype.slickPlay=function(){var i=this;i.autoPlay(),i.options.autoplay=!0,i.paused=!1,i.focussed=!1,i.interrupted=!1},e.prototype.postSlide=function(e){var t=this;t.unslicked||(t.$slider.trigger("afterChange",[t,e]),t.animating=!1,t.slideCount>t.options.slidesToShow&&t.setPosition(),t.swipeLeft=null,t.options.autoplay&&t.autoPlay(),!0===t.options.accessibility&&(t.initADA(),t.options.focusOnChange&&i(t.$slides.get(t.currentSlide)).attr("tabindex",0).focus()))},e.prototype.prev=e.prototype.slickPrev=function(){this.changeSlide({data:{message:"previous"}})},e.prototype.preventDefault=function(i){i.preventDefault()},e.prototype.progressiveLazyLoad=function(e){e=e||1;var t,o,s,n,r,l=this,d=i("img[data-lazy]",l.$slider);d.length?(t=d.first(),o=t.attr("data-lazy"),s=t.attr("data-srcset"),n=t.attr("data-sizes")||l.$slider.attr("data-sizes"),(r=document.createElement("img")).onload=function(){s&&(t.attr("srcset",s),n&&t.attr("sizes",n)),t.attr("src",o).removeAttr("data-lazy data-srcset data-sizes").removeClass("slick-loading"),!0===l.options.adaptiveHeight&&l.setPosition(),l.$slider.trigger("lazyLoaded",[l,t,o]),l.progressiveLazyLoad()},r.onerror=function(){e<3?setTimeout(function(){l.progressiveLazyLoad(e+1)},500):(t.removeAttr("data-lazy").removeClass("slick-loading").addClass("slick-lazyload-error"),l.$slider.trigger("lazyLoadError",[l,t,o]),l.progressiveLazyLoad())},r.src=o):l.$slider.trigger("allImagesLoaded",[l])},e.prototype.refresh=function(e){var t,o,s=this;o=s.slideCount-s.options.slidesToShow,!s.options.infinite&&s.currentSlide>o&&(s.currentSlide=o),s.slideCount<=s.options.slidesToShow&&(s.currentSlide=0),t=s.currentSlide,s.destroy(!0),i.extend(s,s.initials,{currentSlide:t}),s.init(),e||s.changeSlide({data:{message:"index",index:t}},!1)},e.prototype.registerBreakpoints=function(){var e,t,o,s=this,n=s.options.responsive||null;if("array"===i.type(n)&&n.length){s.respondTo=s.options.respondTo||"window";for(e in n)if(o=s.breakpoints.length-1,n.hasOwnProperty(e)){for(t=n[e].breakpoint;o>=0;)s.breakpoints[o]&&s.breakpoints[o]===t&&s.breakpoints.splice(o,1),o--;s.breakpoints.push(t),s.breakpointSettings[t]=n[e].settings}s.breakpoints.sort(function(i,e){return s.options.mobileFirst?i-e:e-i})}},e.prototype.reinit=function(){var e=this;e.$slides=e.$slideTrack.children(e.options.slide).addClass("slick-slide"),e.slideCount=e.$slides.length,e.currentSlide>=e.slideCount&&0!==e.currentSlide&&(e.currentSlide=e.currentSlide-e.options.slidesToScroll),e.slideCount<=e.options.slidesToShow&&(e.currentSlide=0),e.registerBreakpoints(),e.setProps(),e.setupInfinite(),e.buildArrows(),e.updateArrows(),e.initArrowEvents(),e.buildDots(),e.updateDots(),e.initDotEvents(),e.cleanUpSlideEvents(),e.initSlideEvents(),e.checkResponsive(!1,!0),!0===e.options.focusOnSelect&&i(e.$slideTrack).children().on("click.slick",e.selectHandler),e.setSlideClasses("number"==typeof e.currentSlide?e.currentSlide:0),e.setPosition(),e.focusHandler(),e.paused=!e.options.autoplay,e.autoPlay(),e.$slider.trigger("reInit",[e])},e.prototype.resize=function(){var e=this;i(window).width()!==e.windowWidth&&(clearTimeout(e.windowDelay),e.windowDelay=window.setTimeout(function(){e.windowWidth=i(window).width(),e.checkResponsive(),e.unslicked||e.setPosition()},50))},e.prototype.removeSlide=e.prototype.slickRemove=function(i,e,t){var o=this;if(i="boolean"==typeof i?!0===(e=i)?0:o.slideCount-1:!0===e?--i:i,o.slideCount<1||i<0||i>o.slideCount-1)return!1;o.unload(),!0===t?o.$slideTrack.children().remove():o.$slideTrack.children(this.options.slide).eq(i).remove(),o.$slides=o.$slideTrack.children(this.options.slide),o.$slideTrack.children(this.options.slide).detach(),o.$slideTrack.append(o.$slides),o.$slidesCache=o.$slides,o.reinit()},e.prototype.setCSS=function(i){var e,t,o=this,s={};!0===o.options.rtl&&(i=-i),e="left"==o.positionProp?Math.ceil(i)+"px":"0px",t="top"==o.positionProp?Math.ceil(i)+"px":"0px",s[o.positionProp]=i,!1===o.transformsEnabled?o.$slideTrack.css(s):(s={},!1===o.cssTransitions?(s[o.animType]="translate("+e+", "+t+")",o.$slideTrack.css(s)):(s[o.animType]="translate3d("+e+", "+t+", 0px)",o.$slideTrack.css(s)))},e.prototype.setDimensions=function(){var i=this;!1===i.options.vertical?!0===i.options.centerMode&&i.$list.css({padding:"0px "+i.options.centerPadding}):(i.$list.height(i.$slides.first().outerHeight(!0)*i.options.slidesToShow),!0===i.options.centerMode&&i.$list.css({padding:i.options.centerPadding+" 0px"})),i.listWidth=i.$list.width(),i.listHeight=i.$list.height(),!1===i.options.vertical&&!1===i.options.variableWidth?(i.slideWidth=Math.ceil(i.listWidth/i.options.slidesToShow),i.$slideTrack.width(Math.ceil(i.slideWidth*i.$slideTrack.children(".slick-slide").length))):!0===i.options.variableWidth?i.$slideTrack.width(5e3*i.slideCount):(i.slideWidth=Math.ceil(i.listWidth),i.$slideTrack.height(Math.ceil(i.$slides.first().outerHeight(!0)*i.$slideTrack.children(".slick-slide").length)));var e=i.$slides.first().outerWidth(!0)-i.$slides.first().width();!1===i.options.variableWidth&&i.$slideTrack.children(".slick-slide").width(i.slideWidth-e)},e.prototype.setFade=function(){var e,t=this;t.$slides.each(function(o,s){e=t.slideWidth*o*-1,!0===t.options.rtl?i(s).css({position:"relative",right:e,top:0,zIndex:t.options.zIndex-2,opacity:0}):i(s).css({position:"relative",left:e,top:0,zIndex:t.options.zIndex-2,opacity:0})}),t.$slides.eq(t.currentSlide).css({zIndex:t.options.zIndex-1,opacity:1})},e.prototype.setHeight=function(){var i=this;if(1===i.options.slidesToShow&&!0===i.options.adaptiveHeight&&!1===i.options.vertical){var e=i.$slides.eq(i.currentSlide).outerHeight(!0);i.$list.css("height",e)}},e.prototype.setOption=e.prototype.slickSetOption=function(){var e,t,o,s,n,r=this,l=!1;if("object"===i.type(arguments[0])?(o=arguments[0],l=arguments[1],n="multiple"):"string"===i.type(arguments[0])&&(o=arguments[0],s=arguments[1],l=arguments[2],"responsive"===arguments[0]&&"array"===i.type(arguments[1])?n="responsive":void 0!==arguments[1]&&(n="single")),"single"===n)r.options[o]=s;else if("multiple"===n)i.each(o,function(i,e){r.options[i]=e});else if("responsive"===n)for(t in s)if("array"!==i.type(r.options.responsive))r.options.responsive=[s[t]];else{for(e=r.options.responsive.length-1;e>=0;)r.options.responsive[e].breakpoint===s[t].breakpoint&&r.options.responsive.splice(e,1),e--;r.options.responsive.push(s[t])}l&&(r.unload(),r.reinit())},e.prototype.setPosition=function(){var i=this;i.setDimensions(),i.setHeight(),!1===i.options.fade?i.setCSS(i.getLeft(i.currentSlide)):i.setFade(),i.$slider.trigger("setPosition",[i])},e.prototype.setProps=function(){var i=this,e=document.body.style;i.positionProp=!0===i.options.vertical?"top":"left","top"===i.positionProp?i.$slider.addClass("slick-vertical"):i.$slider.removeClass("slick-vertical"),void 0===e.WebkitTransition&&void 0===e.MozTransition&&void 0===e.msTransition||!0===i.options.useCSS&&(i.cssTransitions=!0),i.options.fade&&("number"==typeof i.options.zIndex?i.options.zIndex<3&&(i.options.zIndex=3):i.options.zIndex=i.defaults.zIndex),void 0!==e.OTransform&&(i.animType="OTransform",i.transformType="-o-transform",i.transitionType="OTransition",void 0===e.perspectiveProperty&&void 0===e.webkitPerspective&&(i.animType=!1)),void 0!==e.MozTransform&&(i.animType="MozTransform",i.transformType="-moz-transform",i.transitionType="MozTransition",void 0===e.perspectiveProperty&&void 0===e.MozPerspective&&(i.animType=!1)),void 0!==e.webkitTransform&&(i.animType="webkitTransform",i.transformType="-webkit-transform",i.transitionType="webkitTransition",void 0===e.perspectiveProperty&&void 0===e.webkitPerspective&&(i.animType=!1)),void 0!==e.msTransform&&(i.animType="msTransform",i.transformType="-ms-transform",i.transitionType="msTransition",void 0===e.msTransform&&(i.animType=!1)),void 0!==e.transform&&!1!==i.animType&&(i.animType="transform",i.transformType="transform",i.transitionType="transition"),i.transformsEnabled=i.options.useTransform&&null!==i.animType&&!1!==i.animType},e.prototype.setSlideClasses=function(i){var e,t,o,s,n=this;if(t=n.$slider.find(".slick-slide").removeClass("slick-active slick-center slick-current").attr("aria-hidden","true"),n.$slides.eq(i).addClass("slick-current"),!0===n.options.centerMode){var r=n.options.slidesToShow%2==0?1:0;e=Math.floor(n.options.slidesToShow/2),!0===n.options.infinite&&(i>=e&&i<=n.slideCount-1-e?n.$slides.slice(i-e+r,i+e+1).addClass("slick-active").attr("aria-hidden","false"):(o=n.options.slidesToShow+i,t.slice(o-e+1+r,o+e+2).addClass("slick-active").attr("aria-hidden","false")),0===i?t.eq(t.length-1-n.options.slidesToShow).addClass("slick-center"):i===n.slideCount-1&&t.eq(n.options.slidesToShow).addClass("slick-center")),n.$slides.eq(i).addClass("slick-center")}else i>=0&&i<=n.slideCount-n.options.slidesToShow?n.$slides.slice(i,i+n.options.slidesToShow).addClass("slick-active").attr("aria-hidden","false"):t.length<=n.options.slidesToShow?t.addClass("slick-active").attr("aria-hidden","false"):(s=n.slideCount%n.options.slidesToShow,o=!0===n.options.infinite?n.options.slidesToShow+i:i,n.options.slidesToShow==n.options.slidesToScroll&&n.slideCount-is.options.slidesToShow)){for(o=!0===s.options.centerMode?s.options.slidesToShow+1:s.options.slidesToShow,e=s.slideCount;e>s.slideCount-o;e-=1)t=e-1,i(s.$slides[t]).clone(!0).attr("id","").attr("data-slick-index",t-s.slideCount).prependTo(s.$slideTrack).addClass("slick-cloned");for(e=0;ea.getDotCount()*a.options.slidesToScroll))!1===a.options.fade&&(o=a.currentSlide,!0!==t?a.animateSlide(r,function(){a.postSlide(o)}):a.postSlide(o));else if(!1===a.options.infinite&&!0===a.options.centerMode&&(i<0||i>a.slideCount-a.options.slidesToScroll))!1===a.options.fade&&(o=a.currentSlide,!0!==t?a.animateSlide(r,function(){a.postSlide(o)}):a.postSlide(o));else{if(a.options.autoplay&&clearInterval(a.autoPlayTimer),s=o<0?a.slideCount%a.options.slidesToScroll!=0?a.slideCount-a.slideCount%a.options.slidesToScroll:a.slideCount+o:o>=a.slideCount?a.slideCount%a.options.slidesToScroll!=0?0:o-a.slideCount:o,a.animating=!0,a.$slider.trigger("beforeChange",[a,a.currentSlide,s]),n=a.currentSlide,a.currentSlide=s,a.setSlideClasses(a.currentSlide),a.options.asNavFor&&(l=(l=a.getNavTarget()).slick("getSlick")).slideCount<=l.options.slidesToShow&&l.setSlideClasses(a.currentSlide),a.updateDots(),a.updateArrows(),!0===a.options.fade)return!0!==t?(a.fadeSlideOut(n),a.fadeSlide(s,function(){a.postSlide(s)})):a.postSlide(s),void a.animateHeight();!0!==t?a.animateSlide(d,function(){a.postSlide(s)}):a.postSlide(s)}},e.prototype.startLoad=function(){var i=this;!0===i.options.arrows&&i.slideCount>i.options.slidesToShow&&(i.$prevArrow.hide(),i.$nextArrow.hide()),!0===i.options.dots&&i.slideCount>i.options.slidesToShow&&i.$dots.hide(),i.$slider.addClass("slick-loading")},e.prototype.swipeDirection=function(){var i,e,t,o,s=this;return i=s.touchObject.startX-s.touchObject.curX,e=s.touchObject.startY-s.touchObject.curY,t=Math.atan2(e,i),(o=Math.round(180*t/Math.PI))<0&&(o=360-Math.abs(o)),o<=45&&o>=0?!1===s.options.rtl?"left":"right":o<=360&&o>=315?!1===s.options.rtl?"left":"right":o>=135&&o<=225?!1===s.options.rtl?"right":"left":!0===s.options.verticalSwiping?o>=35&&o<=135?"down":"up":"vertical"},e.prototype.swipeEnd=function(i){var e,t,o=this;if(o.dragging=!1,o.swiping=!1,o.scrolling)return o.scrolling=!1,!1;if(o.interrupted=!1,o.shouldClick=!(o.touchObject.swipeLength>10),void 0===o.touchObject.curX)return!1;if(!0===o.touchObject.edgeHit&&o.$slider.trigger("edge",[o,o.swipeDirection()]),o.touchObject.swipeLength>=o.touchObject.minSwipe){switch(t=o.swipeDirection()){case"left":case"down":e=o.options.swipeToSlide?o.checkNavigable(o.currentSlide+o.getSlideCount()):o.currentSlide+o.getSlideCount(),o.currentDirection=0;break;case"right":case"up":e=o.options.swipeToSlide?o.checkNavigable(o.currentSlide-o.getSlideCount()):o.currentSlide-o.getSlideCount(),o.currentDirection=1}"vertical"!=t&&(o.slideHandler(e),o.touchObject={},o.$slider.trigger("swipe",[o,t]))}else o.touchObject.startX!==o.touchObject.curX&&(o.slideHandler(o.currentSlide),o.touchObject={})},e.prototype.swipeHandler=function(i){var e=this;if(!(!1===e.options.swipe||"ontouchend"in document&&!1===e.options.swipe||!1===e.options.draggable&&-1!==i.type.indexOf("mouse")))switch(e.touchObject.fingerCount=i.originalEvent&&void 0!==i.originalEvent.touches?i.originalEvent.touches.length:1,e.touchObject.minSwipe=e.listWidth/e.options.touchThreshold,!0===e.options.verticalSwiping&&(e.touchObject.minSwipe=e.listHeight/e.options.touchThreshold),i.data.action){case"start":e.swipeStart(i);break;case"move":e.swipeMove(i);break;case"end":e.swipeEnd(i)}},e.prototype.swipeMove=function(i){var e,t,o,s,n,r,l=this;return n=void 0!==i.originalEvent?i.originalEvent.touches:null,!(!l.dragging||l.scrolling||n&&1!==n.length)&&(e=l.getLeft(l.currentSlide),l.touchObject.curX=void 0!==n?n[0].pageX:i.clientX,l.touchObject.curY=void 0!==n?n[0].pageY:i.clientY,l.touchObject.swipeLength=Math.round(Math.sqrt(Math.pow(l.touchObject.curX-l.touchObject.startX,2))),r=Math.round(Math.sqrt(Math.pow(l.touchObject.curY-l.touchObject.startY,2))),!l.options.verticalSwiping&&!l.swiping&&r>4?(l.scrolling=!0,!1):(!0===l.options.verticalSwiping&&(l.touchObject.swipeLength=r),t=l.swipeDirection(),void 0!==i.originalEvent&&l.touchObject.swipeLength>4&&(l.swiping=!0,i.preventDefault()),s=(!1===l.options.rtl?1:-1)*(l.touchObject.curX>l.touchObject.startX?1:-1),!0===l.options.verticalSwiping&&(s=l.touchObject.curY>l.touchObject.startY?1:-1),o=l.touchObject.swipeLength,l.touchObject.edgeHit=!1,!1===l.options.infinite&&(0===l.currentSlide&&"right"===t||l.currentSlide>=l.getDotCount()&&"left"===t)&&(o=l.touchObject.swipeLength*l.options.edgeFriction,l.touchObject.edgeHit=!0),!1===l.options.vertical?l.swipeLeft=e+o*s:l.swipeLeft=e+o*(l.$list.height()/l.listWidth)*s,!0===l.options.verticalSwiping&&(l.swipeLeft=e+o*s),!0!==l.options.fade&&!1!==l.options.touchMove&&(!0===l.animating?(l.swipeLeft=null,!1):void l.setCSS(l.swipeLeft))))},e.prototype.swipeStart=function(i){var e,t=this;if(t.interrupted=!0,1!==t.touchObject.fingerCount||t.slideCount<=t.options.slidesToShow)return t.touchObject={},!1;void 0!==i.originalEvent&&void 0!==i.originalEvent.touches&&(e=i.originalEvent.touches[0]),t.touchObject.startX=t.touchObject.curX=void 0!==e?e.pageX:i.clientX,t.touchObject.startY=t.touchObject.curY=void 0!==e?e.pageY:i.clientY,t.dragging=!0},e.prototype.unfilterSlides=e.prototype.slickUnfilter=function(){var i=this;null!==i.$slidesCache&&(i.unload(),i.$slideTrack.children(this.options.slide).detach(),i.$slidesCache.appendTo(i.$slideTrack),i.reinit())},e.prototype.unload=function(){var e=this;i(".slick-cloned",e.$slider).remove(),e.$dots&&e.$dots.remove(),e.$prevArrow&&e.htmlExpr.test(e.options.prevArrow)&&e.$prevArrow.remove(),e.$nextArrow&&e.htmlExpr.test(e.options.nextArrow)&&e.$nextArrow.remove(),e.$slides.removeClass("slick-slide slick-active slick-visible slick-current").attr("aria-hidden","true").css("width","")},e.prototype.unslick=function(i){var e=this;e.$slider.trigger("unslick",[e,i]),e.destroy()},e.prototype.updateArrows=function(){var i=this;Math.floor(i.options.slidesToShow/2),!0===i.options.arrows&&i.slideCount>i.options.slidesToShow&&!i.options.infinite&&(i.$prevArrow.removeClass("slick-disabled").attr("aria-disabled","false"),i.$nextArrow.removeClass("slick-disabled").attr("aria-disabled","false"),0===i.currentSlide?(i.$prevArrow.addClass("slick-disabled").attr("aria-disabled","true"),i.$nextArrow.removeClass("slick-disabled").attr("aria-disabled","false")):i.currentSlide>=i.slideCount-i.options.slidesToShow&&!1===i.options.centerMode?(i.$nextArrow.addClass("slick-disabled").attr("aria-disabled","true"),i.$prevArrow.removeClass("slick-disabled").attr("aria-disabled","false")):i.currentSlide>=i.slideCount-1&&!0===i.options.centerMode&&(i.$nextArrow.addClass("slick-disabled").attr("aria-disabled","true"),i.$prevArrow.removeClass("slick-disabled").attr("aria-disabled","false")))},e.prototype.updateDots=function(){var i=this;null!==i.$dots&&(i.$dots.find("li").removeClass("slick-active").end(),i.$dots.find("li").eq(Math.floor(i.currentSlide/i.options.slidesToScroll)).addClass("slick-active"))},e.prototype.visibility=function(){var i=this;i.options.autoplay&&(document[i.hidden]?i.interrupted=!0:i.interrupted=!1)},i.fn.slick=function(){var i,t,o=this,s=arguments[0],n=Array.prototype.slice.call(arguments,1),r=o.length;for(i=0;i
+
+ Restant is One Of The Most Hygienic & Trusted Food Serviceq
+
+ Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.qqq
+
+ Restaurants range from inexpensive and informal lunching or dining places catering to people working nearby.q
+
+
+Knowq More
\ No newline at end of file
diff --git a/themes/30coffe/layouts/master.htm b/themes/30coffe/layouts/master.htm
new file mode 100644
index 00000000..bce8d2d8
--- /dev/null
+++ b/themes/30coffe/layouts/master.htm
@@ -0,0 +1,300 @@
+==
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{this.theme.site_name}} | {{ this.page.title }}
+
+
+ {% styles %}
+
+
+
+
+
+
+
+
+
+
+ {% page %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Copyright ©
+
+ Design & Developed by HiBootstrap
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/themes/30coffe/meta/menus/top_menu.yaml b/themes/30coffe/meta/menus/top_menu.yaml
new file mode 100644
index 00000000..f0c5e777
--- /dev/null
+++ b/themes/30coffe/meta/menus/top_menu.yaml
@@ -0,0 +1,86 @@
+items:
+ -
+ title: 'Baş Sahypa'
+ nesting: null
+ type: cms-page
+ url: null
+ code: ''
+ reference: home
+ cmsPage: null
+ replace: null
+ viewBag:
+ locale:
+ ru:
+ title: ''
+ url: ''
+ isHidden: '0'
+ cssClass: ''
+ isExternal: '0'
+ -
+ title: 'Biz Barada'
+ type: url
+ url: /a
+ code: ''
+ viewBag:
+ locale:
+ ru:
+ title: ''
+ url: ''
+ isHidden: '0'
+ cssClass: ''
+ isExternal: '0'
+ items:
+ -
+ title: Hyzmatlarymyz
+ type: url
+ url: /s
+ code: ''
+ viewBag:
+ locale:
+ ru:
+ title: ''
+ url: ''
+ isHidden: '0'
+ cssClass: ''
+ isExternal: '0'
+ -
+ title: 'Habarlaşmak Üçin'
+ type: url
+ url: /d
+ code: ''
+ viewBag:
+ locale:
+ ru:
+ title: ''
+ url: ''
+ isHidden: '0'
+ cssClass: ''
+ isExternal: '0'
+ -
+ title: Habarlar
+ type: url
+ url: /f
+ code: ''
+ viewBag:
+ locale:
+ ru:
+ title: ''
+ url: ''
+ isHidden: '0'
+ cssClass: ''
+ isExternal: '0'
+ items:
+ -
+ title: Wideolar
+ type: url
+ url: /g
+ code: ''
+ viewBag:
+ locale:
+ ru:
+ title: ''
+ url: ''
+ isHidden: '0'
+ cssClass: ''
+ isExternal: '0'
+name: top_menu
diff --git a/themes/30coffe/meta/static-pages.yaml b/themes/30coffe/meta/static-pages.yaml
new file mode 100644
index 00000000..69beb95b
--- /dev/null
+++ b/themes/30coffe/meta/static-pages.yaml
@@ -0,0 +1 @@
+static-pages: { }
diff --git a/themes/30coffe/pages/home.htm b/themes/30coffe/pages/home.htm
new file mode 100644
index 00000000..7c5db6ee
--- /dev/null
+++ b/themes/30coffe/pages/home.htm
@@ -0,0 +1,10 @@
+title = "Baş Sahypa"
+url = "/"
+layout = "master"
+is_hidden = 0
+==
+{% partial 'home/slider' %}
+{% partial 'home/categories' %}
+{% partial 'home/content' %}
+{% partial 'home/products' %}
+{% partial 'home/posts' %}
\ No newline at end of file
diff --git a/themes/30coffe/partials/home/categories.htm b/themes/30coffe/partials/home/categories.htm
new file mode 100644
index 00000000..3401ee6c
--- /dev/null
+++ b/themes/30coffe/partials/home/categories.htm
@@ -0,0 +1,36 @@
+[viewBag]
+
+[CategoryList]
+==
+{% set obCategoryList = CategoryList.make().tree() %}
+{% if obCategoryList.isNotEmpty() %}
+
+
+
+
Kategoriýalar
+
Ýokary Hilli 30Coffee Naharlary
+
+
+ {% for obCategory in obCategoryList %}
+
+
+
+
+
+
+ {{obCategory.name}}
+
+
+
+
+
+
+
+
+
+
+ {% endfor %}
+
+
+
+{% endif %}
\ No newline at end of file
diff --git a/themes/30coffe/partials/home/content.htm b/themes/30coffe/partials/home/content.htm
new file mode 100644
index 00000000..47ecdff3
--- /dev/null
+++ b/themes/30coffe/partials/home/content.htm
@@ -0,0 +1,25 @@
+[viewBag]
+==
+
+
+
+
+
+
+
+
+
+ {% content 'home/contents' %}
+
+
+
+
+
\ No newline at end of file
diff --git a/themes/30coffe/partials/home/posts.htm b/themes/30coffe/partials/home/posts.htm
new file mode 100644
index 00000000..39f2c83b
--- /dev/null
+++ b/themes/30coffe/partials/home/posts.htm
@@ -0,0 +1,80 @@
+[viewBag]
+
+[blogPosts]
+pageNumber = "{{ :page }}"
+categoryFilter = "umumy"
+postsPerPage = 3
+noPostsMessage = "No posts found"
+sortOrder = "published_at desc"
+categoryPage = "home"
+postPage = "home"
+==
+{% component 'blogPosts' %}
+
+
+
+
Habarlar
+
+
+
+
+
+
+
+
+
+
01 May 2020
+
+
+
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
+ ut labore
+
Read More
+
+
+
+
+
+
+
+
+
+
02 May 2020
+
+
+
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
+ ut labore
+
Read More
+
+
+
+
+
+
+
+
+
+
03 May 2020
+
+
+
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
+ ut labore
+
Read More
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/themes/30coffe/partials/home/products.htm b/themes/30coffe/partials/home/products.htm
new file mode 100644
index 00000000..62499989
--- /dev/null
+++ b/themes/30coffe/partials/home/products.htm
@@ -0,0 +1,88 @@
+[viewBag]
+
+[CategoryList]
+
+[CategoryData]
+
+[ProductList]
+sorting = "no"
+==
+{% set obCategoryList = CategoryList.make().tree() %}
+
+
+
+
+
Our Regular Food Collections
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore
+ et dolore magna aliqua.
+
+
+
+ {% if obCategoryList.isNotEmpty() %}
+
+ {% for obCategory in obCategoryList %}
+
+ {% if obCategory.featured == 1 %}
+ {% set obCategoryq = CategoryData.get(obCategory.id) %}
+ {% set obProductList = ProductList.make().sort(ProductList.getSorting()).active().category(obCategoryq.id) %}
+
+ {% for obProduct in obProductList %}
+ {% set obOffer = obProduct.offer.first() %}
+
+
+
+
+ {% if obProduct.preview_image is not empty %}
+
+ {% endif %}
+
+
+
+
{{ obProduct.name }}
+
+
+
+
+ {% endfor %}
+ {% endif %}
+ {% endfor %}
+ {% endif %}
+
+
+
+
+
\ No newline at end of file
diff --git a/themes/30coffe/partials/home/slider.htm b/themes/30coffe/partials/home/slider.htm
new file mode 100644
index 00000000..79a9890b
--- /dev/null
+++ b/themes/30coffe/partials/home/slider.htm
@@ -0,0 +1,41 @@
+[builderList Slider]
+modelClass = "Akami\Coffe30\Models\Slider"
+scope = "-"
+scopeValue = "{{ :scope }}"
+displayColumn = "id"
+noRecordsMessage = "No records found"
+detailsPage = "-"
+detailsUrlParameter = "id"
+pageNumber = "{{ :page }}"
+sortColumn = "id"
+sortDirection = "desc"
+
+[viewBag]
+==
+{% set records = Slider.records %}
+{% set displayColumn = Slider.displayColumn %}
+{% set noRecordsMessage = Slider.noRecordsMessage %}
+{% set detailsPage = Slider.detailsPage %}
+{% set detailsKeyColumn = Slider.detailsKeyColumn %}
+{% set detailsUrlParameter = Slider.detailsUrlParameter %}
+
+
+
+
+
+
+
+
+
+ {% for record in records %}
+
+
+
+ {% endfor %}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/themes/30coffe/partials/menu.htm b/themes/30coffe/partials/menu.htm
new file mode 100644
index 00000000..49e084c0
--- /dev/null
+++ b/themes/30coffe/partials/menu.htm
@@ -0,0 +1,26 @@
+[staticMenu]
+code = "top_menu"
+
+[viewBag]
+==
+
+{% for item in staticMenu.menuItems %}
+ {% if not item.isHidden and item.items|length %}
+
+ {{ item.title }}
+
+
+ {% elseif not item.viewBag.isHidden %}
+
+ {{ item.title }}
+
+ {% endif %}
+{% endfor %}
+
\ No newline at end of file
diff --git a/themes/30coffe/theme.yaml b/themes/30coffe/theme.yaml
new file mode 100644
index 00000000..8b2e5b6a
--- /dev/null
+++ b/themes/30coffe/theme.yaml
@@ -0,0 +1,6 @@
+name: 30Coffe
+description: ''
+author: AKAMI
+homepage: ''
+code: ''
+form: config/fields.yaml